# Project export: Furly

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: AI-powered rescue coordination that helps every animal find a safe way home.
- Devpost: https://devpost.com/software/furly-0v3zc6
- GitHub: https://github.com/campixl/furly
- Demo: https://furly-rescue-home.campixl.chatgpt.site/
- Video: https://www.youtube.com/embed/Kv3MDVZoDCY?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Camelia (6 commits)

## Devpost submission (written by the team)

### Inspiration

In Bali, animal rescue is often coordinated through scattered social media posts, group chats, and private messages. Rescuers may receive incomplete information, potential adopters can be difficult to verify, and urgent cases can disappear beneath newer posts. I wanted to explore a safer and more organized alternative. Furly is not a marketplace, and animals are never treated as products. It is a rescue coordination network designed to help rescued animals move from first report to verified care, foster support, and a responsible home. Every rescue deserves a safe way home. What Furly does Furly connects rescued animals with rescuers, adopters, foster homes, volunteers, and veterinary partners. The prototype includes: Discovery for dogs, cats, and other companion animals Animal profiles with photos, rescue stories, health information, compatibility details, and current needs Approximate public locations without exposing private home addresses Clear rescuer and veterinary verification information Structured adoption applications Rescue timelines and follow-up information Requests for foster care, medical help, transport, supplies, photography, and home checks Success stories showing an animal's journey from rescue to home How AI helps Furly uses AI as a rescue coordination assistant. A rescuer can enter rough or incomplete notes from the field. The assistant organizes them into: Confirmed information Unconfirmed information Missing details Follow-up questions A clearer public description Suggested next actions The rescuer must review and edit the result before publishing it. AI can also summarize adoption applications, identify missing information, highlight positive compatibility signals, and suggest interview questions. It does not generate a numeric adopter score, approve applications automatically, or replace human judgment. How I built it I built Furly with Codex and GPT-5.6, using Next.js, TypeScript, and Tailwind CSS. Codex helped me turn the initial social-impact idea into a structured product, plan the user journeys, create reusable components, implement the responsive interface, connect the prototype's interactions, and troubleshoot development and deployment problems. GPT-5.6 was used to develop and demonstrate the intelligence behind Furly's rescue-note assistant and application-review workflow. It helps transform unstructured information into useful, reviewable actions while keeping humans responsible for every important decision. The current version uses realistic local mock data so judges can explore the complete experience without creating accounts or supplying credentials. The data layer is structured so a future version can connect authentication, media, and persistent records through Supabase. Challenges I faced The biggest challenge was balancing automation with safety. Animal rescue contains incomplete information and emotional, time-sensitive decisions. I did not want AI to create false certainty or silently make adoption decisions. I designed the assistant to clearly separate known facts from assumptions and to ask for clarification when information is missing. Another challenge was creating a useful prototype within a short build period. I focused on demonstrating one connected rescue journey instead of building production infrastructure prematurely. I also had to resolve development and preview issues while making sure the important controls produced meaningful outcomes. What I learned I learned that responsible AI is not only about generating better answers. It is also about showing uncertainty, requesting missing context, preserving accountability, and knowing when a human must decide. I also learned how Codex can support an entire product workflow—from architecture and interface development to testing, debugging, and iteration—rather than being used only to generate isolated pieces of code.

### What's next

Furly would begin with a pilot in Bali involving a small group of rescuers and veterinary partners. The next steps would include: Secure authentication and role-based access Persistent rescue and adoption records Media uploads and moderation Stronger identity and veterinary verification Private messaging with anti-scam protections Follow-up reminders after adoption Transparent veterinary discounts and legitimate care costs Multilingual and multi-country support Feedback from real rescuers before expanding the AI workflows Furly's long-term goal is to become trusted coordination infrastructure for animal rescue—helping more people act safely, helping rescuers spend less time organizing fragmented information, and helping more animals find the right home. From rescue to home.

## README (from the GitHub repository)

# Furly

> From rescue to home.

Furly is a mobile-first rescue coordination prototype for OpenAI Build Week. It helps communities move companion animals from an incomplete report to verified care, foster support, responsible adoption and post-adoption follow-up.

Furly is not an animal marketplace. Animals are never treated as products, and consequential rescue or adoption decisions remain with people.

## What Furly does

- Turns photos, descriptions and reporter answers into a structured rescue case
- Separates confirmed facts, unconfirmed observations and missing information
- Proposes editable rescue tasks that humans must accept
- Tracks transport, veterinary, foster and adoption progress
- Publishes privacy-safe animal journey profiles from verified information
- Helps people contribute through fostering, transport, supplies, photography, translation and other support
- Organizes adoption applications for human review without numeric scores or automatic decisions
- Supports English, Indonesian and Chinese UI choices

The primary demo follows Mango, a young dog reported near Sunset Road in Bali, from incomplete report through coordinated care and responsible placement.

## Safety and privacy

Furly does not provide veterinary diagnoses, prescribe treatment, automatically dispatch responders, expose exact public addresses, approve adopters or invent partner availability. AI output is a draft that requires human review.

Private locations, phone numbers and WhatsApp details must remain server-side in a production deployment. The included demo uses local mock data and browser storage only. Do not use it for real sensitive rescue information without adding production authentication, authorization and durable storage.

## Technology

- Next.js App Router and TypeScript
- React 19
- Tailwind CSS
- Vinext, Vite and Cloudflare Workers for ChatGPT Sites
- Repository interfaces for future relational and object-storage adapters
- Mock and real AI service abstractions
- Node's built-in test runner

## Local setup

Requirements:

- Node.js 22.13 or newer
- pnpm

Install and run:

```bash
git clone https://github.com/campixl/furly.git
cd furly
pnpm install
pnpm dev
```

Open `http://localhost:3000`.

Quality checks:

```bash
pnpm lint
pnpm typecheck
pnpm test
pnpm build
```

No environment variables are required for the demo. When `OPENAI_API_KEY` is unavailable, Furly uses `MockAIService` so the complete prototype remains usable. The Sites deployment provisions D1 and R2 bindings for animal-profile drafts and uploaded photos.

For optional server-side integrations, configure secrets in the hosting platform rather than in browser code or committed files. Supported server variables include `OPENAI_API_KEY` and the WhatsApp Business variables documented in the notification section below.

## Mock data

The main demo records live in [`src/lib/data.ts`](src/lib/data.ts):

- Mango: an urgent rescue intake and coordination case
- Nori: a cat recovering in foster care with a vet-confirmed update
- Bumi: a successfully adopted dog with follow-up history
- Kiki: a foster-supported companion rabbit
- Help requests, tasks and sample adoption applications

To customize the demo:

1. Edit the exported `animals`, `cases`, `helpRequests` and `applications` collections in `src/lib/data.ts`.
2. Keep IDs stable across related records, such as `animalId` and `caseId`.
3. Use only approximate public locations and fictional contact details.
4. Preserve source attribution such as community-reported, rescuer-confirmed or veterinarian-confirmed.
5. Run `pnpm typecheck`, `pnpm test` and `pnpm build` after changes.

Repository contracts are defined in [`src/lib/repositories.ts`](src/lib/repositories.ts). They are designed so the local arrays can later be replaced by Sites-managed D1 or another relational store without changing the product flows.

## AI architecture

The `AIService` abstraction supports:

- Rescue intake clarification
- Case-plan proposals
- Public-profile drafting
- Adoption-application summaries

`MockAIService` is the default fallback. `OpenAIAIService` is selected only when a server-side API key is available. No secret is exposed to client components. AI may organize and recommend, but it does not make medical, dispatch, moderation or adoption decisions.

## WhatsApp notifications

Foster offers and adoption applications collect a private WhatsApp number with explicit contact consent. The demo calls a server-only notification endpoint. Without provider credentials, it returns a safe queued mock result.

A production integration can use:

- `WHATSAPP_ACCESS_TOKEN`
- `WHATSAPP_PHONE_NUMBER_ID`
- Approved WhatsApp template names
- `FURLY_WHATSAPP_RECIPIENTS_JSON`

