mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:21:10 +02:00
🎨 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:
parent
9992b32e3e
commit
1c24ad9d5c
15 changed files with 989 additions and 488 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
import { Stack } from 'expo-router';
|
||||
|
||||
export default function AuthLayout() {
|
||||
return (
|
||||
<Stack screenOptions={{ headerShown: false }}>
|
||||
<Stack.Screen name="login" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
46
apps/figgos/apps/mobile/app/collection.tsx
Normal file
46
apps/figgos/apps/mobile/app/collection.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
412
apps/figgos/apps/mobile/app/index.tsx
Normal file
412
apps/figgos/apps/mobile/app/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
189
apps/figgos/apps/mobile/app/neo-brutalist.tsx
Normal file
189
apps/figgos/apps/mobile/app/neo-brutalist.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
271
apps/figgos/apps/mobile/app/retro-pixel.tsx
Normal file
271
apps/figgos/apps/mobile/app/retro-pixel.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue