From 1c24ad9d5c7bd70e82c3b285e23489c08a39f3d0 Mon Sep 17 00:00:00 2001 From: Chr1st1anG <73988455+Chr1st1anG@users.noreply.github.com> Date: Wed, 11 Feb 2026 13:41:36 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20feat(figgos):=20neo-brutalist=20?= =?UTF-8?q?game=20UI=20design=20system?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- apps/figgos/apps/mobile/app.json | 2 +- .../figgos/apps/mobile/app/(auth)/_layout.tsx | 9 - apps/figgos/apps/mobile/app/(auth)/login.tsx | 77 ---- .../figgos/apps/mobile/app/(tabs)/_layout.tsx | 49 --- apps/figgos/apps/mobile/app/(tabs)/create.tsx | 150 ------- apps/figgos/apps/mobile/app/(tabs)/index.tsx | 15 - apps/figgos/apps/mobile/app/(tabs)/shelf.tsx | 95 ---- apps/figgos/apps/mobile/app/+not-found.tsx | 16 - apps/figgos/apps/mobile/app/_layout.tsx | 22 +- apps/figgos/apps/mobile/app/collection.tsx | 46 ++ apps/figgos/apps/mobile/app/index.tsx | 412 ++++++++++++++++++ apps/figgos/apps/mobile/app/neo-brutalist.tsx | 189 ++++++++ apps/figgos/apps/mobile/app/retro-pixel.tsx | 271 ++++++++++++ apps/figgos/apps/mobile/global.css | 111 ++--- apps/figgos/apps/mobile/tailwind.config.js | 13 +- 15 files changed, 989 insertions(+), 488 deletions(-) delete mode 100644 apps/figgos/apps/mobile/app/(auth)/_layout.tsx delete mode 100644 apps/figgos/apps/mobile/app/(auth)/login.tsx delete mode 100644 apps/figgos/apps/mobile/app/(tabs)/_layout.tsx delete mode 100644 apps/figgos/apps/mobile/app/(tabs)/create.tsx delete mode 100644 apps/figgos/apps/mobile/app/(tabs)/index.tsx delete mode 100644 apps/figgos/apps/mobile/app/(tabs)/shelf.tsx delete mode 100644 apps/figgos/apps/mobile/app/+not-found.tsx create mode 100644 apps/figgos/apps/mobile/app/collection.tsx create mode 100644 apps/figgos/apps/mobile/app/index.tsx create mode 100644 apps/figgos/apps/mobile/app/neo-brutalist.tsx create mode 100644 apps/figgos/apps/mobile/app/retro-pixel.tsx diff --git a/apps/figgos/apps/mobile/app.json b/apps/figgos/apps/mobile/app.json index b13ce9593..888d972f7 100644 --- a/apps/figgos/apps/mobile/app.json +++ b/apps/figgos/apps/mobile/app.json @@ -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", diff --git a/apps/figgos/apps/mobile/app/(auth)/_layout.tsx b/apps/figgos/apps/mobile/app/(auth)/_layout.tsx deleted file mode 100644 index 819279f22..000000000 --- a/apps/figgos/apps/mobile/app/(auth)/_layout.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Stack } from 'expo-router'; - -export default function AuthLayout() { - return ( - - - - ); -} diff --git a/apps/figgos/apps/mobile/app/(auth)/login.tsx b/apps/figgos/apps/mobile/app/(auth)/login.tsx deleted file mode 100644 index 759c75fa3..000000000 --- a/apps/figgos/apps/mobile/app/(auth)/login.tsx +++ /dev/null @@ -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(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 ( - - - - Figgos - Collect your action figures - - - - - - - - {error && {error}} - - - - {loading ? 'Signing in...' : 'Sign In'} - - - - - - ); -} diff --git a/apps/figgos/apps/mobile/app/(tabs)/_layout.tsx b/apps/figgos/apps/mobile/app/(tabs)/_layout.tsx deleted file mode 100644 index 7275f38bf..000000000 --- a/apps/figgos/apps/mobile/app/(tabs)/_layout.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Tabs } from 'expo-router'; -import { Ionicons } from '@expo/vector-icons'; - -export default function TabLayout() { - return ( - - ( - - ), - }} - /> - ( - - ), - }} - /> - ( - - ), - }} - /> - - ); -} diff --git a/apps/figgos/apps/mobile/app/(tabs)/create.tsx b/apps/figgos/apps/mobile/app/(tabs)/create.tsx deleted file mode 100644 index a9ea88e82..000000000 --- a/apps/figgos/apps/mobile/app/(tabs)/create.tsx +++ /dev/null @@ -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 = { - 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 ( - - {rarity} - - ); -} - -export default function CreateScreen() { - const [name, setName] = useState(''); - const [description, setDescription] = useState(''); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [result, setResult] = useState(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 ( - - - - - 🎭 - - {result.name} - - {result.userInput.description} - - - - - - - - - Create Another - - - - - ); - } - - return ( - - - - Create a Figure - - Describe your character and we'll generate a collectible figure. - - - Name - - - Description - - - {error && {error}} - - - {loading ? ( - - - Generating... - - ) : ( - - Generate Figure - - )} - - - - - ); -} diff --git a/apps/figgos/apps/mobile/app/(tabs)/index.tsx b/apps/figgos/apps/mobile/app/(tabs)/index.tsx deleted file mode 100644 index 9ff76af4a..000000000 --- a/apps/figgos/apps/mobile/app/(tabs)/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { View, Text } from 'react-native'; -import { SafeAreaView } from 'react-native-safe-area-context'; - -export default function CommunityScreen() { - return ( - - - Community - - Public figures from the community will appear here. - - - - ); -} diff --git a/apps/figgos/apps/mobile/app/(tabs)/shelf.tsx b/apps/figgos/apps/mobile/app/(tabs)/shelf.tsx deleted file mode 100644 index 5d672bbc5..000000000 --- a/apps/figgos/apps/mobile/app/(tabs)/shelf.tsx +++ /dev/null @@ -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 = { - 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 ( - - {rarity} - - ); -} - -function FigureCard({ figure }: { figure: FigureResponse }) { - return ( - - - 🎭 - - - {figure.name} - - - - - - ); -} - -export default function ShelfScreen() { - const [figures, setFigures] = useState([]); - 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 ( - - - - ); - } - - return ( - - item.id} - numColumns={2} - contentContainerStyle={{ padding: 12 }} - renderItem={({ item }) => } - onRefresh={handleRefresh} - refreshing={refreshing} - ListEmptyComponent={ - - 📦 - No figures yet - - Head to the Create tab to generate your first figure! - - - } - /> - - ); -} diff --git a/apps/figgos/apps/mobile/app/+not-found.tsx b/apps/figgos/apps/mobile/app/+not-found.tsx deleted file mode 100644 index 25e0660e1..000000000 --- a/apps/figgos/apps/mobile/app/+not-found.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { View, Text } from 'react-native'; -import { Link, Stack } from 'expo-router'; - -export default function NotFoundScreen() { - return ( - <> - - - Page not found - - Go to home - - - - ); -} diff --git a/apps/figgos/apps/mobile/app/_layout.tsx b/apps/figgos/apps/mobile/app/_layout.tsx index 9ea1a4251..8c1ae6f97 100644 --- a/apps/figgos/apps/mobile/app/_layout.tsx +++ b/apps/figgos/apps/mobile/app/_layout.tsx @@ -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 ( - - - - - + + + + + + + + + + ); } diff --git a/apps/figgos/apps/mobile/app/collection.tsx b/apps/figgos/apps/mobile/app/collection.tsx new file mode 100644 index 000000000..3d93a9f81 --- /dev/null +++ b/apps/figgos/apps/mobile/app/collection.tsx @@ -0,0 +1,46 @@ +import { View, Text } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; + +export default function CollectionScreen() { + return ( + + + {/* Empty state card */} + + + + + 📦 + + + No figures yet + + + Create your first Figgo{'\n'}to start your collection. + + + + + + ); +} diff --git a/apps/figgos/apps/mobile/app/index.tsx b/apps/figgos/apps/mobile/app/index.tsx new file mode 100644 index 000000000..974bd8006 --- /dev/null +++ b/apps/figgos/apps/mobile/app/index.tsx @@ -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 = { + 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 ( + + + + + {rarity} + + + + ); +} + +// ── Screen ── + +export default function CreateScreen() { + const [name, setName] = useState(''); + const [description, setDescription] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [result, setResult] = useState(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 ( + + + + {/* Badge */} + + + Unboxing + + + + {/* Figure Card */} + + + + {/* Image */} + + + Image coming soon + + + + + {result.name} + + + {result.userInput.description} + + + + + + + + + {/* Create Another */} + + + + + + + Create Another + + + + + + + + + ); + } + + // ── Create ── + return ( + + + + {/* Header */} + + + + New Drop + + + + CREATE YOUR{'\n'}FIGGO + + + + {/* Form */} + + {/* Name */} + + Name + + + + + + + {/* Story */} + + Story + + + + + + + {/* Error */} + {error && ( + + + {error} + + + )} + + {/* Generate Button */} + + + + + {loading ? ( + + + + Rolling... + + + ) : ( + + Generate Figgo + + )} + + + + + + + + ); +} diff --git a/apps/figgos/apps/mobile/app/neo-brutalist.tsx b/apps/figgos/apps/mobile/app/neo-brutalist.tsx new file mode 100644 index 000000000..e326f4a9d --- /dev/null +++ b/apps/figgos/apps/mobile/app/neo-brutalist.tsx @@ -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 ( + + + {/* Header */} + + + + New Drop + + + + CREATE YOUR{'\n'}FIGGO + + + + + {/* Name */} + + Name + + + + + + + {/* Story */} + + Story + + + + + + + {/* Button */} + ({ opacity: pressed ? 0.9 : 1 })}> + + + + + Generate Figure + + + + + + + + ); +} diff --git a/apps/figgos/apps/mobile/app/retro-pixel.tsx b/apps/figgos/apps/mobile/app/retro-pixel.tsx new file mode 100644 index 000000000..1df30e231 --- /dev/null +++ b/apps/figgos/apps/mobile/app/retro-pixel.tsx @@ -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 ( + + + {/* Header */} + + {/* Chunky badge */} + + + Player 1 + + + + Create your{'\n'}Figgo + + + + + {/* Form container */} + + {/* Name */} + + + + Name + + + + + {/* Story */} + + + + Story + + + + + + {/* Button */} + ({ transform: [{ scale: pressed ? 0.97 : 1 }] })}> + + + + + Generate! + + + + + + {/* Stats bar */} + + + + Common + + 60% + + + + Rare + + 25% + + + + Epic + + 12% + + + + Lgndy + + 3% + + + + + + ); +} diff --git a/apps/figgos/apps/mobile/global.css b/apps/figgos/apps/mobile/global.css index 1220c7720..4ffd3bb49 100644 --- a/apps/figgos/apps/mobile/global.css +++ b/apps/figgos/apps/mobile/global.css @@ -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) / ) ============================================================================= */ -/* 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; } diff --git a/apps/figgos/apps/mobile/tailwind.config.js b/apps/figgos/apps/mobile/tailwind.config.js index 551dd2d6c..07591c67e 100644 --- a/apps/figgos/apps/mobile/tailwind.config.js +++ b/apps/figgos/apps/mobile/tailwind.config.js @@ -6,7 +6,6 @@ module.exports = { theme: { extend: { colors: { - // Semantic tokens background: 'rgb(var(--background) / )', foreground: 'rgb(var(--foreground) / )', surface: { @@ -20,24 +19,29 @@ module.exports = { primary: { DEFAULT: 'rgb(var(--primary) / )', foreground: 'rgb(var(--primary-foreground) / )', + dark: 'rgb(var(--primary-dark) / )', }, secondary: { DEFAULT: 'rgb(var(--secondary) / )', foreground: 'rgb(var(--secondary-foreground) / )', + dark: 'rgb(var(--secondary-dark) / )', }, accent: { DEFAULT: 'rgb(var(--accent) / )', foreground: 'rgb(var(--accent-foreground) / )', + dark: 'rgb(var(--accent-dark) / )', }, destructive: { DEFAULT: 'rgb(var(--destructive) / )', foreground: 'rgb(var(--destructive-foreground) / )', }, - border: 'rgb(var(--border) / )', + border: { + DEFAULT: 'rgb(var(--border) / )', + muted: 'rgb(var(--border-muted) / )', + }, input: 'rgb(var(--input) / )', ring: 'rgb(var(--ring) / )', - // Rarity system (game-specific) rarity: { common: { DEFAULT: 'rgb(var(--rarity-common) / )', @@ -57,6 +61,9 @@ module.exports = { }, }, }, + borderWidth: { + 3: '3px', + }, }, }, plugins: [],