Production delivery also requires recipient opt-in, approved templates, delivery logging, retries and server-side application records. Never commit these values or real phone numbers.

## Storage and Sites deployment

The current prototype keeps its curated demo catalog in mock repositories. New animal-profile drafts use Sites-managed D1 for structured records and R2 for uploaded photos. A public production launch should also add profile ownership and role-based authorization before allowing broader access.

The storage model uses:

- Sites-managed D1 for animal-profile drafts now, with cases, tasks, applications and verification records planned
- R2 object storage for uploaded profile photos now, with video, voice notes and documents planned
- Sign in with ChatGPT for authenticated, server-authorized writes
- Sites runtime environment settings for secrets

See [`docs/SITES_STORAGE.md`](docs/SITES_STORAGE.md) for the proposed data boundary and privacy model.

## How Codex and GPT-5.6 were used

Codex, powered by GPT-5.6, accelerated implementation of the design system, responsive routes, typed domain model, mock repositories, case state machine, form validation, safety guardrails, tests, documentation and ChatGPT Sites packaging. It was also used to inspect failures, verify critical flows and prepare the deployable build.

The human team defined the product vision, target users, brand direction, safety boundaries, language choices and final adoption workflow. Humans remain responsible for moderation, medical care, responder coordination and every adoption decision.

## Sustainable model

Core reporting, animal profiles, volunteer participation and adoption applications remain free. Possible future sustainability paths include completed-care veterinary referrals, newly adopted animal care plans, optional clinic workflows, ethical sponsored adoption kits, pet-care referrals and CSR-funded sterilization campaigns.

Payments and paid placement are not part of this MVP.

## Project documentation

- [`docs/INTERACTIONS.md`](docs/INTERACTIONS.md): interaction and route audit
- [`docs/SITES_STORAGE.md`](docs/SITES_STORAGE.md): production storage and privacy plan

## License

Furly is available under the [MIT License](LICENSE).


## Detected evidence (automated analysis)

