🎨 feat(figgos): neo-brutalist game UI design system

Replace old corporate design with neo-brutalist game style:
- Electric yellow primary, hot pink secondary, teal accent
- Hard offset shadow layers on all interactive elements
- Thick 3px borders as core design language
- Restructure to flat tab layout (Create + Collection)
- Remove old auth/tabs/shelf screens
- Keep retro-pixel variant as reference (hidden tab)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Chr1st1anG 2026-02-11 13:41:36 +01:00
parent 9992b32e3e
commit 1c24ad9d5c
15 changed files with 989 additions and 488 deletions

View file

@ -6,7 +6,7 @@
"scheme": "figgos",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"userInterfaceStyle": "automatic",
"userInterfaceStyle": "dark",
"newArchEnabled": true,
"splash": {
"image": "./assets/images/splash-icon.png",

View file

@ -1,9 +0,0 @@
import { Stack } from 'expo-router';
export default function AuthLayout() {
return (
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="login" />
</Stack>
);
}

View file

@ -1,77 +0,0 @@
import { useState } from 'react';
import { View, Text, TextInput, Pressable, KeyboardAvoidingView, Platform } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useAuth } from '~/contexts/AuthContext';
export default function LoginScreen() {
const { signIn } = useAuth();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const handleLogin = async () => {
if (!email || !password) {
setError('Please enter email and password');
return;
}
setLoading(true);
setError(null);
const result = await signIn(email, password);
if (result.error) {
setError(result.error.message || 'Login failed');
}
setLoading(false);
};
return (
<SafeAreaView className="flex-1 bg-background">
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
className="flex-1 justify-center px-6"
>
<View className="items-center mb-12">
<Text className="text-4xl font-bold text-primary">Figgos</Text>
<Text className="text-base text-muted-foreground mt-2">Collect your action figures</Text>
</View>
<View className="space-y-4">
<TextInput
className="bg-surface border border-border rounded-lg px-4 py-3 text-foreground"
placeholder="Email"
placeholderTextColor="rgb(99 110 114)"
value={email}
onChangeText={setEmail}
autoCapitalize="none"
keyboardType="email-address"
/>
<TextInput
className="bg-surface border border-border rounded-lg px-4 py-3 text-foreground mt-3"
placeholder="Password"
placeholderTextColor="rgb(99 110 114)"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
{error && <Text className="text-destructive text-center mt-2">{error}</Text>}
<Pressable
onPress={handleLogin}
disabled={loading}
className={`bg-primary rounded-lg py-3 mt-4 active:opacity-80 ${loading ? 'opacity-50' : ''}`}
>
<Text className="text-primary-foreground text-center font-semibold text-base">
{loading ? 'Signing in...' : 'Sign In'}
</Text>
</Pressable>
</View>
</KeyboardAvoidingView>
</SafeAreaView>
);
}

View file

