From b8596eaceed63053b6f162ebc09123cb60c1adc9 Mon Sep 17 00:00:00 2001
From: Harkamal Randhawa
Date: Sun, 24 May 2026 05:21:56 -0600
Subject: [PATCH] Improve readability and randomize guest landing pet.
Pair Share Tech Mono body text with Press Start 2P display accents, and show a randomized dressed pixel character for signed-out homepage previews.
---
app/globals.css | 17 ++-
app/layout.tsx | 11 +-
components/landing/landing-pet-preview.tsx | 3 +-
components/layout/app-nav.tsx | 2 +-
components/layout/app-page-header.tsx | 4 +-
components/layout/auth-bottom-nav.tsx | 2 +-
components/layout/habit-brand.tsx | 2 +-
components/layout/site-header.tsx | 2 +-
components/ui/badge.tsx | 2 +-
components/ui/button.tsx | 2 +-
components/ui/card.tsx | 4 +-
components/ui/input.tsx | 2 +-
components/ui/label.tsx | 2 +-
lib/landing-random-pet.ts | 117 +++++++++++++++++++++
tailwind.config.ts | 2 +-
15 files changed, 153 insertions(+), 21 deletions(-)
create mode 100644 lib/landing-random-pet.ts
diff --git a/app/globals.css b/app/globals.css
index 7e082c4..4e9a4d4 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -19,7 +19,7 @@
--secondary: 198 100% 45%;
--secondary-foreground: 252 38% 10%;
--muted: 252 24% 20%;
- --muted-foreground: 52 40% 68%;
+ --muted-foreground: 52 35% 74%;
--accent: 142 70% 45%;
--accent-foreground: 252 38% 10%;
--destructive: 0 84% 58%;
@@ -51,7 +51,7 @@
--secondary: 198 100% 45%;
--secondary-foreground: 252 38% 10%;
--muted: 252 24% 20%;
- --muted-foreground: 52 40% 68%;
+ --muted-foreground: 52 35% 74%;
--accent: 142 70% 45%;
--accent-foreground: 252 38% 10%;
--destructive: 0 84% 58%;
@@ -73,9 +73,16 @@
}
body {
- @apply overflow-x-hidden bg-background text-foreground text-xs leading-relaxed;
- font-family: var(--font-pixel), monospace;
- image-rendering: pixelated;
+ @apply overflow-x-hidden bg-background text-foreground text-sm leading-relaxed;
+ }
+
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ @apply font-pixel leading-snug;
}
}
diff --git a/app/layout.tsx b/app/layout.tsx
index bee97d9..99e3326 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,5 +1,5 @@
import type { Metadata, Viewport } from "next";
-import { Press_Start_2P } from "next/font/google";
+import { Press_Start_2P, Share_Tech_Mono } from "next/font/google";
import { ThemeProvider } from "next-themes";
import { GlobalBottomNav } from "@/components/layout/global-bottom-nav";
@@ -30,6 +30,13 @@ const pixelFont = Press_Start_2P({
display: "swap",
});
+const bodyFont = Share_Tech_Mono({
+ weight: "400",
+ subsets: ["latin"],
+ variable: "--font-body",
+ display: "swap",
+});
+
export default function RootLayout({
children,
}: Readonly<{
@@ -37,7 +44,7 @@ export default function RootLayout({
}>) {
return (
-
+
(function AppNav(
onPointerEnter={() => prefetchByRoute[item.href]?.()}
onFocus={() => prefetchByRoute[item.href]?.()}
className={cn(
- "bottom-nav-item flex flex-col items-center justify-center gap-1.5 overflow-hidden border-2 px-1 py-2 text-[9px] leading-none transition-colors duration-75",
+ "bottom-nav-item flex flex-col items-center justify-center gap-1.5 overflow-hidden border-2 px-1 py-2 font-pixel text-[8px] leading-none transition-colors duration-75",
isActive
? "border-secondary bg-primary text-primary-foreground shadow-[var(--retro-shadow-sm)]"
: "border-transparent text-muted-foreground hover:border-border hover:bg-muted hover:text-foreground",
diff --git a/components/layout/app-page-header.tsx b/components/layout/app-page-header.tsx
index 2dac165..37ed7f1 100644
--- a/components/layout/app-page-header.tsx
+++ b/components/layout/app-page-header.tsx
@@ -16,9 +16,9 @@ export function AppPageHeader({
{eyebrow}
) : null}
- {title}
+ {title}
{description ? (
-
+
{description}
) : null}
diff --git a/components/layout/auth-bottom-nav.tsx b/components/layout/auth-bottom-nav.tsx
index 637f7ed..1969ef8 100644
--- a/components/layout/auth-bottom-nav.tsx
+++ b/components/layout/auth-bottom-nav.tsx
@@ -28,7 +28,7 @@ export function AuthBottomNav() {
key={item.href}
href={item.href}
className={cn(
- "flex flex-col items-center gap-1 rounded-lg px-1 py-2 text-[9px] font-medium leading-tight transition-colors",
+ "flex flex-col items-center gap-1 rounded-lg px-1 py-2 font-pixel text-[8px] font-normal leading-tight transition-colors",
isActive
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:bg-muted hover:text-foreground",
diff --git a/components/layout/habit-brand.tsx b/components/layout/habit-brand.tsx
index 8d6c942..6288bbf 100644
--- a/components/layout/habit-brand.tsx
+++ b/components/layout/habit-brand.tsx
@@ -41,7 +41,7 @@ export function HabitBrand({
/>
) : (
HaBit
diff --git a/components/layout/site-header.tsx b/components/layout/site-header.tsx
index f39d961..e31e9fa 100644
--- a/components/layout/site-header.tsx
+++ b/components/layout/site-header.tsx
@@ -11,7 +11,7 @@ export function SiteHeader() {
return (
-
+
Habit Pet
{!hasEnvVars ? (
diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx
index 5542588..dbcc4bc 100644
--- a/components/ui/badge.tsx
+++ b/components/ui/badge.tsx
@@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
- "inline-flex items-center rounded-none border-2 border-border px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 shadow-[var(--retro-shadow-sm)]",
+ "inline-flex items-center rounded-none border-2 border-border px-2 py-0.5 text-[10px] font-normal uppercase tracking-wide transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 shadow-[var(--retro-shadow-sm)]",
{
variants: {
variant: {
diff --git a/components/ui/button.tsx b/components/ui/button.tsx
index 8504581..c737bff 100644
--- a/components/ui/button.tsx
+++ b/components/ui/button.tsx
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
- "inline-flex items-center justify-center gap-2 whitespace-nowrap border-2 border-border text-xs font-normal transition-[transform,box-shadow] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap border-2 border-border font-pixel text-[10px] font-normal uppercase tracking-wide transition-[transform,box-shadow] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
diff --git a/components/ui/card.tsx b/components/ui/card.tsx
index b09c29c..5441485 100644
--- a/components/ui/card.tsx
+++ b/components/ui/card.tsx
@@ -35,7 +35,7 @@ const CardTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
));
@@ -47,7 +47,7 @@ const CardDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
));
diff --git a/components/ui/input.tsx b/components/ui/input.tsx
index 4543bbc..bc9849c 100644
--- a/components/ui/input.tsx
+++ b/components/ui/input.tsx
@@ -8,7 +8,7 @@ const Input = React.forwardRef
>(
(items: readonly T[]): T {
+ return items[Math.floor(Math.random() * items.length)]!;
+}
+
+function presetHsl(id: string) {
+ const preset = COLOR_PRESETS.find((entry) => entry.id === id);
+ if (!preset) {
+ throw new Error(`Missing color preset: ${id}`);
+ }
+
+ return { ...preset.hsl };
+}
+
+function pickRandomVariant(
+ layerId: Exclude,
+ allowNone = false,
+): string {
+ const layer = CHARACTER_LAYERS.find((entry) => entry.id === layerId);
+ if (!layer) {
+ throw new Error(`Missing character layer: ${layerId}`);
+ }
+
+ const options = layer.variants
+ .map((variant) => variant.id)
+ .filter((id) => allowNone || id !== NONE_VARIANT_ID);
+
+ return pickRandom(options);
+}
+
+export function buildRandomLandingPetCustomization(): AvatarCustomization {
+ const freeRooms = ROOM_BACKGROUNDS.filter((room) => room.free).map(
+ (room) => room.id,
+ );
+
+ return {
+ name: DEFAULT_AVATAR_NAME,
+ colors: {
+ skin: presetHsl(pickRandom(SKIN_TONE_IDS)),
+ pants: presetHsl(pickRandom(OUTFIT_COLOR_IDS)),
+ shoes: presetHsl(pickRandom(OUTFIT_COLOR_IDS)),
+ torso: presetHsl(pickRandom(OUTFIT_COLOR_IDS)),
+ eyes: presetHsl(pickRandom(EYE_COLOR_IDS)),
+ head: presetHsl(pickRandom(HAIR_COLOR_IDS)),
+ },
+ variants: {
+ skin: "skin-1",
+ pants: pickRandomVariant("pants"),
+ shoes: pickRandomVariant("shoes"),
+ torso: pickRandomVariant("torso"),
+ eyes: pickRandomVariant("eyes"),
+ head: pickRandomVariant("head", Math.random() < 0.15),
+ },
+ customized: true,
+ equippedItems: [],
+ roomBackground: pickRandom(freeRooms),
+ };
+}
+
+export function getGuestLandingPetCustomization(): AvatarCustomization {
+ if (typeof window === "undefined") {
+ return buildRandomLandingPetCustomization();
+ }
+
+ try {
+ const cached = sessionStorage.getItem(GUEST_PET_STORAGE_KEY);
+ if (cached) {
+ return JSON.parse(cached) as AvatarCustomization;
+ }
+
+ const next = buildRandomLandingPetCustomization();
+ sessionStorage.setItem(GUEST_PET_STORAGE_KEY, JSON.stringify(next));
+ return next;
+ } catch {
+ return buildRandomLandingPetCustomization();
+ }
+}
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 63e961d..e36d19f 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -60,7 +60,7 @@ export default {
sm: "calc(var(--radius) - 4px)",
},
fontFamily: {
- sans: ["var(--font-pixel)", "monospace"],
+ sans: ["var(--font-body)", "monospace"],
pixel: ["var(--font-pixel)", "monospace"],
},
spacing: {