Indexed codebase: 67 recognized source files, 395 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- JavaScript (language) — claimed on Devpost, not found in the code
- OpenAI (technology) — claimed on Devpost, not found in the code
- Supabase (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (75 of 75)

```
.gitignore
.openai/hosting.json
docs/INTERACTIONS.md
docs/SITES_STORAGE.md
drizzle/0001_animals.sql
eslint.config.mjs
LICENSE
next.config.ts
package.json
pnpm-workspace.yaml
postcss.config.mjs
README.md
src/app/account.css
src/app/animals/[id]/apply/page.tsx
src/app/animals/[id]/page.tsx
src/app/animals/layout.tsx
src/app/animals/page.tsx
src/app/api/ai/coordinator/route.ts
src/app/api/notifications/whatsapp/route.ts
src/app/applications/[id]/page.tsx
src/app/case.css
src/app/cases/[id]/page.tsx
src/app/cozy.css
src/app/design.css
src/app/directory.css
src/app/donate/page.tsx
src/app/expanded.css
src/app/forms.css
src/app/globals.css
src/app/help/layout.tsx
src/app/help/page.tsx
src/app/home.css
src/app/homey-final.css
src/app/layout.tsx
src/app/marketplace.css
src/app/page.tsx
src/app/profile-design.css
src/app/profile/[id]/page.tsx
src/app/readability.css
src/app/rehoming-refresh.css
src/app/rehoming/layout.tsx
src/app/rehoming/page.tsx
src/app/report/page.tsx
src/app/robots.ts
src/app/signup/page.tsx
src/app/sitemap.ts
src/app/support/layout.tsx
src/app/support/page.tsx
src/app/ui-refresh.css
src/app/updates/page.tsx
src/components/AICoordinator.tsx
src/components/AnimalActions.tsx
src/components/AnimalCard.tsx
src/components/AnimalProfilePage.tsx
src/components/AppProvider.tsx
src/components/LocalizedContent.tsx
src/components/LocalizedText.tsx
src/components/ProfileActions.tsx
src/components/SiteFooter.tsx
src/components/SiteHeader.tsx
src/components/ui.tsx
src/lib/ai.ts
src/lib/case-machine.ts
src/lib/data.ts
src/lib/filter-animals.ts
src/lib/i18n.ts
src/lib/page-translations.ts
src/lib/repositories.ts
src/lib/types.ts
src/lib/whatsapp.ts
start-preview.cmd
tests/interactions.test.mjs
tsconfig.json
vite.config.ts
worker/index.ts
```

### Dependencies

- package.json: @cloudflare/vite-plugin@1.37.1, @tailwindcss/postcss@^4, @types/node@^20, @types/react@^19, @types/react-dom@^19, @vitejs/plugin-react@6.0.2, @vitejs/plugin-rsc@0.5.26, eslint@^9, eslint-config-next@16.2.10, next@16.2.10, react@19.2.7, react-dom@19.2.7, react-server-dom-webpack@19.2.7, tailwindcss@^4, typescript@^5, vinext@0.0.50, vite@8.0.13, wrangler@4.92.0

### Recent commits (newest first)

- Keep profile actions clear of AI launcher
- Make sign-in redirect reliable on Sites
- Smooth sign-in before adding animals
- Make animal profile uploads functional
- Document Furly for public release
- Prepare Furly for Sites deployment

## Key source files (fetched from GitHub, selected and truncated for size)

### docs/SITES_STORAGE.md

```markdown
# Furly storage for ChatGPT Sites

Furly uses two durable storage services for animal-profile creation. The first MVP schema stores draft animal records in D1 and their uploaded profile photos in R2. The remaining entities below describe the next production storage expansion.

## D1 relational database

Use the Sites-managed D1 binding named `DB` for structured, searchable records:

- `users` and `profiles`
- `animals` and source attribution
- `rescue_cases` and private/public location fields
- `tasks`, assignments and completion history
- `applications` and human review decisions
- `updates`, follows and notifications
- `help_requests`
- `verifications`
- moderation reports
- optional support-campaign pledges (not payment-card data)

D1 should be the authoritative source for records, ownership and workflow state. Every write must check the signed-in user on the server.

## R2 object storage

Use the Sites-managed R2 binding named `MEDIA` for photos, video, voice notes and documents. Store only the object key and searchable metadata in D1. Recommended key prefixes:

- `public/animals/{animalId}/...`
- `private/cases/{caseId}/...`
- `private/verifications/{profileId}/...`

Private objects must never be exposed through predictable public URLs. Serve them only after a server-side ownership or role check.

## Authentication

For a Sites MVP launched through ChatGPT, use dispatch-owned Sign in with ChatGPT (SIWC). Attribute writes to the authenticated user on the server. Do not trust a user id supplied by browser JavaScript.

The current local demo uses `localStorage` only to demonstrate sign-in presentation and follows. Replace that with SIWC plus D1 before accepting real user data. Browser storage may remain for temporary drafts and filter preferences only.

## Runtime secrets

Store `OPENAI_API_KEY` and any future service credentials in Sites runtime environment settings. Never place them in browser code or commit them. Set `NEXT_PUBLIC_SITE_URL` to the final Sites URL for canonical links and the sitemap.

## Hosting metadata

The current `.openai/hosting.json` declares `DB` and `MEDIA` as logical bindings. Sites owns the real resources and deployment wiring.

```

### docs/INTERACTIONS.md

```markdown
# Furly interaction inventory

| Element | Route or action | Expected result | Status |
|---|---|---|---|
| Furly logo | `/` | Return to discovery | Implemented |
| Animals / Help / Updates navigation | Matching routes | Open each primary area | Implemented |
| Sign in / Create account | `/signup` | Validate details, save a local demo session, open profile | Implemented |
| Account menu | Profile / sign out | Open the profile or clear the local session | Implemented |
| Report an animal | `/report` | Open four-step rescue intake | Implemented |
| Find a home | `/rehoming` | Choose rescuer, foster or guardian path and create a reviewed placement profile | Implemented |
| Find a pet | `/animals` | Browse adoption and foster profiles from all three sources | Implemented |
| Dog / Cat / Other categories | Controlled filter state | Show matching pets and update the result count | Implemented |
| Source / placement / location dropdowns | Controlled filter state | Combine filters using explicit values | Implemented |
| Support a campaign | `/support` | Select a transparent outcome and record a no-payment demo pledge | Implemented |
| Report upload | Local file picker | Show selected filename in intake | Implemented |
| Report Continue / Back / Cancel | Intake state or browser history | Validate, move between steps, or leave safely | Implemented |
| Create rescue case | Intake state | Require confirmation and show case link | Implemented |
| I want to help | `/help` | Open case-linked opportunities | Implemented |
| Homepage animal categories | Client-side filter | Update visible animal cards | Implemented |
| Animal card | `/animals/[id]` | Open permanent rescue journey | Implemented |
| Animal directory filters | Client-side filter | Filter species, status, location and verification | Implemented |
| Clear filters | Client-side reset | Restore all animal/help results | Implemented |
| Follow | Persistent local state | Toggle follow and show confirmation toast | Implemented |
| Share | Web Share / clipboard | Open native share sheet or copy journey URL | Implemented |
| Apply to adopt | `/animals/[id]/apply` | Open validated adoption application | Implemented |
| Offer foster care | Foster modal | Validate and confirm interest submission | Implemented |
| Other ways to help | `/help` | Open volunteer opportunities | Implemented |
| Report concern | Safety modal | Validate and send for human moderation | Implemented |
| Case task action | Local case state | Accept task, then require human completion confirmation | Implemented |
| Public profile action | `/animals/mango` | Open Mango’s public rescue journey | Implemented |
| Adoption submit | Local form state | Explain missing fields or show review confirmation | Implemented |
| Application review actions | Local review state | Request information, shortlist or record human approval | Implemented |
| Profile role switcher | Persistent local session | Change the active demo role | Implemented |
| Mobile bo
[truncated — 329 more characters]
```

### package.json

```
{
  "name": "furly",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "engines": {"node": ">=22.13.0"},
  "scripts": {
    "dev": "vinext dev",
    "build": "vinext build",
    "start": "vinext start",
    "lint": "eslint",
    "typecheck": "tsc --noEmit",
    "test": "node --test tests/*.test.mjs"
  },
  "dependencies": {
    "next": "16.2.10",
    "react": "19.2.7",
    "react-dom": "19.2.7"
  },
  "devDependencies": {
    "@cloudflare/vite-plugin": "1.37.1",
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "@vitejs/plugin-react": "6.0.2",
    "@vitejs/plugin-rsc": "0.5.26",
    "eslint": "^9",
    "eslint-config-next": "16.2.10",
    "tailwindcss": "^4",
    "typescript": "^5",
    "vinext": "0.0.50",
    "vite": "8.0.13",
    "wrangler": "4.92.0",
    "react-server-dom-webpack": "19.2.7"
  }
}

```

### worker/index.ts

```typescript
import { handleImageOptimization, DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES } from "vinext/server/image-optimization";
import handler from "vinext/server/app-router-entry";

interface AssetFetcher { fetch(request: Request): Promise<Response> }
interface D1Result<T = unknown> { results?: T[]; success: boolean }
interface D1Statement {
  bind(...values: unknown[]): D1Statement;
  run<T = unknown>(): Promise<D1Result<T>>;
  first<T = Record<string, unknown>>(): Promise<T | null>;
}
interface D1Database { prepare(sql: string): D1Statement }
interface R2ObjectBody { body: ReadableStream; httpMetadata?: { contentType?: string }; writeHttpMetadata(headers: Headers): void }
interface R2Bucket {
  put(key: string, value: ReadableStream | ArrayBuffer, options?: { httpMetadata?: { contentType?: string } }): Promise<unknown>;
  get(key: string): Promise<R2ObjectBody | null>;
}
interface Env {
  ASSETS: AssetFetcher;
  IMAGES: { input(stream: ReadableStream): { transform(options: Record<string, unknown>): { output(options: { format: string; quality: number }): Promise<{ response(): Response }> } } };
  DB: D1Database;
  MEDIA: R2Bucket;
}
interface ExecutionContext { waitUntil(promise: Promise<unknown>): void; passThroughOnException(): void }

const animalSchema = `CREATE TABLE IF NOT EXISTS animals (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL,
  species TEXT NOT NULL,
  age TEXT NOT NULL,
  location TEXT NOT NULL,
  placement TEXT NOT NULL,
  origin TEXT NOT NULL,
  story TEXT NOT NULL,
  care_timeline TEXT NOT NULL,
  image_key TEXT NOT NULL,
  image_type TEXT NOT NULL,
  status TEXT NOT NULL,
  created_at TEXT NOT NULL
)`;

const json = (data: unknown, status = 200) => Response.json(data, { status, headers: { "cache-control": "no-store" } });
const clean = (value: FormDataEntryValue | null) => typeof value === "string" ? value.trim() : "";

async function ensureAnimalSchema(env: Env) {
  await env.DB.prepare(animalSchema).run();
}

function publicAnimal(row: Record<string, unknown>) {
  const id = String(row.id);
  return {
    id,
    name: String(row.name),
    species: String(row.species),
    age: String(row.age),
    location: String(row.location),
    status: String(row.status),
    verification: "Awaiting human review",
    need: String(row.placement),
    personality: "Personality details are still being gathered.",
    image: `/api/animal-media/${encodeURIComponent(id)}`,
    story: String(row.story),
    care: `Current care availability: ${String(row.care_timeline)}.`,
    compatibility: "Compatibility information has not yet been assessed.",
    medical: "No verified medical information has been added.",
    source: "Community report",
    origin: String(row.origin),
    placement: String(row.placement),
    timeline: [{ date: "Submitted today", title: "Profile submitted for review", detail: "The animal profile and photo were saved. A human must review it before public listing.", source: "Community report" }]
  };
}

async function createAnimal(request: Request, env: Env) {
  const form = await request.formData();
  const photo = form.get("photo");
  const name = clean(form.get("name"));
  const species = clean(form.get("species"));
  const age = clean(form.get("age"));
  const location = clean(form.get("location"));
  const placement = clean(form.get("placement"));
  const origin = clean(form.get("origin"));
  const story = clean(form.get("story"));
  const careTimeline = clean(form.get("timeline"));
  if (!name || !species || !age || !location || !placement || !origin || !story || !careTimeline || !(photo instanceof File)) {
    return json({ error: "Complete every required field and choose a photo." }, 400);
  }
  if (!photo.type.startsWith("image/")) return json({ error: "Please choose a JPG, PNG or WebP image." }, 400);
  if (photo.size > 8 * 1024 * 1024) return json({ error: "The photo must be smaller than 8 MB." }, 400);
  if (!["Dog", "Cat", "Other"].includes(species)) return json({ error: "Choose a supported animal type." }, 400);
  if (!["Adoption", "Foster", "Adoption or foster"].includes(placement)) return json({ error: "Choose a placement type." }, 400);

  await ensureAnimalSchema(env);
  const id = `community-${crypto.randomUUID()}`;
  const extension = photo.name.split(".").pop()?.replace(/[^a-z0-9]/gi, "").toLowerCase() || "jpg";
  const imageKey = `private/animal-drafts/${id}/profile.${extension}`;
  await env.MEDIA.put(imageKey, photo.stream(), { httpMetadata: { contentType: photo.type } });
  const createdAt = new Date().toISOString();
  await env.DB.prepare(`INSERT INTO animals (id, name, species, age, location, placement, origin, story, care_timeline, image_key, image_type, status, created_at)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
    .bind(id, name, species, age, location, placement, origin, story, careTimeline, imageKey, photo.type, "Draft awaiting human review", createdAt)
    .run();
  return json({ animal: publicAnimal({ id, name, species, age, location, placement, origin, story, care_timeline: careTimeline, status: "Draft awaiting human review" }), draft: true }, 201);
}