@ -1,49 +0,0 @@
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
export default function TabLayout() {
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: 'rgb(108, 92, 231)',
tabBarInactiveTintColor: 'rgb(178, 190, 195)',
tabBarStyle: {
backgroundColor: 'rgb(255, 255, 255)',
borderTopColor: 'rgb(223, 230, 233)',
},
headerStyle: {
backgroundColor: 'rgb(255, 255, 255)',
},
headerTintColor: 'rgb(45, 52, 54)',
}}
>
<Tabs.Screen
name="index"
options={{
title: 'Community',
tabBarIcon: ({ color, size }) => (
<Ionicons name="globe-outline" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="create"
options={{
title: 'Create',
tabBarIcon: ({ color, size }) => (
<Ionicons name="add-circle-outline" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="shelf"
options={{
title: 'Collection',
tabBarIcon: ({ color, size }) => (
<Ionicons name="grid-outline" size={size} color={color} />
),
}}
/>
</Tabs>
);
}

View file

@ -1,150 +0,0 @@
import { useState } from 'react';
import {
View,
Text,
TextInput,
Pressable,
ScrollView,
KeyboardAvoidingView,
Platform,
ActivityIndicator,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { api } from '~/services/api';
import type { FigureResponse, FigureRarity } from '@figgos/shared';
const RARITY_STYLES: Record<FigureRarity, { bg: string; text: string }> = {
common: { bg: 'bg-rarity-common', text: 'text-rarity-common-foreground' },
rare: { bg: 'bg-rarity-rare', text: 'text-rarity-rare-foreground' },
epic: { bg: 'bg-rarity-epic', text: 'text-rarity-epic-foreground' },
legendary: { bg: 'bg-rarity-legendary', text: 'text-rarity-legendary-foreground' },
};
function RarityBadge({ rarity }: { rarity: FigureRarity }) {
const s = RARITY_STYLES[rarity];
return (
<View className={`${s.bg} px-3 py-1 rounded-full`}>
<Text className={`${s.text} text-xs font-bold uppercase`}>{rarity}</Text>
</View>
);
}
export default function CreateScreen() {
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [result, setResult] = useState<FigureResponse | null>(null);
const handleGenerate = async () => {
if (!name.trim() || !description.trim()) {
setError('Please enter a name and description');
return;
}
setLoading(true);
setError(null);
try {
const { figure } = await api.figures.create(name.trim(), description.trim());
setResult(figure);
} catch (e: any) {
setError(e.message || 'Failed to create figure');
} finally {
setLoading(false);
}
};
const handleReset = () => {
setName('');
setDescription('');
setResult(null);
setError(null);
};
if (result) {
return (
<SafeAreaView className="flex-1 bg-background" edges={['bottom']}>
<ScrollView className="flex-1 px-6 pt-4">
<View className="bg-surface rounded-2xl border border-border p-6 items-center">
<View className="w-48 h-48 bg-muted rounded-xl items-center justify-center mb-4">
<Text className="text-4xl">🎭</Text>
</View>
<Text className="text-xl font-bold text-foreground">{result.name}</Text>
<Text className="text-sm text-muted-foreground mt-1 text-center">
{result.userInput.description}
</Text>
<View className="mt-3">
<RarityBadge rarity={result.rarity} />
</View>
</View>
<Pressable
onPress={handleReset}
className="bg-primary rounded-lg py-3 mt-6 mb-8 active:opacity-80"
>
<Text className="text-primary-foreground text-center font-semibold">
Create Another
</Text>
</Pressable>
</ScrollView>
</SafeAreaView>
);
}
return (
<SafeAreaView className="flex-1 bg-background" edges={['bottom']}>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
className="flex-1"
>
<ScrollView className="flex-1 px-6 pt-4">
<Text className="text-2xl font-bold text-foreground mb-2">Create a Figure</Text>
<Text className="text-muted-foreground mb-6">
Describe your character and we'll generate a collectible figure.
</Text>
<Text className="text-sm font-medium text-foreground mb-1">Name</Text>
<TextInput
className="bg-surface border border-border rounded-lg px-4 py-3 text-foreground mb-4"
placeholder="e.g. Captain Thunderstrike"
placeholderTextColor="rgb(99 110 114)"
value={name}
onChangeText={setName}
maxLength={200}
/>
<Text className="text-sm font-medium text-foreground mb-1">Description</Text>
<TextInput
className="bg-surface border border-border rounded-lg px-4 py-3 text-foreground mb-4"
placeholder="e.g. A cyberpunk warrior with lightning gauntlets"
placeholderTextColor="rgb(99 110 114)"
value={description}
onChangeText={setDescription}
multiline
numberOfLines={3}
style={{ textAlignVertical: 'top', minHeight: 80 }}
maxLength={2000}
/>
{error && <Text className="text-destructive text-center mb-4">{error}</Text>}
<Pressable
onPress={handleGenerate}
disabled={loading}
className={`bg-primary rounded-lg py-4 active:opacity-80 ${loading ? 'opacity-50' : ''}`}
>
{loading ? (
<View className="flex-row items-center justify-center">
<ActivityIndicator color="white" size="small" />
<Text className="text-primary-foreground font-semibold ml-2">Generating...</Text>
</View>
) : (
<Text className="text-primary-foreground text-center font-semibold text-base">
Generate Figure
</Text>
)}
</Pressable>
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
);
}

View file

@ -1,15 +0,0 @@
import { View, Text } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function CommunityScreen() {
return (
<SafeAreaView className="flex-1 bg-background" edges={['bottom']}>
<View className="flex-1 items-center justify-center px-6">
<Text className="text-2xl font-bold text-foreground">Community</Text>
<Text className="text-muted-foreground mt-2 text-center">
Public figures from the community will appear here.
</Text>
</View>
</SafeAreaView>
);
}

View file

@ -1,95 +0,0 @@
import { useState, useEffect, useCallback } from 'react';
import { View, Text, FlatList, ActivityIndicator } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { api } from '~/services/api';
import type { FigureResponse, FigureRarity } from '@figgos/shared';
const RARITY_STYLES: Record<FigureRarity, { bg: string; text: string }> = {
common: { bg: 'bg-rarity-common', text: 'text-rarity-common-foreground' },
rare: { bg: 'bg-rarity-rare', text: 'text-rarity-rare-foreground' },
epic: { bg: 'bg-rarity-epic', text: 'text-rarity-epic-foreground' },
legendary: { bg: 'bg-rarity-legendary', text: 'text-rarity-legendary-foreground' },
};
function RarityBadge({ rarity }: { rarity: FigureRarity }) {
const s = RARITY_STYLES[rarity];
return (
<View className={`${s.bg} px-2 py-0.5 rounded-full`}>
<Text className={`${s.text} text-[10px] font-bold uppercase`}>{rarity}</Text>
</View>
);
}
function FigureCard({ figure }: { figure: FigureResponse }) {
return (
<View className="flex-1 bg-surface rounded-xl border border-border p-3 m-1.5 items-center">
<View className="w-full aspect-square bg-muted rounded-lg items-center justify-center mb-2">
<Text className="text-3xl">🎭</Text>
</View>
<Text className="text-sm font-semibold text-foreground text-center" numberOfLines={1}>
{figure.name}
</Text>
<View className="mt-1">
<RarityBadge rarity={figure.rarity} />
</View>
</View>
);
}
export default function ShelfScreen() {
const [figures, setFigures] = useState<FigureResponse[]>([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const loadFigures = useCallback(async () => {
try {
const { figures: data } = await api.figures.list();
setFigures(data);
} catch (e) {
console.error('Failed to load figures:', e);
} finally {
setLoading(false);
setRefreshing(false);
}
}, []);
useEffect(() => {
loadFigures();
}, [loadFigures]);
const handleRefresh = () => {
setRefreshing(true);
loadFigures();
};
if (loading) {
return (
<SafeAreaView className="flex-1 bg-background items-center justify-center" edges={['bottom']}>
<ActivityIndicator size="large" color="rgb(108, 92, 231)" />
</SafeAreaView>
);
}
return (
<SafeAreaView className="flex-1 bg-background" edges={['bottom']}>
<FlatList
data={figures}
keyExtractor={(item) => item.id}
numColumns={2}
contentContainerStyle={{ padding: 12 }}
renderItem={({ item }) => <FigureCard figure={item} />}
onRefresh={handleRefresh}
refreshing={refreshing}
ListEmptyComponent={
<View className="flex-1 items-center justify-center pt-20">
<Text className="text-4xl mb-4">📦</Text>
<Text className="text-lg font-semibold text-foreground">No figures yet</Text>
<Text className="text-muted-foreground mt-1 text-center">
Head to the Create tab to generate your first figure!
</Text>
</View>
}
/>
</SafeAreaView>
);
}

View file

@ -1,16 +0,0 @@
import { View, Text } from 'react-native';
import { Link, Stack } from 'expo-router';
export default function NotFoundScreen() {
return (
<>
<Stack.Screen options={{ title: 'Not Found' }} />
<View className="flex-1 items-center justify-center bg-background">
<Text className="text-xl font-bold text-foreground">Page not found</Text>
<Link href="/(tabs)" className="mt-4">
<Text className="text-primary text-base">Go to home</Text>
</Link>
</View>
</>
);
}

View file

@ -1,14 +1,20 @@
import '../global.css';
import { Stack } from 'expo-router';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { NativeTabs, Icon, Label } from 'expo-router/unstable-native-tabs';
export default function RootLayout() {
export default function TabLayout() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
</GestureHandlerRootView>
<NativeTabs tintColor="rgb(255, 204, 0)" backgroundColor="rgb(15, 15, 30)">
<NativeTabs.Trigger name="index">
<Icon sf="plus.circle.fill" drawable="add_circle" />
<Label>Create</Label>
</NativeTabs.Trigger>
<NativeTabs.Trigger name="collection">
<Icon sf="square.grid.2x2.fill" drawable="grid_view" />
<Label>Collection</Label>
</NativeTabs.Trigger>
<NativeTabs.Trigger name="neo-brutalist" hidden />
<NativeTabs.Trigger name="retro-pixel" hidden />
</NativeTabs>
);
}

View file

@ -0,0 +1,46 @@
import { View, Text } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function CollectionScreen() {
return (
<SafeAreaView className="flex-1 bg-background" edges={['top']}>
<View className="flex-1 items-center justify-center px-8">
{/* Empty state card */}
<View style={{ position: 'relative' }}>
<View
className="bg-primary-dark rounded-lg"
style={{ position: 'absolute', top: 5, left: 5, right: -5, bottom: -5 }}
/>
<View
className="bg-surface rounded-lg items-center"
style={{
borderWidth: 3,
borderColor: 'rgb(255, 204, 0)',
paddingHorizontal: 32,
paddingVertical: 32,
}}
>
<View
className="bg-input rounded-lg items-center justify-center mb-4"
style={{ width: 56, height: 56, borderWidth: 2, borderColor: 'rgb(50, 50, 80)' }}
>
<Text style={{ fontSize: 24 }}>📦</Text>
</View>
<Text
className="text-foreground"
style={{ fontSize: 18, fontWeight: '900', letterSpacing: -0.3 }}
>
No figures yet
</Text>
<Text
className="text-muted-foreground text-center mt-2"
style={{ fontSize: 14, lineHeight: 20 }}
>
Create your first Figgo{'\n'}to start your collection.
</Text>
</View>
</View>
</View>
</SafeAreaView>
);
}

View file

@ -0,0 +1,412 @@
import { useState } from 'react';
import {
View,
Text,
TextInput,
Pressable,
ScrollView,
KeyboardAvoidingView,
Platform,
ActivityIndicator,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import type { FigureResponse, FigureRarity } from '@figgos/shared';
// ── Rarity ──
const RARITY_SHADOW: Record<FigureRarity, string> = {
common: 'rgb(80, 90, 100)',
rare: 'rgb(60, 120, 180)',
epic: 'rgb(120, 80, 180)',
legendary: 'rgb(180, 130, 20)',
};
function RarityBadge({ rarity }: { rarity: FigureRarity }) {
const shadowColor = RARITY_SHADOW[rarity];
const bgClass = `bg-rarity-${rarity}`;
const fgClass = `text-rarity-${rarity}-foreground`;
return (
<View style={{ position: 'relative', alignSelf: 'center' }}>
<View
style={{
position: 'absolute',
top: 3,
left: 2,
right: -2,
bottom: -3,
borderRadius: 999,
backgroundColor: shadowColor,
}}
/>
<View
className={`${bgClass} rounded-full`}
style={{
paddingHorizontal: 20,
paddingVertical: 8,
borderWidth: 2,
borderColor: 'rgba(255,255,255,0.2)',
}}
>
<Text
className={`${fgClass}`}
style={{ fontSize: 12, fontWeight: '900', letterSpacing: 2, textTransform: 'uppercase' }}
>
{rarity}
</Text>
</View>
</View>
);
}
// ── Screen ──
export default function CreateScreen() {
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [result, setResult] = useState<FigureResponse | null>(null);
const handleGenerate = async () => {
if (!name.trim() || !description.trim()) {
setError('Give your figure a name and a story');
return;
}
setLoading(true);
setError(null);
try {
await new Promise((r) => setTimeout(r, 1500));
const rarities: FigureRarity[] = [
'common',
'common',
'common',
'rare',
'rare',
'epic',
'legendary',
];
setResult({
id: 'mock-id',
userId: 'mock-user',
name: name.trim(),
userInput: { description: description.trim() },
imageUrl: null,
rarity: rarities[Math.floor(Math.random() * rarities.length)],
isPublic: false,
isArchived: false,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
} catch (e: any) {
setError(e.message || 'Something went wrong');
} finally {
setLoading(false);
}
};
const handleReset = () => {
setName('');
setDescription('');
setResult(null);
setError(null);
};
// ── Result ──
if (result) {
return (
<SafeAreaView className="flex-1 bg-background" edges={['top']}>
<ScrollView className="flex-1" contentContainerStyle={{ paddingBottom: 40 }}>
<View className="px-6 pt-8 items-center">
{/* Badge */}
<View
className="bg-secondary rounded mb-5"
style={{
paddingHorizontal: 14,
paddingVertical: 4,
transform: [{ rotate: '-2deg' }],
}}
>
<Text
className="text-secondary-foreground"
style={{
fontSize: 11,
fontWeight: '900',
letterSpacing: 3,
textTransform: 'uppercase',
}}
>
Unboxing
</Text>
</View>
{/* Figure Card */}
<View className="w-full" style={{ position: 'relative' }}>
<View
className="bg-primary-dark rounded-lg"
style={{ position: 'absolute', top: 5, left: 5, right: -5, bottom: -5 }}
/>
<View
className="bg-surface rounded-lg"
style={{ borderWidth: 3, borderColor: 'rgb(255, 204, 0)', padding: 24 }}
>
{/* Image */}
<View
className="bg-input rounded-lg self-center items-center justify-center mb-5"
style={{
width: 200,
height: 200,
borderWidth: 2,
borderColor: 'rgb(50, 50, 80)',
}}
>
<Text className="text-muted-foreground" style={{ fontSize: 12 }}>
Image coming soon
</Text>
</View>
<Text
className="text-foreground text-center"
style={{ fontSize: 22, fontWeight: '900', letterSpacing: -0.3 }}
>
{result.name}
</Text>
<Text
className="text-muted-foreground text-center mt-3"
style={{ fontSize: 14, lineHeight: 20 }}
>
{result.userInput.description}
</Text>
<View className="mt-4">
<RarityBadge rarity={result.rarity} />
</View>
</View>
</View>
{/* Create Another */}
<View className="w-full mt-8">
<Pressable onPress={handleReset} className="active:opacity-90">
<View style={{ position: 'relative' }}>
<View
style={{
position: 'absolute',
top: 5,
left: 3,
right: -3,
bottom: -5,
borderRadius: 8,
backgroundColor: 'rgb(0, 150, 120)',
}}
/>
<View
className="bg-accent rounded-lg items-center justify-center"
style={{
paddingVertical: 16,
borderWidth: 2,
borderColor: 'rgba(255,255,255,0.15)',
}}
>
<Text
style={{
fontSize: 16,
fontWeight: '900',
color: 'rgb(15, 15, 30)',
letterSpacing: 1,
textTransform: 'uppercase',
}}
>
Create Another
</Text>
</View>
</View>
</Pressable>
</View>
</View>
</ScrollView>
</SafeAreaView>
);
}
// ── Create ──
return (
<SafeAreaView className="flex-1 bg-background" edges={['top']}>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
className="flex-1"
>
<ScrollView
className="flex-1"
contentContainerStyle={{ paddingBottom: 40 }}
keyboardShouldPersistTaps="handled"
>
{/* Header */}
<View className="px-6 pt-10 pb-8 items-center">
<View
className="bg-secondary rounded mb-2"
style={{
paddingHorizontal: 14,
paddingVertical: 4,
transform: [{ rotate: '-2deg' }],
}}
>
<Text
className="text-secondary-foreground"
style={{
fontSize: 11,
fontWeight: '900',
letterSpacing: 3,
textTransform: 'uppercase',
}}
>
New Drop
</Text>
</View>
<Text
className="text-foreground text-center"
style={{ fontSize: 32, fontWeight: '900', letterSpacing: -1 }}
>
CREATE YOUR{'\n'}FIGGO
</Text>
</View>
{/* Form */}
<View className="px-6">
{/* Name */}
<Text
className="text-primary mb-2"
style={{
fontSize: 13,
fontWeight: '900',
letterSpacing: 3,
textTransform: 'uppercase',
}}
>
Name
</Text>
<View style={{ position: 'relative', marginBottom: 24 }}>
<View
className="bg-primary-dark rounded-lg"
style={{ position: 'absolute', top: 5, left: 5, right: -5, bottom: -5 }}
/>
<TextInput
className="bg-input text-foreground rounded-lg"
style={{
borderWidth: 3,
borderColor: 'rgb(255, 204, 0)',
paddingHorizontal: 16,
height: 52,
fontSize: 16,
}}
placeholder="Captain Thunderstrike"
placeholderTextColor="rgb(136, 136, 170)"
value={name}
onChangeText={setName}
maxLength={200}
/>
</View>
{/* Story */}
<Text
className="text-primary mb-2"
style={{
fontSize: 13,
fontWeight: '900',
letterSpacing: 3,
textTransform: 'uppercase',
}}
>
Story
</Text>
<View style={{ position: 'relative', marginBottom: 24 }}>
<View
className="bg-primary-dark rounded-lg"
style={{ position: 'absolute', top: 5, left: 5, right: -5, bottom: -5 }}
/>
<TextInput
className="bg-input text-foreground rounded-lg"
style={{
borderWidth: 3,
borderColor: 'rgb(255, 204, 0)',
paddingHorizontal: 16,
paddingVertical: 14,
fontSize: 16,
minHeight: 120,
textAlignVertical: 'top',
}}
placeholder="A cyberpunk warrior with lightning gauntlets..."
placeholderTextColor="rgb(136, 136, 170)"
value={description}
onChangeText={setDescription}
multiline
numberOfLines={4}
maxLength={2000}
/>
</View>
{/* Error */}
{error && (
<View
className="bg-destructive/10 rounded-lg mb-4"
style={{ borderWidth: 2, borderColor: 'rgba(255, 80, 80, 0.3)', padding: 12 }}
>
<Text
className="text-destructive text-center"
style={{ fontSize: 14, fontWeight: '600' }}
>
{error}
</Text>
</View>
)}
{/* Generate Button */}
<Pressable
onPress={handleGenerate}
disabled={loading}
className={`active:opacity-90 ${loading ? 'opacity-60' : ''}`}
>
<View style={{ position: 'relative' }}>
<View
className="bg-primary-dark rounded-lg"
style={{ position: 'absolute', top: 6, left: 4, right: -4, bottom: -6 }}
/>
<View
className="bg-primary rounded-lg items-center justify-center"
style={{ paddingVertical: 18, borderWidth: 3, borderColor: 'rgb(255, 224, 102)' }}
>
{loading ? (
<View className="flex-row items-center">
<ActivityIndicator color="rgb(15, 15, 30)" size="small" />
<Text
className="text-primary-foreground ml-2"
style={{
fontSize: 18,
fontWeight: '900',
letterSpacing: 2,
textTransform: 'uppercase',
}}
>
Rolling...
</Text>
</View>
) : (
<Text
className="text-primary-foreground"
style={{
fontSize: 18,
fontWeight: '900',
letterSpacing: 2,
textTransform: 'uppercase',
}}
>
Generate Figgo
</Text>
)}
</View>
</View>
</Pressable>
</View>
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
);
}

View file

@ -0,0 +1,189 @@
import { View, Text, TextInput, Pressable, ScrollView } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
const C = {
bg: '#0f0f1e',
card: '#1a1a35',
border: '#ffcc00',
borderDark: '#b38f00',
accent: '#ff3366',
accentDark: '#b3234a',
text: '#f5f5f5',
textMuted: '#8888aa',
input: '#141428',
button: '#ffcc00',
buttonText: '#0f0f1e',
};
export default function NeoBrutalistScreen() {
return (
<SafeAreaView style={{ flex: 1, backgroundColor: C.bg }} edges={['top']}>
<ScrollView contentContainerStyle={{ paddingBottom: 60 }}>
{/* Header */}
<View
style={{ paddingHorizontal: 24, paddingTop: 40, paddingBottom: 32, alignItems: 'center' }}
>
<View
style={{
backgroundColor: C.accent,
paddingHorizontal: 14,
paddingVertical: 4,
borderRadius: 4,
marginBottom: 8,
transform: [{ rotate: '-2deg' }],
}}
>
<Text
style={{
fontSize: 11,
fontWeight: '900',
color: '#fff',
letterSpacing: 3,
textTransform: 'uppercase',
}}
>
New Drop
</Text>
</View>
<Text
style={{
fontSize: 32,
fontWeight: '900',
color: C.text,
textAlign: 'center',
letterSpacing: -1,
}}
>
CREATE YOUR{'\n'}FIGGO
</Text>
</View>
<View style={{ paddingHorizontal: 24 }}>
{/* Name */}
<Text
style={{
fontSize: 13,
fontWeight: '900',
color: C.border,
letterSpacing: 3,
textTransform: 'uppercase',
marginBottom: 8,
}}
>
Name
</Text>
<View style={{ position: 'relative', marginBottom: 24 }}>
<View
style={{
position: 'absolute',
top: 5,
left: 5,
right: -5,
bottom: -5,
backgroundColor: C.borderDark,
borderRadius: 8,
}}
/>
<TextInput
style={{
backgroundColor: C.input,
borderWidth: 3,
borderColor: C.border,
borderRadius: 8,
paddingHorizontal: 16,
height: 52,
fontSize: 16,
color: C.text,
}}
placeholder="Captain Thunderstrike"
placeholderTextColor={C.textMuted}
/>
</View>
{/* Story */}
<Text
style={{
fontSize: 13,
fontWeight: '900',
color: C.border,
letterSpacing: 3,
textTransform: 'uppercase',
marginBottom: 8,
}}
>
Story
</Text>
<View style={{ position: 'relative', marginBottom: 32 }}>
<View
style={{
position: 'absolute',
top: 5,
left: 5,
right: -5,
bottom: -5,
backgroundColor: C.borderDark,
borderRadius: 8,
}}
/>
<TextInput
style={{
backgroundColor: C.input,
borderWidth: 3,
borderColor: C.border,
borderRadius: 8,
paddingHorizontal: 16,
paddingVertical: 14,
fontSize: 16,
color: C.text,
minHeight: 120,
textAlignVertical: 'top',
}}
placeholder="A cyberpunk warrior with lightning gauntlets..."
placeholderTextColor={C.textMuted}
multiline
/>
</View>
{/* Button */}
<Pressable style={({ pressed }) => ({ opacity: pressed ? 0.9 : 1 })}>
<View style={{ position: 'relative' }}>
<View
style={{
position: 'absolute',
top: 6,
left: 4,
right: -4,
bottom: -6,
backgroundColor: C.borderDark,
borderRadius: 8,
}}
/>
<View
style={{
backgroundColor: C.button,
borderRadius: 8,
borderWidth: 3,
borderColor: '#ffe066',
paddingVertical: 18,
alignItems: 'center',
}}
>
<Text
style={{
fontSize: 18,
fontWeight: '900',
color: C.buttonText,
letterSpacing: 2,
textTransform: 'uppercase',
}}
>
Generate Figure
</Text>
</View>
</View>
</Pressable>
</View>
</ScrollView>
</SafeAreaView>
);
}

View file

@ -0,0 +1,271 @@
import { View, Text, TextInput, Pressable, ScrollView } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
const C = {
bg: '#1a1a2e',
card: '#16213e',
cardBorder: '#e94560',
blue: '#0f3460',
red: '#e94560',
redDark: '#a3304a',
yellow: '#f5c518',
yellowDark: '#b8940f',
text: '#eeeef0',
textMuted: '#6a6a8a',
inputBg: '#0f1a30',
inputBorder: '#2a3a5a',
};
export default function RetroPixelScreen() {
return (
<SafeAreaView style={{ flex: 1, backgroundColor: C.bg }} edges={['top']}>
<ScrollView contentContainerStyle={{ paddingBottom: 60 }}>
{/* Header */}
<View
style={{ paddingHorizontal: 24, paddingTop: 40, paddingBottom: 28, alignItems: 'center' }}
>
{/* Chunky badge */}
<View
style={{
backgroundColor: C.red,
borderRadius: 4,
paddingHorizontal: 16,
paddingVertical: 6,
marginBottom: 14,
borderWidth: 3,
borderColor: C.redDark,
}}
>
<Text
style={{
fontSize: 11,
fontWeight: '900',
color: '#fff',
letterSpacing: 4,
textTransform: 'uppercase',
}}
>
Player 1
</Text>
</View>
<Text
style={{
fontSize: 30,
fontWeight: '900',
color: C.text,
textAlign: 'center',
letterSpacing: 2,
textTransform: 'uppercase',
}}
>
Create your{'\n'}Figgo
</Text>
</View>
<View style={{ paddingHorizontal: 20 }}>
{/* Form container */}
<View
style={{
backgroundColor: C.card,
borderRadius: 4,
borderWidth: 3,
borderColor: C.inputBorder,
padding: 20,
marginBottom: 20,
}}
>
{/* Name */}
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 8 }}>
<View
style={{
width: 8,
height: 8,
backgroundColor: C.red,
borderRadius: 1,
marginRight: 8,
}}
/>
<Text
style={{
fontSize: 13,
fontWeight: '900',
color: C.red,
letterSpacing: 2,
textTransform: 'uppercase',
}}
>
Name
</Text>
</View>
<TextInput
style={{
backgroundColor: C.inputBg,
borderWidth: 2,
borderColor: C.inputBorder,
borderRadius: 4,
paddingHorizontal: 14,
height: 48,
fontSize: 16,
color: C.text,
marginBottom: 20,
}}
placeholder="Captain Thunderstrike"
placeholderTextColor={C.textMuted}
/>
{/* Story */}
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 8 }}>
<View
style={{
width: 8,
height: 8,
backgroundColor: C.yellow,
borderRadius: 1,
marginRight: 8,
}}
/>
<Text
style={{
fontSize: 13,
fontWeight: '900',
color: C.yellow,
letterSpacing: 2,
textTransform: 'uppercase',
}}
>
Story
</Text>
</View>
<TextInput
style={{
backgroundColor: C.inputBg,
borderWidth: 2,
borderColor: C.inputBorder,
borderRadius: 4,
paddingHorizontal: 14,
paddingVertical: 12,
fontSize: 16,
color: C.text,
minHeight: 120,
textAlignVertical: 'top',
}}
placeholder="A cyberpunk warrior with lightning gauntlets..."
placeholderTextColor={C.textMuted}
multiline
/>
</View>
{/* Button */}
<Pressable style={({ pressed }) => ({ transform: [{ scale: pressed ? 0.97 : 1 }] })}>
<View style={{ position: 'relative' }}>
<View
style={{
position: 'absolute',
top: 5,
left: 3,
right: -3,
bottom: -5,
backgroundColor: C.yellowDark,
borderRadius: 4,
}}
/>
<View
style={{
backgroundColor: C.yellow,
borderRadius: 4,
borderWidth: 3,
borderColor: '#ffe066',
paddingVertical: 16,
alignItems: 'center',
}}
>
<Text
style={{
fontSize: 18,
fontWeight: '900',
color: '#1a1a2e',
letterSpacing: 3,
textTransform: 'uppercase',
}}
>
Generate!
</Text>
</View>
</View>
</Pressable>
{/* Stats bar */}
<View
style={{
flexDirection: 'row',
justifyContent: 'space-around',
marginTop: 28,
backgroundColor: C.card,
borderRadius: 4,
borderWidth: 2,
borderColor: C.inputBorder,
paddingVertical: 12,
}}
>
<View style={{ alignItems: 'center' }}>
<Text
style={{
fontSize: 10,
fontWeight: '800',
color: C.textMuted,
letterSpacing: 1,
textTransform: 'uppercase',
}}
>
Common
</Text>
<Text style={{ fontSize: 16, fontWeight: '900', color: '#888' }}>60%</Text>
</View>
<View style={{ alignItems: 'center' }}>
<Text
style={{
fontSize: 10,
fontWeight: '800',
color: C.textMuted,
letterSpacing: 1,
textTransform: 'uppercase',
}}
>
Rare
</Text>
<Text style={{ fontSize: 16, fontWeight: '900', color: '#74b9ff' }}>25%</Text>
</View>
<View style={{ alignItems: 'center' }}>
<Text
style={{
fontSize: 10,
fontWeight: '800',
color: C.textMuted,
letterSpacing: 1,
textTransform: 'uppercase',
}}
>
Epic
</Text>
<Text style={{ fontSize: 16, fontWeight: '900', color: '#a29bfe' }}>12%</Text>
</View>
<View style={{ alignItems: 'center' }}>
<Text
style={{
fontSize: 10,
fontWeight: '800',
color: C.textMuted,
letterSpacing: 1,
textTransform: 'uppercase',
}}
>
Lgndy
</Text>
<Text style={{ fontSize: 16, fontWeight: '900', color: '#ffd73c' }}>3%</Text>
</View>
</View>
</View>
</ScrollView>
</SafeAreaView>
);
}

View file

@ -3,83 +3,64 @@
@tailwind utilities;
/* =============================================================================
Figgos Design System Toy Collectible Theme
Figgos Design System Neo-Brutalist Game UI
Color strategy:
- Primary: Vibrant purple playful, gaming, fun
- Secondary: Electric blue action, energy
- Accent: Teal green success, highlights
- Rarity colors: Common(gray), Rare(blue), Epic(purple), Legendary(gold)
- Primary: Electric Yellow bold, in-your-face, action
- Secondary: Hot Pink accents, badges, highlights
- Surface: Deep navy dark but warm enough for contrast
- Borders: Thick, visible, part of the design language
- Shadows: Hard offset layers, not soft blurs
Values are RGB triplets for Tailwind alpha support:
rgb(var(--color) / <alpha-value>)
============================================================================= */
/* Light Mode */
:root {
--background: 248 249 250;
--foreground: 45 52 54;
--surface: 255 255 255;
--surface-elevated: 255 255 255;
--muted: 223 230 233;
--muted-foreground: 99 110 114;
--background: 15 15 30;
--foreground: 245 245 245;
--primary: 108 92 231;
--primary-foreground: 255 255 255;
--secondary: 9 132 227;
--surface: 26 26 53;
--surface-elevated: 35 35 65;
--muted: 55 55 80;
--muted-foreground: 136 136 170;
/* Electric Yellow */
--primary: 255 204 0;
--primary-foreground: 15 15 30;
--primary-dark: 179 143 0;
/* Hot Pink */
--secondary: 255 51 102;
--secondary-foreground: 255 255 255;
--accent: 0 184 148;
--accent-foreground: 255 255 255;
--destructive: 231 76 60;
--destructive-foreground: 255 255 255;
--secondary-dark: 179 35 74;
--border: 223 230 233;
--input: 223 230 233;
--ring: 108 92 231;
/* Teal (success, positive actions) */
--accent: 0 210 170;
--accent-foreground: 15 15 30;
--accent-dark: 0 150 120;
--destructive: 255 80 80;
--destructive-foreground: 15 15 30;
/* Borders — yellow is the signature */
--border: 255 204 0;
--border-muted: 50 50 80;
--input: 20 20 40;
--ring: 255 204 0;
/* Rarity System */
--rarity-common: 178 190 195;
--rarity-rare: 9 132 227;
--rarity-epic: 108 92 231;
--rarity-legendary: 248 214 43;
--rarity-common: 136 136 170;
--rarity-common-foreground: 245 245 245;
/* Rarity Text (on rarity background) */
--rarity-common-foreground: 45 52 54;
--rarity-rare-foreground: 255 255 255;
--rarity-epic-foreground: 255 255 255;
--rarity-legendary-foreground: 30 30 30;
}
/* Dark Mode */
.dark {
--background: 26 26 46;
--foreground: 255 255 255;
--surface: 22 33 62;
--surface-elevated: 31 52 96;
--muted: 45 52 54;
--muted-foreground: 178 190 195;
--primary: 162 155 254;
--primary-foreground: 26 26 46;
--secondary: 116 185 255;
--secondary-foreground: 26 26 46;
--accent: 85 239 196;
--accent-foreground: 26 26 46;
--destructive: 255 107 107;
--destructive-foreground: 26 26 46;
--border: 45 52 54;
--input: 45 52 54;
--ring: 162 155 254;
/* Rarity System (brighter in dark mode) */
--rarity-common: 99 110 114;
--rarity-rare: 116 185 255;
--rarity-epic: 162 155 254;
--rarity-legendary: 248 214 43;
--rarity-common-foreground: 255 255 255;
--rarity-rare-foreground: 26 26 46;
--rarity-epic-foreground: 26 26 46;
--rarity-legendary-foreground: 30 30 30;
--rarity-rare: 100 180 255;
--rarity-rare-foreground: 15 15 30;
--rarity-epic: 180 130 255;
--rarity-epic-foreground: 15 15 30;
--rarity-legendary: 255 185 30;
--rarity-legendary-foreground: 25 25 25;
}

View file

@ -6,7 +6,6 @@ module.exports = {
theme: {
extend: {
colors: {
// Semantic tokens
background: 'rgb(var(--background) / <alpha-value>)',
foreground: 'rgb(var(--foreground) / <alpha-value>)',
surface: {
@ -20,24 +19,29 @@ module.exports = {
primary: {
DEFAULT: 'rgb(var(--primary) / <alpha-value>)',
foreground: 'rgb(var(--primary-foreground) / <alpha-value>)',
dark: 'rgb(var(--primary-dark) / <alpha-value>)',
},
secondary: {
DEFAULT: 'rgb(var(--secondary) / <alpha-value>)',
foreground: 'rgb(var(--secondary-foreground) / <alpha-value>)',
dark: 'rgb(var(--secondary-dark) / <alpha-value>)',
},
accent: {
DEFAULT: 'rgb(var(--accent) / <alpha-value>)',
foreground: 'rgb(var(--accent-foreground) / <alpha-value>)',
dark: 'rgb(var(--accent-dark) / <alpha-value>)',
},
destructive: {
DEFAULT: 'rgb(var(--destructive) / <alpha-value>)',
foreground: 'rgb(var(--destructive-foreground) / <alpha-value>)',
},
border: 'rgb(var(--border) / <alpha-value>)',
border: {
DEFAULT: 'rgb(var(--border) / <alpha-value>)',
muted: 'rgb(var(--border-muted) / <alpha-value>)',
},
input: 'rgb(var(--input) / <alpha-value>)',
ring: 'rgb(var(--ring) / <alpha-value>)',
// Rarity system (game-specific)
rarity: {
common: {
DEFAULT: 'rgb(var(--rarity-common) / <alpha-value>)',
@ -57,6 +61,9 @@ module.exports = {
},
},
},
borderWidth: {
3: '3px',
},
},
},
plugins: [],