From 8297dce650bb5b60ed554b05f46d0db3e4c29e86 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 2 Jul 2026 22:54:16 +1000 Subject: [PATCH 01/17] feat(app): add auth theme tokens and paw sprite --- apps/app/assets/images/paw.png | Bin 0 -> 294 bytes apps/app/lib/theme.ts | 6 ++++++ 2 files changed, 6 insertions(+) create mode 100755 apps/app/assets/images/paw.png diff --git a/apps/app/assets/images/paw.png b/apps/app/assets/images/paw.png new file mode 100755 index 0000000000000000000000000000000000000000..806e670d13d7f78b160fd9b61250215e14e284b0 GIT binary patch literal 294 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJTb?eCAr*{ord;G?a^!Kjs&&*z z=ZMh}y{X>~4Os4|96xwa@Zm(yGik@?RPC1eBgMSA@#cY?gv)Gl=YNa;vG0-D6Lv9| zxvq1IlK3vQ^BK}7*vjt9GcV=+5YzZ|U;KP+!6l-5-RuLRCrW)#ycEM>H$!H{O8u-) z9$5- Date: Thu, 2 Jul 2026 22:56:44 +1000 Subject: [PATCH 02/17] feat(app): add sage button variant for auth screens --- apps/app/components/Button.tsx | 41 +++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/apps/app/components/Button.tsx b/apps/app/components/Button.tsx index 6bbc880..0d4bb1d 100644 --- a/apps/app/components/Button.tsx +++ b/apps/app/components/Button.tsx @@ -2,8 +2,9 @@ import type { ComponentProps } from "react"; import { Pressable, StyleSheet, Text } from "react-native"; import { colors, fonts } from "@/app/theme"; +import { colors as authColors } from "@/lib/theme"; -type ButtonVariant = "primary" | "subtle"; +type ButtonVariant = "primary" | "subtle" | "sage"; type ButtonProps = { label: string; @@ -12,17 +13,28 @@ type ButtonProps = { export function Button({ label, style, variant = "primary", ...props }: ButtonProps) { const isSubtle = variant === "subtle"; + const isSage = variant === "sage"; return ( [ - styles.button, - isSubtle ? styles.subtle : styles.primary, - state.pressed ? styles.pressed : null, - typeof style === "function" ? style(state) : style, - ]} + style={(state) => { + // hovered exists on react-native-web only; RN core types omit it + const { hovered, pressed } = state as { pressed: boolean; hovered?: boolean }; + return [ + styles.button, + isSage ? styles.sage : isSubtle ? styles.subtle : styles.primary, + isSage && (pressed || hovered) ? styles.sageActive : null, + !isSage && pressed ? styles.pressed : null, + typeof style === "function" ? style(state) : style, + ]; + }} {...props} > - + {label} @@ -46,6 +58,16 @@ const styles = StyleSheet.create({ backgroundColor: colors.card, borderColor: colors.primaryDark, }, + sage: { + backgroundColor: authColors.authSage, + borderColor: authColors.authSage, + borderRadius: 6, + minWidth: 140, + }, + sageActive: { + backgroundColor: authColors.authSageDark, + borderColor: authColors.authSageDark, + }, pressed: { opacity: 0.82, }, @@ -60,4 +82,7 @@ const styles = StyleSheet.create({ labelSubtle: { color: colors.primaryDark, }, + labelSage: { + color: authColors.authCream, + }, }); From 8870f9fcfa1a1f72c7b723c8dff9054532880427 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 2 Jul 2026 22:59:55 +1000 Subject: [PATCH 03/17] feat(app): add AuthScreen chrome with wordmark and paw scatter --- apps/app/components/auth/AuthScreen.tsx | 59 +++++++++++++++++++++++++ apps/app/components/auth/PawScatter.tsx | 58 ++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 apps/app/components/auth/AuthScreen.tsx create mode 100644 apps/app/components/auth/PawScatter.tsx diff --git a/apps/app/components/auth/AuthScreen.tsx b/apps/app/components/auth/AuthScreen.tsx new file mode 100644 index 0000000..17e563b --- /dev/null +++ b/apps/app/components/auth/AuthScreen.tsx @@ -0,0 +1,59 @@ +import type { ReactNode } from "react"; +import { KeyboardAvoidingView, Platform, StyleSheet, Text, View } from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +import { fonts } from "@/app/theme"; +import { colors } from "@/lib/theme"; + +import { PawScatter } from "./PawScatter"; + +export function AuthScreen({ children }: { children: ReactNode }) { + return ( + + + + + + jematala + + {children} + + + + ); +} + +const styles = StyleSheet.create({ + screen: { + backgroundColor: colors.authCream, + flex: 1, + }, + keyboard: { + flex: 1, + }, + column: { + alignSelf: "center", + flex: 1, + justifyContent: "center", + maxWidth: 380, + paddingBottom: 96, + paddingHorizontal: 24, + width: "100%", + }, + wordmarkWrap: { + alignSelf: "center", + borderBottomWidth: 4, + borderColor: colors.authSage, + marginBottom: 40, + paddingBottom: 2, + }, + wordmark: { + color: colors.authSage, + fontFamily: fonts.family, + fontSize: 52, + lineHeight: 52, + }, +}); diff --git a/apps/app/components/auth/PawScatter.tsx b/apps/app/components/auth/PawScatter.tsx new file mode 100644 index 0000000..6564d30 --- /dev/null +++ b/apps/app/components/auth/PawScatter.tsx @@ -0,0 +1,58 @@ +import type { DimensionValue, ImageStyle } from "react-native"; +import { Image, Platform, StyleSheet, View } from "react-native"; + +const PAW = require("../../assets/images/paw.png"); + +type PawSpot = { + left: DimensionValue; + bottom: number; + size: number; + rotate: string; +}; + +// Fixed layout (deterministic — no per-render randomness), lower third of the +// screen, arrangement matched to the mock. +const LAYOUT: PawSpot[] = [ + { left: "6%", bottom: 28, size: 48, rotate: "-18deg" }, + { left: "28%", bottom: 8, size: 56, rotate: "8deg" }, + { left: "34%", bottom: 108, size: 44, rotate: "-6deg" }, + { left: "56%", bottom: 62, size: 40, rotate: "16deg" }, + { left: "64%", bottom: 172, size: 52, rotate: "-10deg" }, + { left: "82%", bottom: 124, size: 48, rotate: "12deg" }, + { left: "88%", bottom: 14, size: 44, rotate: "-4deg" }, +]; + +// react-native-web passes this through to the DOM; keeps the pixel art crisp +const pixelated = + Platform.OS === "web" ? ({ imageRendering: "pixelated" } as unknown as ImageStyle) : null; + +export function PawScatter() { + return ( + + {LAYOUT.map((paw, index) => ( + + ))} + + ); +} + +const styles = StyleSheet.create({ + paw: { + opacity: 0.45, + position: "absolute", + }, +}); From f44bdaa5f6fd33f4ea2aa884d17daba1dc264caa Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 2 Jul 2026 23:03:40 +1000 Subject: [PATCH 04/17] feat(app): add auth form primitives (PixelInput, FormError, clerkError) --- apps/app/components/auth/FormError.tsx | 21 ++++++++++++++ apps/app/components/auth/PixelInput.tsx | 38 +++++++++++++++++++++++++ apps/app/components/auth/clerkError.ts | 8 ++++++ 3 files changed, 67 insertions(+) create mode 100644 apps/app/components/auth/FormError.tsx create mode 100644 apps/app/components/auth/PixelInput.tsx create mode 100644 apps/app/components/auth/clerkError.ts diff --git a/apps/app/components/auth/FormError.tsx b/apps/app/components/auth/FormError.tsx new file mode 100644 index 0000000..dd73d2c --- /dev/null +++ b/apps/app/components/auth/FormError.tsx @@ -0,0 +1,21 @@ +import { StyleSheet, Text } from "react-native"; + +import { fonts } from "@/app/theme"; +import { colors } from "@/lib/theme"; + +export function FormError({ message }: { message: string | null }) { + if (!message) { + return null; + } + return {message}; +} + +const styles = StyleSheet.create({ + text: { + color: colors.authError, + fontFamily: fonts.family, + fontSize: fonts.sizes.sm, + marginTop: 8, + textAlign: "center", + }, +}); diff --git a/apps/app/components/auth/PixelInput.tsx b/apps/app/components/auth/PixelInput.tsx new file mode 100644 index 0000000..e4472fd --- /dev/null +++ b/apps/app/components/auth/PixelInput.tsx @@ -0,0 +1,38 @@ +import type { ComponentProps } from "react"; +import { StyleSheet, TextInput } from "react-native"; + +import { fonts } from "@/app/theme"; +import { colors } from "@/lib/theme"; + +type PixelInputProps = { + hasError?: boolean; +} & ComponentProps; + +export function PixelInput({ hasError = false, style, ...props }: PixelInputProps) { + return ( + + ); +} + +const styles = StyleSheet.create({ + input: { + backgroundColor: colors.authCream, + borderColor: colors.authSage, + borderRadius: 8, + borderWidth: 2, + color: colors.authSage, + fontFamily: fonts.family, + fontSize: fonts.sizes.md, + height: 48, + marginBottom: 12, + paddingHorizontal: 16, + textAlign: "center", + }, + error: { + borderColor: colors.authError, + }, +}); diff --git a/apps/app/components/auth/clerkError.ts b/apps/app/components/auth/clerkError.ts new file mode 100644 index 0000000..80b6770 --- /dev/null +++ b/apps/app/components/auth/clerkError.ts @@ -0,0 +1,8 @@ +type ClerkErrorShape = { + errors?: { longMessage?: string; message?: string }[]; +}; + +export function clerkErrorMessage(err: unknown): string { + const first = (err as ClerkErrorShape)?.errors?.[0]; + return first?.longMessage ?? first?.message ?? "something went wrong — try again"; +} From b1d7094550b9cbe84ee7a249bb3a85b4edc6b5df Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 2 Jul 2026 23:06:23 +1000 Subject: [PATCH 05/17] feat(app): render pixel-art landing screen for signed-out users --- apps/app/app/index.tsx | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/apps/app/app/index.tsx b/apps/app/app/index.tsx index 9a738a6..497955c 100644 --- a/apps/app/app/index.tsx +++ b/apps/app/app/index.tsx @@ -1,16 +1,19 @@ import { useAuth } from "@clerk/expo"; -import { Redirect } from "expo-router"; -import { StyleSheet, View } from "react-native"; +import { Redirect, useRouter } from "expo-router"; +import { ActivityIndicator, StyleSheet, View } from "react-native"; -import { ThemedText } from "@/components/ThemedText"; +import { AuthScreen } from "@/components/auth/AuthScreen"; +import { Button } from "@/components/Button"; +import { colors } from "@/lib/theme"; export default function HomeScreen() { const { isLoaded, isSignedIn } = useAuth({ treatPendingAsSignedOut: false }); + const router = useRouter(); if (!isLoaded) { return ( - - Loading... + + ); } @@ -19,13 +22,25 @@ export default function HomeScreen() { return ; } - return ; + return ( + + +