diff --git a/app/_components/AuthBrandPanel.tsx b/app/_components/AuthBrandPanel.tsx
new file mode 100644
index 0000000..05f5167
--- /dev/null
+++ b/app/_components/AuthBrandPanel.tsx
@@ -0,0 +1,186 @@
+import { Fragment, type ReactNode } from "react";
+import type { Stage } from "@/lib/pets/growth";
+import { PetArt } from "@/app/dashboard/_components/PetArt";
+import { fadeUp } from "@/lib/ui/motion";
+import { Logo } from "./Logo";
+
+const STAGES: { stage: Stage; label: string }[] = [
+ { stage: "egg", label: "Egg" },
+ { stage: "hatchling", label: "Hatchling" },
+ { stage: "juvenile", label: "Juvenile" },
+ { stage: "adult", label: "Adult" },
+];
+
+// The only two sizings StageStrip is ever asked for — desktop brand panel
+// vs. the compact mobile hero. A closed set of variants instead of five
+// independent style props means there's no way to call it with a mismatched
+// combination (e.g. mobile-sized art with desktop-sized chevrons).
+const STAGE_STRIP_VARIANTS = {
+ full: {
+ artClassName: "h-20 w-auto",
+ tileClassName: "px-3 py-5",
+ chevronSize: 16,
+ gapClassName: "gap-2.5",
+ labels: true,
+ },
+ compact: {
+ artClassName: "h-9 w-auto",
+ tileClassName: "px-1.5 py-2.5",
+ chevronSize: 12,
+ gapClassName: "gap-1.5",
+ labels: false,
+ },
+} as const;
+
+// Shared growth-stage row, sized differently for the desktop brand panel vs.
+// the compact mobile hero below. Labels are dropped on mobile — four
+// two-word labels don't fit at that width without wrapping into the chevrons.
+function StageStrip({
+ variant,
+}: {
+ variant: keyof typeof STAGE_STRIP_VARIANTS;
+}) {
+ const { artClassName, tileClassName, chevronSize, gapClassName, labels } =
+ STAGE_STRIP_VARIANTS[variant];
+
+ return (
+
+ {STAGES.map(({ stage, label }, i) => (
+
+
+
+ {labels && (
+
+ {label}
+
+ )}
+
+ {i < STAGES.length - 1 && (
+
+ )}
+
+ ))}
+
+ );
+}
+
+// Shared left panel for sign-in/sign-up — hidden below lg, where
+// MobileAuthHero (below) takes over. Doubles as the app's de facto landing
+// page for signed-out visitors, since "/" redirects straight here instead of
+// showing separate marketing content.
+export function AuthBrandPanel() {
+ const headline = fadeUp(80);
+
+ return (
+
+
+
+
+
+
+
+ Commit Pet
+
+
+
+
+
+ A pet that grows as you commit.
+
+
+ Install it on a GitHub repo, watch it grow from egg to adult, and
+ retire once you ship.
+
+
+
+
+
+
+
+
+ );
+}
+
+// Compact hero for the auth pages below `lg`, shown above the Clerk widget
+// instead of just a bare logo. Same brand moment as AuthBrandPanel (headline,
+// subtext, growth-stage strip) at mobile scale.
+export function MobileAuthHero() {
+ const headline = fadeUp(80);
+ const stageStrip = fadeUp(160);
+
+ return (
+
+
+
+
+
+
+ Commit Pet
+
+
+
+
+
+ A pet that grows as you commit.
+
+
+ Install it on a GitHub repo, watch it grow from egg to adult, and
+ retire once you ship.
+
+
+
+
+
+
+
+ );
+}
+
+// Shared page shell for /sign-in and /sign-up — desktop split panel plus
+// mobile hero, with the Clerk widget (SignIn or SignUp) as children. Was
+// duplicated verbatim in both page files; this is the one copy.
+export function AuthShell({ children }: { children: ReactNode }) {
+ const widget = fadeUp(200);
+
+ return (
+
+
+
+
+
+
+ {children}
+
+
+
+
+ );
+}
diff --git a/app/dashboard/[repoId]/page.tsx b/app/dashboard/[repoId]/page.tsx
index 96e038d..78fee09 100644
--- a/app/dashboard/[repoId]/page.tsx
+++ b/app/dashboard/[repoId]/page.tsx
@@ -3,14 +3,10 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { getAccessibleInstallationIds } from "@/lib/github/user-auth";
import { getDashboardPet } from "@/lib/pets/dashboard-data";
+import { repoShortName } from "@/lib/pets/repo-name";
import { getMcpTokenStatus } from "@/lib/mcp/tokens";
-import { Hero } from "../_components/Hero";
-import { GrowthCard } from "../_components/GrowthCard";
-import { OpenIssuesCard } from "../_components/OpenIssuesCard";
-import { HealthCard } from "../_components/HealthCard";
-import { BadgeCard } from "../_components/BadgeCard";
-import { RepoInfoCard } from "../_components/RepoInfoCard";
-import { McpTokenCard } from "../_components/McpTokenCard";
+import { fadeUp } from "@/lib/ui/motion";
+import { PetDetailSection } from "../_components/PetDetailSection";
export async function generateMetadata({
params,
@@ -47,7 +43,9 @@ export default async function PetDetailPage({
return (