async function getAnimal(id: string, env: Env) {
  await ensureAnimalSchema(env);
  const row = await env.DB.prepare("SELECT * FROM animals WHERE id = ?").bind(id).first<Record<string, unknown>>();
  return row ? json({ animal: publicAnimal(row), draft: true }) : json({ error: "Animal profile not found." }, 404);
}

async function listAnimals(env: Env) {
  await ensureAnimalSchema(env);
  const result = await env.DB.prepare("SELECT * FROM animals ORDER BY created_at DESC LIMIT 24").run<Record<string, unknown>>();
  return json({ animals: (result.results || []).map(publicAnimal), draft: true });
}

async function getAnimalMedia(id: string, env: Env) {
  await ensureAnimalSchema(env);
  const row = await env.DB.prepare("SELECT image_key, image_type FROM animals WHERE id = ?").bind(id).first<{ image_key: string; image_type: string }>();
  if (!row) return new Response("Not found
[truncated — 1523 more characters]
```

### src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { headers } from "next/headers";
import Link from "next/link";
import "./globals.css";
import "./ui-refresh.css";
import "./rehoming-refresh.css";
import "./homey-final.css";
import { AppProvider } from "@/components/AppProvider";
import { SiteHeader } from "@/components/SiteHeader";
import { SiteFooter } from "@/components/SiteFooter";
import { AICoordinator } from "@/components/AICoordinator";

const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000";
const description = "Furly helps rescuers, foster carers and pet guardians find safe foster or adoptive homes through verified profiles and human-reviewed applications.";
export async function generateMetadata():Promise<Metadata>{
  const requestHeaders=await headers();
  const host=requestHeaders.get("x-forwarded-host")||requestHeaders.get("host");
  const protocol=requestHeaders.get("x-forwarded-proto")||(host?.startsWith("localhost")?"http":"https");
  const requestUrl=host?`${protocol}://${host}`:siteUrl;
  return {
    metadataBase:new URL(requestUrl), title:{default:"Furly | Adopt, Foster or Responsibly Rehome a Pet",template:"%s | Furly"}, description,
    keywords:["pet adoption Bali","animal foster Bali","responsible pet rehoming","animal rescue coordination","adopt a dog","adopt a cat"], applicationName:"Furly", alternates:{canonical:"/"},
    openGraph:{type:"website",url:requestUrl,siteName:"Furly",title:"Furly | From rescue to home",description:"Adopt, foster or responsibly rehome through a trusted, human-reviewed network.",images:[{url:`${requestUrl}/og.png`,width:1200,height:630,alt:"Furly, from rescue to home"}]},
    twitter:{card:"summary_large_image",title:"Furly | From rescue to home",description:"Adopt, foster or responsibly rehome through a trusted network.",images:[`${requestUrl}/og.png`]}, robots:{index:true,follow:true}
  };
}
export default function Layout({children}:{children:React.ReactNode}){
  const structuredData={"@context":"https://schema.org","@type":"WebApplication",name:"Furly",url:siteUrl,applicationCategory:"LifestyleApplication",description,offers:{"@type":"Offer",price:"0",priceCurrency:"IDR"}};
  return <html lang="en" suppressHydrationWarning><body><AppProvider><script type="application/ld+json" dangerouslySetInnerHTML={{__html:JSON.stringify(structuredData)}}/><SiteHeader/><main>{children}</main><SiteFooter/><AICoordinator/><nav className="mobile-nav" aria-label="Mobile navigation"><Link href="/"><span>⌂</span>Discover</Link><Link href="/animals"><span>♡</span>Find pet</Link><Link href="/rehoming" className="mobile-report"><span>＋</span>Add animal</Link><Link href="/updates"><span>◷</span>Updates</Link><Link href="/profile/ayu"><span>○</span>Profile</Link></nav></AppProvider></body></html>;
}

```

### src/app/page.tsx

```typescript
"use client";

import Link from "next/link";
import { useState } from "react";
import { animals } from "@/lib/data";
import { AnimalCard } from "@/components/AnimalCard";
import { Badge, Button } from "@/components/ui";

export default function Home() {
  const [type,setType]=useState("All");
  const shown=type==="All"?animals:animals.filter(animal=>animal.species===type);
  return <>
    <section className="home-hero cozy-hero"><div className="shell home-hero-grid"><div className="hero-copy"><Badge tone="coral">From one caring home to the next</Badge><h1>Let’s find them a safe, loving home.</h1><p>For rescuers, foster carers and pet guardians, everything you need to rehome, foster or adopt with care.</p><div className="hero-actions"><Button href="/rehoming" className="report-cta">Find a home</Button><Button href="/animals" variant="secondary">Adopt or foster</Button></div><div className="trust-row"><span>✓ Human-reviewed</span><span>♡ Kind and private</span><span>◇ Never animal selling</span></div></div><div className="hero-photo cozy-photo"><img src={animals[1].image} alt="Nori relaxing in a warm foster home"/><span className="hero-heart">♡</span><div className="photo-note"><span className="live-dot"/><div><b>Nori is ready to meet her family</b><small>Fostered in Ubud</small></div><Link href="/animals/nori" aria-label="Meet Nori">Meet Nori →</Link></div></div></div></section>

    <section className="shell entry-section"><div className="entry-heading"><p className="eyebrow">WHAT DO YOU NEED?</p><h2>Start here.</h2></div><div className="entry-grid"><Link href="/rehoming" className="entry-card"><span>♡</span><div><small>RESCUER</small><h3>I found or rescued a pet</h3><b>Create a profile →</b></div></Link><Link href="/rehoming" className="entry-card"><span>⌂</span><div><small>FOSTER CARER</small><h3>My foster needs a home</h3><b>Find their next home →</b></div></Link><Link href="/rehoming" className="entry-card"><span>○</span><div><small>PET GUARDIAN</small><h3>I need to rehome with care</h3><b>Start safely →</b></div></Link></div></section>

    <section className="journey-section cozy-journeys"><div className="shell"><div className="section-heading"><div><p className="eyebrow">WAITING FOR HOME</p><h2>Someone special might be here.</h2></div><div className="category-tabs" role="group" aria-label="Filter animals">{["All","Dog","Cat","Other"].map(item=><button key={item} aria-pressed={type===item} onClick={()=>setType(item)}>{item==="All"?"All":`${item}s`}</button>)}</div></div><div className="animal-grid">{shown.map(animal=><AnimalCard key={animal.id} animal={animal}/>)}</div><div className="section-end"><Button href="/animals" variant="secondary">See all pets</Button></div></div></section>

    <section className="homey-benefits"><div className="shell"><div><span>♡</span><b>Honest profiles</b><small>Care history stays clear.</small></div><div><span>✓</span><b>Thoughtful matches</b><small>People make every decision.</small></div><div><span>＋</span><b>Care that continues</b><small>Follow-ups and future vet benefits.</small></div></div></section>

    <section className="partner-band cozy-membership"><div className="shell"><div><Badge tone="coral">Free for the MVP</Badge><h2>Profiles, applications and rescue help stay open to everyone.</h2><p>Furly is free to use. People can contribute through fostering, transport, translation, photography and other practical help.</p></div><div><Button href="/help">See ways to help</Button></div></div></section>

    <section className="policy-strip"><div className="shell"><div><span>♡</span><p><b>Homes, never sales.</b><br/>Core profiles, applications and rescue help stay free.</p></div><Link href="/rehoming">Our safe placement promise →</Link></div></section>
  </>;
}

```

### src/app/donate/page.tsx

```typescript
import {redirect} from "next/navigation";

export default function RetiredDonatePage(){redirect("/help")}

```

### src/app/support/page.tsx

```typescript
import {redirect} from "next/navigation";

export default function RetiredSupportPage(){redirect("/help")}

```

### src/app/support/layout.tsx

```typescript
import type {Metadata} from "next";
export const metadata:Metadata={robots:{index:false,follow:true}};
export default function Layout({children}:{children:React.ReactNode}){return children}

```

### src/app/animals/layout.tsx

```typescript
import type { Metadata } from "next";
export const metadata: Metadata = { title: "Adopt or Foster a Pet in Bali", description: "Browse verified dogs, cats and other companion animals looking for adoptive or foster homes in Bali.", alternates: { canonical: "/animals" } };
export default function AnimalsLayout({children}:{children:React.ReactNode}){return children}

```

[56 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]