diff --git a/apps/figgos/apps/mobile/app/(tabs)/_layout.tsx b/apps/figgos/apps/mobile/app/(tabs)/_layout.tsx
new file mode 100644
index 000000000..96c56a7f8
--- /dev/null
+++ b/apps/figgos/apps/mobile/app/(tabs)/_layout.tsx
@@ -0,0 +1,24 @@
+import { NativeTabs, Icon, Label } from 'expo-router/unstable-native-tabs';
+
+export default function TabLayout() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/figgos/apps/mobile/app/(tabs)/carousel.tsx b/apps/figgos/apps/mobile/app/(tabs)/carousel.tsx
new file mode 100644
index 000000000..3ab0ffa89
--- /dev/null
+++ b/apps/figgos/apps/mobile/app/(tabs)/carousel.tsx
@@ -0,0 +1,147 @@
+import { useRef } from 'react';
+import { View, Text, Image, Pressable, Animated, Dimensions } from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { useRouter } from 'expo-router';
+import { CARDS } from '../../data/cards';
+import type { FigureRarity } from '@figgos/shared';
+
+const { width: SCREEN_WIDTH } = Dimensions.get('window');
+const CARD_WIDTH = SCREEN_WIDTH * 0.65;
+const CARD_HEIGHT = CARD_WIDTH * 1.45;
+const SPACING = 14;
+const SIDE_SPACE = (SCREEN_WIDTH - CARD_WIDTH) / 2;
+
+const RARITY_COLORS: Record = {
+ common: 'rgb(136, 136, 170)',
+ rare: 'rgb(100, 180, 255)',
+ epic: 'rgb(180, 130, 255)',
+ legendary: 'rgb(255, 185, 30)',
+};
+
+export default function CarouselScreen() {
+ const router = useRouter();
+ const scrollX = useRef(new Animated.Value(0)).current;
+
+ return (
+
+
+
+ Showcase
+
+
+
+
+ item.id}
+ horizontal
+ showsHorizontalScrollIndicator={false}
+ snapToInterval={CARD_WIDTH + SPACING}
+ decelerationRate="fast"
+ contentContainerStyle={{
+ paddingHorizontal: SIDE_SPACE,
+ alignItems: 'center',
+ }}
+ onScroll={Animated.event([{ nativeEvent: { contentOffset: { x: scrollX } } }], {
+ useNativeDriver: true,
+ })}
+ renderItem={({ item, index }) => {
+ const inputRange = [
+ (index - 1) * (CARD_WIDTH + SPACING),
+ index * (CARD_WIDTH + SPACING),
+ (index + 1) * (CARD_WIDTH + SPACING),
+ ];
+
+ const scale = scrollX.interpolate({
+ inputRange,
+ outputRange: [0.85, 1, 0.85],
+ extrapolate: 'clamp',
+ });
+
+ const opacity = scrollX.interpolate({
+ inputRange,
+ outputRange: [0.5, 1, 0.5],
+ extrapolate: 'clamp',
+ });
+
+ const rotate = scrollX.interpolate({
+ inputRange,
+ outputRange: ['4deg', '0deg', '-4deg'],
+ extrapolate: 'clamp',
+ });
+
+ return (
+
+ router.push(`/card/v2/${item.id}` as any)}
+ className="active:opacity-90"
+ >
+
+
+
+
+
+ );
+ }}
+ />
+
+
+
+ {CARDS.map((card, i) => {
+ const inputRange = [
+ (i - 1) * (CARD_WIDTH + SPACING),
+ i * (CARD_WIDTH + SPACING),
+ (i + 1) * (CARD_WIDTH + SPACING),
+ ];
+
+ const dotScale = scrollX.interpolate({
+ inputRange,
+ outputRange: [1, 1.4, 1],
+ extrapolate: 'clamp',
+ });
+
+ const dotOpacity = scrollX.interpolate({
+ inputRange,
+ outputRange: [0.3, 1, 0.3],
+ extrapolate: 'clamp',
+ });
+
+ return (
+
+ );
+ })}
+
+
+ );
+}
diff --git a/apps/figgos/apps/mobile/app/(tabs)/collection.tsx b/apps/figgos/apps/mobile/app/(tabs)/collection.tsx
new file mode 100644
index 000000000..4062cfeee
--- /dev/null
+++ b/apps/figgos/apps/mobile/app/(tabs)/collection.tsx
@@ -0,0 +1,61 @@
+import { View, Text, Image, Pressable, FlatList, Dimensions } from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { useRouter } from 'expo-router';
+import { CARDS, type CardData } from '../../data/cards';
+
+const { width: SCREEN_WIDTH } = Dimensions.get('window');
+const GAP = 10;
+const PADDING = 14;
+const COLUMNS = 2;
+const CARD_WIDTH = (SCREEN_WIDTH - PADDING * 2 - GAP * (COLUMNS - 1)) / COLUMNS;
+const CARD_HEIGHT = CARD_WIDTH * 1.45;
+
+function CardThumbnail({ card }: { card: CardData }) {
+ const router = useRouter();
+
+ return (
+ router.push(`/card/v2/${card.id}` as any)}
+ style={{ width: CARD_WIDTH }}
+ className="active:opacity-80"
+ >
+
+
+
+
+ );
+}
+
+export default function CollectionScreen() {
+ return (
+
+
+
+ Collection
+
+
+ {CARDS.length} {CARDS.length === 1 ? 'Figgo' : 'Figgos'}
+
+
+
+ item.id}
+ contentContainerStyle={{ paddingHorizontal: PADDING, paddingBottom: 40 }}
+ columnWrapperStyle={{ gap: GAP, marginBottom: GAP }}
+ renderItem={({ item }) => }
+ />
+
+ );
+}
diff --git a/apps/figgos/apps/mobile/app/index.tsx b/apps/figgos/apps/mobile/app/(tabs)/index.tsx
similarity index 100%
rename from apps/figgos/apps/mobile/app/index.tsx
rename to apps/figgos/apps/mobile/app/(tabs)/index.tsx
diff --git a/apps/figgos/apps/mobile/app/neo-brutalist.tsx b/apps/figgos/apps/mobile/app/(tabs)/neo-brutalist.tsx
similarity index 100%
rename from apps/figgos/apps/mobile/app/neo-brutalist.tsx
rename to apps/figgos/apps/mobile/app/(tabs)/neo-brutalist.tsx
diff --git a/apps/figgos/apps/mobile/app/retro-pixel.tsx b/apps/figgos/apps/mobile/app/(tabs)/retro-pixel.tsx
similarity index 100%
rename from apps/figgos/apps/mobile/app/retro-pixel.tsx
rename to apps/figgos/apps/mobile/app/(tabs)/retro-pixel.tsx
diff --git a/apps/figgos/apps/mobile/app/(tabs)/shelf.tsx b/apps/figgos/apps/mobile/app/(tabs)/shelf.tsx
new file mode 100644
index 000000000..872f5ece6
--- /dev/null
+++ b/apps/figgos/apps/mobile/app/(tabs)/shelf.tsx
@@ -0,0 +1,117 @@
+import { View, Text, Image, Pressable, ScrollView, Dimensions } from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { useRouter } from 'expo-router';
+import { CARDS, type CardData } from '../../data/cards';
+import type { FigureRarity } from '@figgos/shared';
+
+const { width: SCREEN_WIDTH } = Dimensions.get('window');
+const CARD_WIDTH = SCREEN_WIDTH * 0.32;
+const CARD_HEIGHT = CARD_WIDTH * 1.45;
+const OVERLAP = -16;
+
+const RARITY_COLORS: Record = {
+ common: 'rgb(136, 136, 170)',
+ rare: 'rgb(100, 180, 255)',
+ epic: 'rgb(180, 130, 255)',
+ legendary: 'rgb(255, 185, 30)',
+};
+
+const RARITY_ORDER: FigureRarity[] = ['legendary', 'epic', 'rare', 'common'];
+const RARITY_LABELS: Record = {
+ legendary: 'LEGENDARY',
+ epic: 'EPIC',
+ rare: 'RARE',
+ common: 'COMMON',
+};
+
+function ShelfRow({ rarity, cards }: { rarity: FigureRarity; cards: CardData[] }) {
+ const router = useRouter();
+ const color = RARITY_COLORS[rarity];
+
+ if (cards.length === 0) return null;
+
+ return (
+
+
+ {RARITY_LABELS[rarity]}
+
+
+
+ {cards.map((card, i) => (
+ router.push(`/card/v2/${card.id}` as any)}
+ className="active:opacity-80"
+ style={{ marginRight: i < cards.length - 1 ? OVERLAP : 0 }}
+ >
+
+
+
+
+ ))}
+
+
+
+
+ );
+}
+
+export default function ShelfScreen() {
+ const grouped = RARITY_ORDER.map((rarity) => ({
+ rarity,
+ cards: CARDS.filter((c) => c.rarity === rarity),
+ }));
+
+ return (
+
+
+
+
+ Shelf
+
+
+
+ {grouped.map(({ rarity, cards }) => (
+
+ ))}
+
+
+ );
+}
diff --git a/apps/figgos/apps/mobile/app/(tabs)/stack.tsx b/apps/figgos/apps/mobile/app/(tabs)/stack.tsx
new file mode 100644
index 000000000..e95f90552
--- /dev/null
+++ b/apps/figgos/apps/mobile/app/(tabs)/stack.tsx
@@ -0,0 +1,191 @@
+import { useState, useRef, useCallback } from 'react';
+import { View, Text, Image, Pressable, Animated, Dimensions, PanResponder } from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { useRouter } from 'expo-router';
+import { CARDS } from '../../data/cards';
+import type { FigureRarity } from '@figgos/shared';
+
+const { width: SCREEN_WIDTH } = Dimensions.get('window');
+const CARD_WIDTH = SCREEN_WIDTH * 0.72;
+const CARD_HEIGHT = CARD_WIDTH * 1.45;
+const VISIBLE_STACK = 4;
+const SWIPE_THRESHOLD = 60;
+const STACK_OFFSET = 22;
+
+const RARITY_COLORS: Record = {
+ common: 'rgb(136, 136, 170)',
+ rare: 'rgb(100, 180, 255)',
+ epic: 'rgb(180, 130, 255)',
+ legendary: 'rgb(255, 185, 30)',
+};
+
+export default function StackScreen() {
+ const router = useRouter();
+ const [order, setOrder] = useState(() => CARDS.map((_, i) => i));
+ const [currentIndex, setCurrentIndex] = useState(0);
+ const swipeY = useRef(new Animated.Value(0)).current;
+ const isAnimating = useRef(false);
+
+ const dismissTop = useCallback(() => {
+ if (isAnimating.current) return;
+ isAnimating.current = true;
+
+ Animated.timing(swipeY, {
+ toValue: -500,
+ duration: 250,
+ useNativeDriver: true,
+ }).start(() => {
+ setOrder((prev) => [...prev.slice(1), prev[0]]);
+ setCurrentIndex((prev) => (prev + 1) % CARDS.length);
+ swipeY.setValue(0);
+ isAnimating.current = false;
+ });
+ }, [swipeY]);
+
+ const snapBack = useCallback(() => {
+ Animated.spring(swipeY, {
+ toValue: 0,
+ tension: 80,
+ friction: 10,
+ useNativeDriver: true,
+ }).start();
+ }, [swipeY]);
+
+ const panResponder = useRef(
+ PanResponder.create({
+ onStartShouldSetPanResponder: () => false,
+ onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dy) > 15,
+ onPanResponderMove: (_, g) => {
+ if (g.dy < 0) {
+ swipeY.setValue(g.dy);
+ }
+ },
+ onPanResponderRelease: (_, g) => {
+ if (g.dy < -SWIPE_THRESHOLD || g.vy < -0.5) {
+ dismissTop();
+ } else {
+ snapBack();
+ }
+ },
+ })
+ ).current;
+
+ const topCard = CARDS[order[0]];
+
+ const topOpacity = swipeY.interpolate({
+ inputRange: [-200, 0],
+ outputRange: [0.3, 1],
+ extrapolate: 'clamp',
+ });
+
+ // Total height needed: card + stack peek area
+ const stackHeight = CARD_HEIGHT + (VISIBLE_STACK - 1) * STACK_OFFSET;
+
+ return (
+
+
+
+ Stack
+
+
+ Swipe up to browse
+
+
+
+
+
+ {/* Background cards — each peeks out below the one above */}
+ {order
+ .slice(1, VISIBLE_STACK)
+ .reverse()
+ .map((cardIdx, reverseI) => {
+ const depth = VISIBLE_STACK - 1 - reverseI; // 3, 2, 1
+ const card = CARDS[cardIdx];
+ const shrink = depth * 8; // each card slightly narrower
+ return (
+
+
+
+
+
+ );
+ })}
+
+ {/* Top card */}
+
+ router.push(`/card/v2/${topCard.id}` as any)}
+ className="active:opacity-90"
+ style={{ width: '100%', height: '100%' }}
+ >
+
+
+
+
+
+
+
+ {/* Dot indicator */}
+
+ {CARDS.map((card, i) => (
+
+ ))}
+
+
+
+ );
+}
diff --git a/apps/figgos/apps/mobile/app/_layout.tsx b/apps/figgos/apps/mobile/app/_layout.tsx
index 8c1ae6f97..15a2e7311 100644
--- a/apps/figgos/apps/mobile/app/_layout.tsx
+++ b/apps/figgos/apps/mobile/app/_layout.tsx
@@ -1,20 +1,21 @@
import '../global.css';
-import { NativeTabs, Icon, Label } from 'expo-router/unstable-native-tabs';
+import { Stack } from 'expo-router';
+import { GestureHandlerRootView } from 'react-native-gesture-handler';
-export default function TabLayout() {
+export default function RootLayout() {
return (
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
);
}
diff --git a/apps/figgos/apps/mobile/app/card/[id].tsx b/apps/figgos/apps/mobile/app/card/[id].tsx
new file mode 100644
index 000000000..b77cc875b
--- /dev/null
+++ b/apps/figgos/apps/mobile/app/card/[id].tsx
@@ -0,0 +1,279 @@
+import { View, Text, Pressable, Image, Dimensions } from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { useLocalSearchParams, useRouter } from 'expo-router';
+import Animated, {
+ useSharedValue,
+ useAnimatedStyle,
+ withTiming,
+ interpolate,
+ Easing,
+} from 'react-native-reanimated';
+import { CARDS } from '../../data/cards';
+import type { FigureRarity } from '@figgos/shared';
+
+const { width: SCREEN_WIDTH } = Dimensions.get('window');
+const CARD_WIDTH = SCREEN_WIDTH - 48;
+const CARD_HEIGHT = CARD_WIDTH * 1.5;
+
+const RARITY_COLORS: Record = {
+ common: 'rgb(136, 136, 170)',
+ rare: 'rgb(100, 180, 255)',
+ epic: 'rgb(180, 130, 255)',
+ legendary: 'rgb(255, 185, 30)',
+};
+
+const STAT_COLORS = {
+ attack: 'rgb(255, 51, 102)',
+ defense: 'rgb(0, 210, 170)',
+ special: 'rgb(180, 130, 255)',
+};
+
+export default function CardDetailScreen() {
+ const { id } = useLocalSearchParams<{ id: string }>();
+ const router = useRouter();
+ const card = CARDS.find((c) => c.id === id);
+
+ const rotation = useSharedValue(0);
+ const isFlipped = useSharedValue(false);
+
+ const handleFlip = () => {
+ const target = isFlipped.value ? 0 : 180;
+ isFlipped.value = !isFlipped.value;
+ rotation.value = withTiming(target, {
+ duration: 600,
+ easing: Easing.bezier(0.4, 0, 0.2, 1),
+ });
+ };
+
+ const frontStyle = useAnimatedStyle(() => {
+ const rotateY = interpolate(rotation.value, [0, 180], [0, 180]);
+ return {
+ transform: [{ perspective: 1200 }, { rotateY: `${rotateY}deg` }],
+ backfaceVisibility: 'hidden' as const,
+ };
+ });
+
+ const backStyle = useAnimatedStyle(() => {
+ const rotateY = interpolate(rotation.value, [0, 180], [180, 360]);
+ return {
+ transform: [{ perspective: 1200 }, { rotateY: `${rotateY}deg` }],
+ backfaceVisibility: 'hidden' as const,
+ };
+ });
+
+ if (!card) {
+ return (
+
+
+ Card not found
+
+
+ );
+ }
+
+ return (
+
+ {/* Back button */}
+ router.back()}
+ className="active:opacity-70"
+ style={{ paddingHorizontal: 20, paddingVertical: 12 }}
+ >
+
+ ← Back
+
+
+
+
+
+
+ {/* ── Front: just the image ── */}
+
+
+
+
+ {/* ── Back ── */}
+
+ {/* Shadow layer */}
+
+ {/* Card back */}
+
+ {/* Header */}
+
+
+
+ Backstory
+
+
+
+ {card.name}
+
+
+ {card.subtitle}
+
+
+
+ {/* Description */}
+
+
+ {card.description}
+
+
+
+ {/* Stats */}
+
+
+ Stats
+
+
+
+
+
+
+ {/* Bottom: rarity + ID */}
+
+
+
+ {card.rarity}
+
+
+
+ #{card.id.split('-').pop()?.toUpperCase()}
+
+
+
+
+
+
+
+
+ );
+}
+
+function StatBar({ label, value, color }: { label: string; value: number; color: string }) {
+ return (
+
+
+ {label}
+
+
+
+
+
+ {value}
+
+
+ );
+}
diff --git a/apps/figgos/apps/mobile/app/card/v2/[id].tsx b/apps/figgos/apps/mobile/app/card/v2/[id].tsx
new file mode 100644
index 000000000..dd39538a3
--- /dev/null
+++ b/apps/figgos/apps/mobile/app/card/v2/[id].tsx
@@ -0,0 +1,329 @@
+import { useState } from 'react';
+import { View, Text, Pressable, Image, Dimensions } from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { useLocalSearchParams, useRouter } from 'expo-router';
+import { Gesture, GestureDetector } from 'react-native-gesture-handler';
+import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
+import { CARDS } from '../../../data/cards';
+import type { FigureRarity } from '@figgos/shared';
+
+const { width: SCREEN_WIDTH } = Dimensions.get('window');
+const CONTAINER_WIDTH = SCREEN_WIDTH - 48;
+const CONTAINER_HEIGHT = CONTAINER_WIDTH * 1.5;
+
+const RARITY_COLORS: Record = {
+ common: 'rgb(136, 136, 170)',
+ rare: 'rgb(100, 180, 255)',
+ epic: 'rgb(180, 130, 255)',
+ legendary: 'rgb(255, 185, 30)',
+};
+
+const STAT_COLORS = {
+ attack: 'rgb(255, 51, 102)',
+ defense: 'rgb(0, 210, 170)',
+ special: 'rgb(180, 130, 255)',
+};
+
+const SPRING_CONFIG = { damping: 20, stiffness: 200, mass: 0.8 };
+
+export default function CardDetailV2Screen() {
+ const { id } = useLocalSearchParams<{ id: string }>();
+ const router = useRouter();
+ const card = CARDS.find((c) => c.id === id);
+
+ // Track the actual rendered image size
+ const [imageSize, setImageSize] = useState({ width: CONTAINER_WIDTH, height: CONTAINER_HEIGHT });
+
+ const handleImageLayout = (e: { nativeEvent: { layout: { width: number; height: number } } }) => {
+ // Image uses contain, so we can read the actual layout
+ // But we need the source dimensions to compute the real rendered area
+ };
+
+ // Use Image.resolveAssetSource to get original dimensions, then compute contain size
+ const computeContainSize = (srcW: number, srcH: number) => {
+ const ratio = Math.min(CONTAINER_WIDTH / srcW, CONTAINER_HEIGHT / srcH);
+ setImageSize({
+ width: Math.round(srcW * ratio),
+ height: Math.round(srcH * ratio),
+ });
+ };
+
+ // Rotation around vertical axis only
+ const rotateY = useSharedValue(0);
+ const savedRotateY = useSharedValue(0);
+
+ const pan = Gesture.Pan()
+ .onUpdate((e) => {
+ rotateY.value = savedRotateY.value + e.translationX * 0.5;
+ })
+ .onEnd(() => {
+ const normalised = ((rotateY.value % 360) + 360) % 360;
+ let target: number;
+ if (normalised < 90) {
+ target = 0;
+ } else if (normalised < 270) {
+ target = 180;
+ } else {
+ target = 360;
+ }
+ const diff = target - normalised;
+ const snapTo = rotateY.value + diff;
+ savedRotateY.value = snapTo % 360;
+ rotateY.value = withSpring(snapTo, SPRING_CONFIG);
+ });
+
+ // Double tap to do a full flip
+ const doubleTap = Gesture.Tap()
+ .numberOfTaps(2)
+ .onEnd(() => {
+ const target = savedRotateY.value + 180;
+ savedRotateY.value = target % 360;
+ rotateY.value = withSpring(target, { damping: 18, stiffness: 150, mass: 1 });
+ });
+
+ const composed = Gesture.Race(doubleTap, pan);
+
+ const frontStyle = useAnimatedStyle(() => ({
+ transform: [{ perspective: 1200 }, { rotateY: `${rotateY.value}deg` }],
+ backfaceVisibility: 'hidden' as const,
+ }));
+
+ const backStyle = useAnimatedStyle(() => ({
+ transform: [{ perspective: 1200 }, { rotateY: `${rotateY.value + 180}deg` }],
+ backfaceVisibility: 'hidden' as const,
+ }));
+
+ if (!card) {
+ return (
+
+
+ Card not found
+
+
+ );
+ }
+
+ return (
+
+ {/* Back button */}
+
+ router.back()} className="active:opacity-70">
+
+ ← Back
+
+
+
+ V2 — Gesture 3D
+
+
+
+
+
+
+ {/* ── Front: just the image ── */}
+
+ {
+ const { width: srcW, height: srcH } = e.nativeEvent.source;
+ computeContainSize(srcW, srcH);
+ }}
+ />
+
+
+ {/* ── Back ── */}
+
+ {/* Shadow layer */}
+
+ {/* Card back */}
+
+ {/* Header */}
+
+
+
+ Backstory
+
+
+
+ {card.name}
+
+
+ {card.subtitle}
+
+
+
+ {/* Description */}
+
+ {card.description}
+
+
+ {/* Stats */}
+
+
+ Stats
+
+
+
+
+
+
+ {/* Bottom: rarity + ID */}
+
+
+
+ {card.rarity}
+
+
+
+ #{card.id.split('-').pop()?.toUpperCase()}
+
+
+
+
+
+
+
+ {/* Hint */}
+
+ Drag to rotate · Double-tap to flip
+
+
+
+ );
+}
+
+function StatBar({ label, value, color }: { label: string; value: number; color: string }) {
+ return (
+
+
+ {label}
+
+
+
+
+
+ {value}
+
+
+ );
+}
diff --git a/apps/figgos/apps/mobile/app/collection.tsx b/apps/figgos/apps/mobile/app/collection.tsx
deleted file mode 100644
index 3d93a9f81..000000000
--- a/apps/figgos/apps/mobile/app/collection.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-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/assets/images/cole-common.png b/apps/figgos/apps/mobile/assets/images/cole-common.png
new file mode 100644
index 000000000..68535deca
Binary files /dev/null and b/apps/figgos/apps/mobile/assets/images/cole-common.png differ
diff --git a/apps/figgos/apps/mobile/assets/images/cole-epic.png b/apps/figgos/apps/mobile/assets/images/cole-epic.png
new file mode 100644
index 000000000..5c85fe5d3
Binary files /dev/null and b/apps/figgos/apps/mobile/assets/images/cole-epic.png differ
diff --git a/apps/figgos/apps/mobile/assets/images/cole-kraft.png b/apps/figgos/apps/mobile/assets/images/cole-kraft.png
new file mode 100644
index 000000000..594a208bc
Binary files /dev/null and b/apps/figgos/apps/mobile/assets/images/cole-kraft.png differ
diff --git a/apps/figgos/apps/mobile/assets/images/cole-legendary.png b/apps/figgos/apps/mobile/assets/images/cole-legendary.png
new file mode 100644
index 000000000..4fc71f51e
Binary files /dev/null and b/apps/figgos/apps/mobile/assets/images/cole-legendary.png differ
diff --git a/apps/figgos/apps/mobile/assets/images/cole-rare.png b/apps/figgos/apps/mobile/assets/images/cole-rare.png
new file mode 100644
index 000000000..b414977d5
Binary files /dev/null and b/apps/figgos/apps/mobile/assets/images/cole-rare.png differ
diff --git a/apps/figgos/apps/mobile/data/cards.ts b/apps/figgos/apps/mobile/data/cards.ts
new file mode 100644
index 000000000..2f4707e47
--- /dev/null
+++ b/apps/figgos/apps/mobile/data/cards.ts
@@ -0,0 +1,65 @@
+import type { ImageSourcePropType } from 'react-native';
+import type { FigureRarity } from '@figgos/shared';
+
+export interface CardData {
+ id: string;
+ name: string;
+ subtitle: string;
+ description: string;
+ rarity: FigureRarity;
+ image: ImageSourcePropType;
+ stats: { attack: number; defense: number; special: number };
+}
+
+export const CARDS: CardData[] = [
+ {
+ id: 'cole-epic',
+ name: 'Detective Cole',
+ subtitle: 'Noir City Homicide Division',
+ description:
+ 'A hardboiled detective who has seen it all. Armed with nothing but a trench coat, a sharp mind, and an unhealthy coffee addiction. Solves impossible cases in the rain-soaked streets of Noir City.',
+ rarity: 'epic',
+ image: require('../assets/images/cole-epic.png'),
+ stats: { attack: 42, defense: 68, special: 75 },
+ },
+ {
+ id: 'cole-rare',
+ name: 'Detective Cole',
+ subtitle: 'Noir City Homicide Division',
+ description:
+ 'Fresh off his first big case, Cole is making a name for himself in the precinct. His instincts are sharp, but he still has a lot to learn about the darker side of Noir City.',
+ rarity: 'rare',
+ image: require('../assets/images/cole-rare.png'),
+ stats: { attack: 35, defense: 52, special: 60 },
+ },
+ {
+ id: 'cole-legendary',
+ name: 'Detective Cole',
+ subtitle: 'Noir City Homicide Division',
+ description:
+ 'The legend of Noir City. After decades on the force, Cole has become the detective other detectives tell stories about. His case closure rate is unmatched in the history of the division.',
+ rarity: 'legendary',
+ image: require('../assets/images/cole-legendary.png'),
+ stats: { attack: 78, defense: 85, special: 95 },
+ },
+ {
+ id: 'cole-common',
+ name: 'Detective Cole',
+ subtitle: 'Noir City Homicide Division',
+ description:
+ 'A standard-issue detective doing his best in a tough city. Nothing fancy, but reliable. Shows up every day, drinks too much coffee, and gets the job done.',
+ rarity: 'common',
+ image: require('../assets/images/cole-common.png'),
+ stats: { attack: 22, defense: 30, special: 28 },
+ },
+ {
+ id: 'cole-kraft',
+ name: 'Detective Cole',
+ subtitle: 'Kraft Edition',
+ description:
+ "Limited kraft paper edition. A collector's item with a vintage feel. The same old Cole, but with that handmade, artisanal charm that cardboard enthusiasts crave.",
+ rarity: 'common',
+ image: require('../assets/images/cole-kraft.png'),
+ stats: { attack: 25, defense: 32, special: 30 },
+ },
+];