# Project export: Look After

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: LookAfter helps people leave essential meeting details, set a safety check-in, and preserve a useful record if they miss it.
- Devpost: https://devpost.com/software/vibe-no-code
- GitHub: https://github.com/seoanz7x-eng/girlslookaftereachother
- Demo: https://lookafter-buildweek.memkeeee.chatgpt.site/
- Video: https://www.youtube.com/embed/IeFpPlsTCpA?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Codex (90 commits)

## Devpost submission (written by the team)

### Overview

Enjoy the excitement. Leave a clue, just in case. LookAfter is a mobile-first safety planner for dates and solo meetups. It helps someone record who they are meeting, where they are going, and when they expect to check in—before that information becomes difficult to recover.

### Inspiration

The idea began with a simple question: What information would be useful if someone did not return or check in as expected? In many publicly reported cases, the problem was not a lack of concern. Friends and family simply did not know which trail someone had taken, where a date was happening, why a route home had changed, or who was behind a profile shared before a meeting. A small clue recorded in advance could provide important context later. However, I did not want to build a fear-based product or another invasive tracking service. LookAfter should not monitor the person being met, demand unnecessary verification, or behave like an emergency service. It should let the user decide what information is worth leaving behind. That led to the core idea: Create a simple safety plan before leaving, check in later, and preserve a useful record if the check is missed. What LookAfter does Before a date or solo meetup, a user can create a safety plan containing: The person they are meeting The meeting place A planned safety-check time How they met A profile or social account Identifying details Transport information Their plan for getting home Any additional note that may provide useful context The additional fields are optional. LookAfter keeps the primary flow short while allowing more detail when the situation calls for it. The user can then choose between two outcomes: Nominate a trusted contact who would receive a prepared information notice after a missed safety check. Keep a server-backed safety record without a contact, allowing the plan to remain recorded even when the user does not want to involve someone else. Before starting the plan, LookAfter previews exactly what the contact notice or server record will contain. This avoids vague buttons such as “send a message” without showing the user what would actually happen. Once the plan begins, LookAfter creates a record with a unique reference number and displays: The meeting details The remaining safety-check time The nominated contact, when one exists Sequential location updates The last recorded location Options to confirm safety or extend the deadline by 30 minutes If the user confirms that they are safe, the plan closes quietly and no notice is prepared. The completed safety record remains available for review. If the safety check is missed, LookAfter waits through a short grace period before preparing the contact notice or preserving the final server record. LookAfter is not an emergency service and does not prove that someone is in danger. Its purpose is narrower: to make sure that useful information is not lost. The real-stories casebook The landing experience includes a short casebook based on publicly reported situations. These stories show several recurring information gaps: No one knew which route someone had taken. No one knew where a date was happening. A planned route home changed unexpectedly. A profile shared before a meeting later became an important clue. The casebook is not included to create panic. It explains why the product exists and helps users understand the value of recording a small amount of information before leaving. How I built it I built LookAfter as a responsive, mobile-first web application because the product is intended to be used immediately before and during a meetup. The experience is structured around explicit plan states: Drafting the safety plan Reviewing the information Starting and saving the plan Running the safety-check countdown Extending the check time Handling a missed check Confirming safety Preserving the completed record Designing these states explicitly was important. A safety product cannot rely on disconnected screens or ambiguous button behavior. Each action must lead to a predictable result, and the interface must explain that result before the user commits. The application also includes: Mobile-responsive layouts English and Korean interfaces Form validation Optional additional-detail fields Contact and contact-free record flows Message and record previews Persistent server-backed safety records Sequential demo-location updates Completed-plan records How I used Codex and GPT-5.6 Codex and GPT-5.6 were used as development tools rather than being forced into the final user experience. I used them throughout the project to: Turn the initial concept into a structured product flow Define the safety-plan states and transitions Implement and refine the mobile interface Build form validation and conditional fields Implement contact and server-record alternatives Develop the message-preview experience Find and fix mobile layout problems Review unclear language and safety-related wording Test the complete flow repeatedly Identify unnecessary verification and remove friction Compare the running application against the intended requirements I remained responsible for the product decisions, particularly around privacy, tone, demo scope, and what information LookAfter should or should not collect. Codex accelerated implementation, debugging, testing, and iteration. GPT-5.6 was most useful when translating an ambiguous safety concept into explicit states, edge cases, interface copy, and testable behavior. Challenges I faced Communicating safety without creating fear The product deals with situations that can become serious, but an alarmist interface would discourage normal use. I had to keep the experience warm, calm, and appropriate for someone who is still excited about going on a date. Balancing simplicity with useful detail A long mandatory form would make people abandon the product. A form that was too short would fail to preserve useful context. The solution was to require only the person, place, and safety-check time while keeping all other information optional. Supporting users without trusted contacts Originally, the flow focused heavily on sending a message to another person. That excluded users who did not want to involve someone else or did not have an appropriate contact. Adding a server-backed record made the product more inclusive and gave every plan a meaningful outcome. Making the message understandable A generic “SMS test” did not explain what the contact would receive or what they should do. I redesigned this as a complete message preview containing the meeting information, safety guidance, and explanatory link. Demonstrating safely within a hackathon The public build does not send real SMS messages or collect real GPS data. It uses a complete SMS preview and sequential demo locations so judges can test the full product flow without sharing personal location data or contacting a real phone number. The interface clearly labels these boundaries rather than pretending that simulated behavior is live infrastructure. What I learned The most important lesson was that a safety product is not defined only by its features. Its states, language, defaults, and failure behavior are equally important. I also learned that: Optionality is essential when handling sensitive information. Users need to see the outcome of an action before committing. A contact should receive context and guidance, not only an alarming notification. A missed check does not prove danger. A useful record can still have value without continuous surveillance. AI does not need to appear inside the product simply because AI helped build it. Codex was most valuable as an engineering collaborator that helped me move repeatedly between product thinking, implementation, testing, and refinement. What I am proud of I am proud that LookAfter became a coherent working experience rather than only a safety-app concept. The final product demonstrates the complete journey: understand the problem → create a plan → choose a fallback → review the result → start the plan → check in or extend → preserve the record Every major action is visible and understandable on a mobile screen. What is next After the hackathon, the next steps would be: Integrating an authorized SMS provider Adding consent-based live location recording Encrypting sensitive plan fields Introducing configurable grace periods Allowing multiple trusted contacts Adding automatic record-expiration controls Conducting structured usability and safety testing Consulting professionals experienced in privacy, personal safety, and crisis communication LookAfter is not intended to replace emergency services. It is a small preventive tool designed around one specific promise: If something does not go as planned, one useful clue will still remain.

## README (from the GitHub repository)

# LookAfter

> Leave a plan. Check in. Give someone a place to start.

[Live demo](https://lookafter-buildweek.memkeeee.chatgpt.site/) · Apps for Your Life · Mobile-first PWA · English and Korean

## One-line description

LookAfter is a mobile safety-plan demo that lets someone leave the person, place, check-in time, and optional clues before going out, then shows what happens after a safe check-in or a missed check.

## Problem

When someone is late after a first date or solo outing, a friend may not know who they planned to meet, where to begin, or whether any useful record exists. Asking people to share everything live creates a different privacy problem. LookAfter focuses on a smaller action before the outing: leave a concise plan and choose what should happen if the check is missed.

## Solution

The demo creates a server-backed safety record without requiring an account. The user may name a contact or choose a server-record-only path. A completed check closes quietly. For judging, a clearly labeled simulation can move through the missed-check states immediately; it either produces a sandbox message and recipient view or stores the record without a message.

## Target users

The primary demo story is a young adult preparing for a first date. The same pattern can support solo travel or any outing where a person wants to leave a small set of facts without enabling continuous surveillance.

## Core user flow

1. Read or skip the five mobile onboarding cards.
2. Enter the person, meeting place, and future safety-check time.
3. Optionally add factual clues.
4. Choose a named contact or the no-contact record path.
5. Review the exact SMS or server-record preview.
6. Start the plan, inspect the record ID and simulated location events, then check in, extend by 30 minutes, or explicitly run the missed-check demo.
7. Review the sandbox message/recipient page or the stored record.

## Key features

- Server-backed D1 safety records and deterministic state transitions
- Contact-selected and no-contact branches
- Random record IDs plus separate owner and recipient capabilities
- Exact 30-minute wall-clock extension, including midnight rollover
- Three server-saved simulated location events
- Server-backed SMS outbox simulation and capability-scoped recipient page
- Record history for the current browser tab, record deletion, and refresh restoration
- English/Korean UI, keyboard/swipe onboarding, and mobile accessibility work
- PWA manifest, install icons, service worker, and an offline fallback

## Contact-selected flow

A contact name and phone number are required. The public demo stores the normalized number with the record and only displays a masked value after creation. The missed-check simulation moves through `CHECK_DUE` and `GRACE_PERIOD`, builds a deterministic message, and advances the server outbox through `pending`, `sent`, and `delivered`. No SMS provider is called and no real SMS is sent.

## No-contact safety-record flow

The user can create a plan without naming a contact. The missed-check simulation creates no outbox row and moves the record to `STORED`. The owner capability can still open or delete that record in the current browser tab.

## Technical architecture

- **Client:** Next.js App Router, React 19, TypeScript, and CSS
- **Server:** Next.js route handlers compiled by vinext to a Cloudflare Worker
- **Database:** Cloudflare D1 accessed directly through the Worker binding; Drizzle defines the schema and migrations
- **Hosting:** OpenAI Sites configuration in `.openai/hosting.json`
- **State:** React component state for the UI; D1 is the record source of truth; `sessionStorage` retains the draft and owner capabilities for the current tab; `localStorage` retains only the language preference and performs legacy migration cleanup

See [submission/ARCHITECTURE.md](submission/ARCHITECTURE.md) for diagrams and implementation details.

## Plan state machine

The client draft is not stored on the server. A created record begins as `ACTIVE`, then can become `CHECK_DUE`, `GRACE_PERIOD`, `NOTIFIED` or `STORED`, and finally `SAFE` or `CLOSED` where the relevant transition is allowed. The server validates every action.

## Data model

`lookafter_records` stores plan text, locale, IANA time-zone label, contact choice, normalized phone number, state, and latest simulated location. `lookafter_location_events` stores ordered simulated events. `lookafter_outbox` stores one sandbox message and its simulated status per record.

Record IDs use eight random bytes rendered as uppercase hexadecimal with an `LA-` prefix. Owner and recipient capabilities use 32 random bytes. Only SHA-256 digests of capabilities are stored in D1.

## Local-time handling

The safety-check value is stored as the entered `YYYY-MM-DDTHH:mm` wall-clock value, together with the browser's IANA time-zone label. It is formatted without applying a second offset. A 30-minute extension performs calendar arithmetic on the stored wall-clock fields and correctly crosses midnight. The countdown maps that value to an epoch on the current device, so changing devices or time zones is a known demo limitation; this is not a production UTC scheduler.

## SMS sandbox and recipient link

The final product calls no SMS provider. The server uses a fixed template, creates one outbox row, and simulates delivery states. The message contains a public explanation link with no plan data and a separate high-entropy recipient capability URL. The raw recipient capability is not stored in D1.

## Mobile and PWA experience

The UI is designed around a narrow mobile frame, safe-area spacing, large primary controls, swipe/keyboard onboarding, focus handoff, and hidden inactive carousel slides. The service worker caches only a small non-sensitive shell and never caches API responses, query-bearing pages, or the recipient page.

## Privacy and safety decisions

LookAfter keeps user-entered facts unchanged at runtime. It does not judge whether another person is dangerous, contact emergency services, verify identity, or continuously track location. The public demo is not an emergency service. See [submission/SECURITY_AND_PRIVACY.md](submission/SECURITY_AND_PRIVACY.md).

## How Codex and GPT-5.6 were used

Codex was our primary product and engineering collaborator throughout Build Week, not a one-time code generator. We used it to turn an initially broad idea about personal safety into a focused one-minute judge flow, inspect the existing code before each iteration, implement changes across the client, API, database, and PWA layers, diagnose regressions, write focused tests, verify the deployed service, and prepare the submission evidence. The repository history records that collaboration from product exploration through the final deployment.

### How Codex accelerated the workflow

- **Rapid product iteration:** We could describe a user problem in plain language, test the resulting flow, and use Codex to trace the feedback to the relevant state, copy, component, API action, and test. This shortened the loop from idea to deployed, verified behavior.
- **Cross-layer implementation:** Codex coordinated changes that otherwise required separate passes through React UI state, Next.js route handlers, D1 migrations, capability checks, the service worker, localization, and regression tests.
- **Evidence-driven debugging:** When the same check-in time appeared differently across screens, Codex traced the conversion path, isolated the repeated time-zone transformation, introduced a single wall-clock model for the demo, and added Perth, Sydney, UTC, Los Angeles, midnight, and repeated-extension coverage.
- **Continuous verification:** Codex converted recurring UX failures into maintained checks. The final test command performs a production build and runs 37 tests covering onboarding, localization, time arithmetic, record transitions, contact and no-contact branches, recipient capabilities, deletion, SMS sandbox wording, and PWA ca

[README truncated for size]

## Detected evidence (automated analysis)

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

## Codebase structure (from repository index)

### Files (117 of 117)

```
.env.example
.github/workflows/ci.yml
.gitignore
.openai/hosting.json
app/_legal/legal-config.ts
app/_legal/legal-manifest.ts
app/_legal/legal.module.css
app/_legal/LegalShell.tsx
app/api/health/route.ts
app/api/safety-records/route.ts
app/BuildWeekDemo.tsx
app/cloudflare-workers.d.ts
app/globals.css
app/layout.tsx
app/message/page.tsx
app/message/RecipientView.tsx
app/page.tsx
app/privacy/page.tsx
app/PwaRegister.tsx
app/safety/page.tsx
app/server/lookafter-store.ts
app/support/page.tsx
app/terms/page.tsx
app/time-model.mjs
db/index.ts
db/schema.ts
docs/ARCHITECTURE.md
docs/AUTH_SETUP.md
docs/BUILD_WEEK_CODEX_STORY.md
docs/BUILD_WEEK_DEMO_RUNBOOK.md
docs/BUILD_WEEK_SUBMISSION.md
docs/COMPETITIVE_REVIEW_UPGRADE.md
docs/GLOBAL_LAUNCH_FRAMEWORK.md
docs/OPERATIONS_RUNBOOK.md
docs/PRIVACY_SAFETY_REVIEW.md
docs/PRODUCT_SPEC.md
docs/PUBLIC_LAUNCH_GATES.md
docs/PUBLIC_READINESS_SCORECARD.md
docs/USER_TEST_PROTOCOL.md
docs/VIRTUAL_USER_TEST_2026-07-20.md
drizzle.config.ts
drizzle/0000_curious_master_mold.sql
drizzle/0001_opposite_black_panther.sql
drizzle/0002_overrated_cardiac.sql
drizzle/0003_living_molten_man.sql
drizzle/0004_material_rictor.sql
drizzle/0005_noisy_jigsaw.sql
drizzle/0006_tranquil_rocket_racer.sql
drizzle/0007_yielding_ben_parker.sql
drizzle/0008_cleanup_lookafter_qa.sql
drizzle/0009_add_contact_phone.sql
drizzle/meta/_journal.json
drizzle/meta/0000_snapshot.json
drizzle/meta/0001_snapshot.json
drizzle/meta/0002_snapshot.json
drizzle/meta/0003_snapshot.json
drizzle/meta/0004_snapshot.json
drizzle/meta/0005_snapshot.json
drizzle/meta/0006_snapshot.json
drizzle/meta/0007_snapshot.json
drizzle/meta/0008_snapshot.json
drizzle/meta/0009_snapshot.json
eslint.config.mjs
examples/d1/app/api/notes/route.ts
examples/d1/db/schema.ts
next.config.ts
package.json
postcss.config.mjs
proxy.ts
public/manifest.webmanifest
public/offline.html
public/sw.js
README.md
scripts/run-vinext.mjs
scripts/smoke-release.mjs
submission/ARCHITECTURE.md
submission/assets/README.md
submission/assets/screenshots/README.md
submission/BUILD_WEEK_CHANGES.md
submission/CODEX_GPT56_USAGE.md
submission/DEVPOST_SUBMISSION.md
submission/ko/DEVPOST_SUBMISSION_KO.md
submission/ko/REVIEW_SUMMARY_KO.md
submission/LIMITATIONS.md
submission/LINK_CHECK.md
submission/PROJECT_FACTS.json
submission/README.md
submission/SAMPLE_DATA.json
submission/SAMPLE_DATA.md
submission/SCREENSHOT_PLAN.md
submission/SECURITY_AND_PRIVACY.md
submission/SUBMISSION_CHECKLIST.md
submission/TESTING_INSTRUCTIONS.md
submission/THIRD_PARTY_NOTICES.md
submission/VERIFIED_TEST_RESULTS.md
tests/backend-safety-session.test.mjs
tests/build-week-demo.test.mjs
tests/cloudflare-workers-loader.mjs
tests/csp-nonce.test.mjs
tests/deletion-evidence.test.mjs
tests/first-date-ux-regressions.test.mjs
tests/judge-demo-route.test.mjs
tests/legacy-plan-replay.test.mjs
tests/operations-retention.test.mjs
tests/public-legal-pages.test.mjs
tests/release-policy.test.mjs
tests/rendered-html.test.mjs
tests/safety-plan.test.mjs
tests/server-records.test.mjs
tests/time-model.test.mjs
tests/vault-boundaries.test.mjs
tests/vault-crypto.test.mjs
tests/vault-route-races.test.mjs
tsconfig.app.json
tsconfig.json
vite.config.ts
worker/index.ts
```

### Dependencies

- package.json: @cloudflare/vite-plugin@1.44.0, @supabase/supabase-js@2.110.7, @tailwindcss/postcss@4.2.1, @types/node@22.19.19, @types/react@19.2.14, @types/react-dom@19.2.3, @vitejs/plugin-react@6.0.2, @vitejs/plugin-rsc@0.5.26, drizzle-kit@0.31.10, drizzle-orm@0.45.2, eslint@9.39.4, eslint-config-next@16.2.6, next@16.2.6, react@19.2.6, react-dom@19.2.6, react-server-dom-webpack@19.2.6, tailwindcss@4.2.1, typescript@5.9.3, vinext@0.0.50, vite@8.1.4, wrangler@4.110.0

### Recent commits (newest first)

- Explain Codex and GPT-5.6 collaboration
- Record verified Build Week submission evidence
- Prepare Build Week submission package and judge path
- Fix record navigation and add message guide
- Add SMS review and completion actions
- Simplify details and preserve record history
- Localize English demo identities
- Make English the default locale
- Collect contact details for safety notices
- Fix onboarding card first paint
- Clean up synthetic production QA records
- Fix LookAfter privacy and recipient flows
- Build server-backed LookAfter safety demo
- Refocus LookAfter on a direct safety plan
- Use a supported structured output schema
- Remove local font paths from production build
- Fix production image delivery and API diagnostics
- Simplify LookAfter into one Build Week demo
- Tighten consent GPS and emergency UX
- Fix carousel focus handoff

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

### submission/SAMPLE_DATA.md

```markdown
# Fictional sample data

All names and details below are fictional. They are provided only for hackathon judging. Do not enter real personal data.

## English demo

- Plan owner: Maya
- Meeting person: Alex
- Trusted contact: Sophie
- Meeting place: Northbridge
- Detailed venue: a sample bar near the station
- How they met: a dating app
- Social profile: `@sample_profile`
- Safety-check time: choose a future local time on the judging device
- Other note: arriving in a black vehicle
- Contact phone: use a fictional 7–15 digit value; no real SMS is sent
- Location history: `Northbridge` → `Near Northbridge` → `Near the bar` (simulated)

## Korean localization

- Plan owner: 민지 (Minji)
- Meeting person: 민수 (Minsu)
- Trusted contact: 지은 (Jieun)
- Meeting place: 강남역 (Gangnam Station)
- Detailed venue: 11번 출구 근처의 가상 바
- How they met: Tinder
- Social profile: `@sample_profile`
- Safety-check time: 실행 시점보다 뒤의 현지 시간
- Other note: 검은색 차량으로 도착 예정
- Location history: 강남역 → 강남역 인근 → ○○바 근처 (simulated)

The current English public flow intentionally uses globally neutral names instead of Korean names. Both sets refer to fictional demo personas, not the same verified people.

```

### submission/SCREENSHOT_PLAN.md

```markdown
# Static screenshot plan

No screenshots were fabricated. Windows browser automation stopped because it could not determine the current browser URL with enough confidence to enforce its safety policy. Capture the six images below manually after the final deployment.

This is a static-image plan only. It is not a video script, recording scenario, or storyboard.

## Shared capture settings

- Use one mobile viewport, ideally 390 × 844 CSS pixels at 2× device scale.
- Use English mode for Devpost.
- Hide browser chrome and unrelated desktop content.
- Use only the fictional Maya/Alex/Sophie data.
- Keep typography readable; export PNG at consistent dimensions.
- Never show an unmasked phone number or a full recipient capability URL.
- If a record ID appears, it may remain visible; redact the `access=` query value completely.
- Do not imply that GPS or SMS is real.

## Required captures

1. `01-onboarding.png` — first card with hero image, headline, and Next/Skip controls.
2. `02-plan-details.png` — plan form with Alex, Northbridge, future local check-in, contact choice, and only fictional contact fields.
3. `03-active-record.png` — created record, current state, exact time, and simulated location list.
4. `04-sms-sandbox.png` — delivery card plus message body, with the sandbox disclosure visible and sensitive query capability redacted.
5. `05-recipient-view.png` — recipient actions and safety information; the browser address bar must not expose the capability.
6. `06-no-contact-record.png` — stored-record outcome showing that no message was created.

## Validation before adding files

- Every image uses the same viewport/background.
- No real phone number, name, profile, or location is present.
- “Public demo,” “sandbox,” or “simulated” language is visible where relevant.
- The SMS and recipient screenshots come from the same synthetic record.
- No API keys, cookies, request headers, DevTools, or capability tokens are visible.
- Add each final file to `submission/assets/screenshots/` and update `submission/assets/README.md` from `PENDING` to `CAPTURED`.

```

### package.json

```
{
  "name": "lookafter-safety-vault",
  "version": "0.1.0",
  "private": true,
  "engines": {
    "node": ">=22.18.0"
  },
  "scripts": {
    "dev": "node scripts/run-vinext.mjs dev",
    "build": "node scripts/run-vinext.mjs build",
    "start": "node scripts/run-vinext.mjs start",
    "test": "npm run build && node --import ./tests/cloudflare-workers-loader.mjs --test tests/time-model.test.mjs tests/build-week-demo.test.mjs tests/server-records.test.mjs",
    "typecheck": "tsc --noEmit -p tsconfig.app.json",
    "lint": "eslint . --ignore-pattern dist --ignore-pattern .next",
    "db:generate": "drizzle-kit generate",
    "smoke:release": "node scripts/smoke-release.mjs"
  },
  "dependencies": {
    "@supabase/supabase-js": "2.110.7",
    "drizzle-orm": "0.45.2",
    "next": "16.2.6",
    "react": "19.2.6",
    "react-dom": "19.2.6"
  },
  "devDependencies": {
    "@cloudflare/vite-plugin": "1.44.0",
    "@tailwindcss/postcss": "4.2.1",
    "@types/node": "22.19.19",
    "@types/react": "19.2.14",
    "@types/react-dom": "19.2.3",
    "@vitejs/plugin-react": "6.0.2",
    "@vitejs/plugin-rsc": "0.5.26",
    "drizzle-kit": "0.31.10",
    "eslint": "9.39.4",
    "eslint-config-next": "16.2.6",
    "react-server-dom-webpack": "19.2.6",
    "tailwindcss": "4.2.1",
    "typescript": "5.9.3",
    "vinext": "0.0.50",
    "vite": "8.1.4",
    "wrangler": "4.110.0"
  },
  "overrides": {
    "next": {
      "postcss": "8.5.10"
    }
  },
  "type": "module"
}

```

### app/page.tsx

```typescript
import type { Metadata } from "next";
import { BuildWeekDemo } from "./BuildWeekDemo";

export const metadata: Metadata = {
  title: { absolute: "LookAfter · A one-minute safety plan" },
  description: "A mobile safety-plan demo for leaving useful details before meeting someone new.",
};

export default function Home() {
  return <BuildWeekDemo />;
}

```

### worker/index.ts

```typescript
/** Cloudflare Worker entry point for the LookAfter Build Week demo. */
import handler from "vinext/server/app-router-entry";

interface Env {
  ASSETS: Fetcher;
}

interface ExecutionContext {
  waitUntil(promise: Promise<unknown>): void;
  passThroughOnException(): void;
}

const worker = {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    return handler.fetch(request, env, ctx);
  },
};

export default worker;

```

### db/index.ts

```typescript
import { env } from "cloudflare:workers";
import { drizzle } from "drizzle-orm/d1";
import * as schema from "./schema";

export function getD1(): D1Database {
  if (!env.DB) {
    throw new Error(
      "Cloudflare D1 binding `DB` is unavailable. Set the `d1` field in .openai/hosting.json to `DB` or let your control plane inject the real binding values before using the database."
    );
  }

  return env.DB;
}

export function getDb() {
  return drizzle(getD1(), { schema });
}

```

### app/layout.tsx

```typescript
import type { Metadata, Viewport } from "next";
import "./globals.css";
import { PwaRegister } from "./PwaRegister";

export const metadata: Metadata = {
  metadataBase: new URL("https://lookafter-buildweek.memkeeee.chatgpt.site"),
  title: {
    default: "LookAfter · A one-minute safety plan",
    template: "%s · LookAfter",
  },
  description: "A mobile safety-plan demo for leaving useful details before meeting someone new.",
  manifest: "/manifest.webmanifest",
  appleWebApp: {
    capable: true,
    statusBarStyle: "default",
    title: "LookAfter",
  },
  icons: {
    icon: [
      { url: "/favicon.svg", type: "image/svg+xml" },
      { url: "/lookafter-icon-192.png", type: "image/png", sizes: "192x192" },
      { url: "/lookafter-icon-512.png", type: "image/png", sizes: "512x512" },
    ],
    apple: [{ url: "/apple-touch-icon.png", type: "image/png", sizes: "180x180" }],
  },
  openGraph: {
    title: "LookAfter · A one-minute safety plan",
    description: "Leave the person, place, and safety-check time, then experience the missed-check flow.",
    type: "website",
    images: [{
      url: "/og-app.png",
      width: 1536,
      height: 1024,
      alt: "LookAfter one-minute safety plan",
    }],
  },
  twitter: {
    card: "summary_large_image",
    title: "LookAfter · A one-minute safety plan",
    description: "A mobile safety plan for leaving the person, place, and check-in time.",
    images: ["/og-app.png"],
  },
  formatDetection: { telephone: false },
};

export const viewport: Viewport = {
  themeColor: "#17362d",
  colorScheme: "light",
  viewportFit: "cover",
};

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
  return <html lang="en"><body>{children}<PwaRegister /></body></html>;
}

```

### app/message/page.tsx

```typescript
import type { Metadata } from "next";
import { RecipientView } from "./RecipientView";

export const metadata: Metadata = {
  title: { absolute: "LookAfter · Message guide" },
  description: "What a LookAfter safety-check message means and what its recipient can do next.",
};

export default async function MessagePage({ searchParams }: { searchParams: Promise<{ access?: string; about?: string; lang?: string }> }) {
  const { access = "", about = "", lang = "en" } = await searchParams;
  return <RecipientView token={access} about={about === "1"} aboutLocale={lang === "ko" ? "ko" : "en"} />;
}

```

### app/support/page.tsx

```typescript
import type { Metadata } from "next";
import Link from "next/link";
import { ContactBlock, LegalShell, Section, SimpleList } from "../_legal/LegalShell";
import { getPublicOperatorConfig } from "../_legal/legal-config";

export const metadata: Metadata = {
  title: "도움과 문의 · Support",
  description: "LookAfter 일반 문의, 개인정보 요청, 문자 수신 거부와 장애 안내입니다.",
};

export default function SupportPage() {
  const operator = getPublicOperatorConfig();

  return (
    <LegalShell
      current="support"
      eyebrow="도움과 문의 · 응급 신고 창구 아님"
      title="무엇을 도와드릴지 고르세요. 위험하면 여기서 기다리지 마세요."
      summary="앱 사용과 오류, 내 정보, 문자 그만 받기에 대해 묻는 곳이에요. 이곳은 경찰·구급·실종 신고를 받지 않고, 24시간 바로 답하지도 않아요. 화면에 연락 방법이 없으면 아직 문의를 받을 수 없어요."
      englishSummary="Ask here about using the app, errors, your information, or stopping SMS. This is not a police, ambulance, or missing-person report channel, and it does not promise an immediate or 24/7 response. If no contact method is shown, requests cannot be accepted yet."
    >
      <Section title="즉시 위험하거나 사람이 사라졌나요?" englishTitle="DANGER OR A MISSING PERSON" tone="warning">
        <SimpleList>
          <li><strong>LookAfter 답변을 기다리지 마세요.</strong></li>
          <li>현재 지역의 경찰·구급·소방 또는 공식 실종 신고 절차에 직접 연락하세요.</li>
          <li>LookAfter는 신고를 접수하거나 기관에 사건을 자동 전달하지 않습니다.</li>
        </SimpleList>
      </Section>

      <Section title="현재 지원 창구" englishTitle="CURRENT SUPPORT CHANNEL">
        {operator.isLaunchIdentityReady ? (
          <p>
            운영자: <strong>{operator.operatorName}</strong> · {operator.operatorCountry} · {operator.operatorAddress}. 지원 시간과 예상 답변
            시간은 실제 운영 정책에서 따로 알려야 합니다.
          </p>
        ) : (
          <p>
            운영자 이름·주소·지원 연락처가 모두 준비되지 않았습니다. 공개 출시와 실제 사용자 접수를
            시작하면 안 됩니다.
          </p>
        )}
        <ContactBlock />
      </Section>

      <Section title="문의할 수 있는 일" englishTitle="WHAT SUPPORT CAN HANDLE">
        <SimpleList>
          <li>계획 만들기, 안전 확인, 잠긴 비공개 사본 지우기 오류</li>
          <li>잘못된 문자 또는 문자 그만 받기(STOP) 문제</li>
          <li>내 정보 보기, 고치기, 지우기, 사용 멈추기 요청</li>
          <li>다른 사람이 내 정보를 허락 없이 올렸다는 신고</li>
          <li>접근성, 번역, 지역 안내 오류</li>
          <li>보안 문제의 책임 있는 신고</li>
        </SimpleList>
        <p>
          문의할 때 계획 코드의 일부와 오류가 난 시각은 적을 수 있지만, 비밀 패턴·전체
          전화번호·정확한 장소·이미지 원본은 보내지 마세요.
        </p>
        <p>
          허락 없는 정보, 괴롭힘, 스토킹 위험 또는 보안 문제는 먼저{" "}
          <Link href="/report">악용·보안 신고 안내</Link>를 읽고, 설정된 전용 이메일로만 알려
          주세요.
        </p>
      </Section>

      <Section title="문자를 그만 받고 싶어요" englishTitle="SMS OPT-OUT" tone="warning">
        <SimpleList>
          <li>운영 문자는 첫 메시지에 문자를 그만 받는 방법을 분명히 적어야 해요.</li>
          <li>지원되는 발신 번호에 <strong>STOP</strong>이라고 답하면 한 번에 거부할 수 있어야 합니다.</li>
          <li>STOP 뒤에는 다시 직접 동의하기 전까지 새 계획 문자도 보내면 안 됩니다.</li>
          <li>현재 Build Week 시험은 확인된 테스트 번호 하나만 사용합니다.</li>
          <li>STOP 수신 거부와 이후 발송 차단을 이 앱에서 끝까지 확인하지 못했기 때문에 일반 사용자 대상 SMS 출시는 막혀 있습니다.</li>
        </SimpleList>
        <p lang="en">
          The Build Week sandbox is restricted to one verified test number. Public SMS remains
          blocked until STOP and later-message suppression are verified end to end.
        </p>
      </Section>

      <Section title="내 정보를 요청하거나 지우고 싶어요" englishTitle="PRIVACY AND DELETION REQUESTS">
        <SimpleList>
          <li>원하는 일: 보기, 고치기, 지우기, 처리 멈추기, 동의 철회 중 하나를 알려 주세요.</li>
          <li>본인 확인에 꼭 필요한 정보만 요청해야 합니다.</li>
          <li>요청 접수·완료 시각과 남겨야 하는 정보가 있다면 이유를 알려야 합니다.</li>
          <li>계정형 자동 권리 포털과 검증된 답변 기한은 아직 없습니다.</li>
        </SimpleList>
        <ContactBlock purpose="privacy" />
        <p>
          현재 구현된 삭제 방식과 남은 한계는 <Link href="/privacy">개인정보 안내</Link>에서 정확히 볼 수
          있습니다.
        </p>
      </Section>

      <Section title="안전하게 사용하는 방법" englishTitle="SAFE USE">
        <p>
          첫 만남·혼자 여행 전 확인할 내용과 앱이 실제로 하지 못하는 일은{" "}
          <Link href="/safety">안전 안내</Link>에서 읽어 주세요. 이용 조건은{" "}
          <Link href="/terms">이용약관 검토안</Link>에 있습니다.
        </p>
      </Section>
    </LegalShell>
  );
}

```

### app/terms/page.tsx

```typescript
import type { Metadata } from "next";
import { ContactBlock, LegalShell, Section, SimpleList } from "../_legal/LegalShell";

export const metadata: Metadata = {
  title: "이용약관 검토안 · Terms",
  description: "LookAfter의 이용 조건과 안전 한계를 쉬운 말로 설명합니다.",
};

export default function TermsPage() {
  return (
    <LegalShell
      current="terms"
      eyebrow="이용약관 · 공개 출시 전 검토안"
      title="LookAfter가 할 수 있는 일과 할 수 없는 일을 먼저 확인하세요."
      summary="이 글은 아직 정식 이용약관이 아니에요. 서비스 책임자 정보와 나라별 법 확인이 끝나야 정식 문서로 쓸 수 있어요. LookAfter는 안전 계획을 적고, 확인을 놓치면 정해 둔 방법을 실행하는 도구예요."
      englishSummary="These are not final terms yet. The person or company responsible for the service and each country's rules must be checked first. LookAfter records a safety plan and follows the saved rule after a missed check."
    >
      <Section title="누가 사용할 수 있나요?" englishTitle="WHO MAY USE LOOKAFTER" tone="warning">
        <SimpleList>
          <li>본인 계획만 시작하고 관리할 수 있습니다.</li>
          <li>다른 사람을 몰래 추적하거나 압박하는 데 사용하면 안 됩니다.</li>
        </SimpleList>
      </Section>

      <Section title="이 서비스가 아닌 것" englishTitle="WHAT THIS SERVICE IS NOT">
        <SimpleList>
          <li>경찰, 구급, 경비, 실종 신고 또는 응급 출동 서비스가 아닙니다.</li>
          <li>휴대전화가 꺼지면 마지막으로 저장된 위치 뒤의 정보는 남지 않습니다.</li>
          <li>문자가 도착하거나 누군가 읽고 행동한다고 보장하지 않습니다.</li>
          <li>범죄, 위험, 실종을 막거나 증거를 법적으로 보존한다고 보장하지 않습니다.</li>
        </SimpleList>
        <p lang="en">
          LookAfter does not provide police, dispatch, guaranteed delivery,
          guaranteed safety, or legal evidence preservation.
        </p>
      </Section>

      <Section title="문자를 보낼 때의 약속" englishTitle="SMS RULES" tone="warning">
        <p>
          LookAfter가 문자를 보내려면, 받을 사람이 먼저 분명히 동의해야 해요. 계획마다 다시 초대를
          받을 필요는 없게 만들 수 있지만, 처음 연락처를 넣을 때는 그 사람이 직접 동의해야 해요.
          첫 문자에는 LookAfter가 보냈다는 말과 STOP을 보내 문자를 그만 받는 방법이 있어야 해요.
        </p>
        <SimpleList>
          <li>사용자가 “번호가 맞다”고 누르는 것만으로는 수신자 동의가 되지 않습니다.</li>
          <li>문자에는 사용자가 적은 자신의 이름이나 별명이 들어갑니다. 첫 만남 문자에는 사용자가 적은 만남 상대 표시 이름도 들어갑니다. 둘 다 확인된 실명이라고 단정하면 안 됩니다.</li>
          <li>STOP으로 거부한 번호에는 다시 동의하기 전까지 문자를 보내면 안 됩니다.</li>
          <li>통신사 전달 상태는 사람이 읽거나 행동했다는 뜻이 아닙니다.</li>
          <li>긴 문자 본문은 통신사나 문자 회사가 여러 SMS 부분으로 나눌 수 있습니다.</li>
          <li>사전동의·수신거부 시스템이 완성되기 전에는 공개판 문자를 꺼야 합니다.</li>
        </SimpleList>
        <p lang="en">
          A contact SMS includes the user-entered owner name or nickname. A first-date SMS may also include
          the meeting display/profile name entered by that user. Neither is a verified identity. The recipient and messaging provider can see them, and the provider&apos;s
          retention rules apply. While the original request is pending or its plan is active, the
          names may remain as plaintext in that browser tab&apos;s sessionStorage for exact retry and
          message display. Carriers may split a long body into multiple SMS parts. Delivery status
          never proves that a person read or acted on the message.
        </p>
      </Section>

      <Section title="올리면 안 되는 것" englishTitle="CONTENT YOU MUST NOT UPLOAD">
        <SimpleList>
          <li>올릴 권리가 없는 다른 사람의 개인정보</li>
          <li>미성년자의 정보나 이미지</li>
          <li>성적인 이미지, 불법 자료, 협박, 괴롭힘, 차별 내용</li>
          <li>여권·운전면허증 같은 신분증과 필요 없는 금융·의료정보</li>
          <li>몰래 녹음하거나 다른 법을 어겨 만든 자료</li>
        </SimpleList>
        <p>
          내용을 잠갔다고 해서 올릴 권리가 생기는 것은 아니에요. 이미지 신고와 삭제 방법이 준비되기
          전에는 모든 사람이 쓰는 버전에서 이미지 기능을 꺼야 해요.
        </p>
      </Section>

      <Section title="저장한 계획과 단서" englishTitle="SAVED PLAN AND CLUES">
        <SimpleList>
          <li>계획과 추가 단서는 잠근 뒤 서비스에 저장해요.</li>
          <li>문자를 고르면 등록한 연락처가 문자에 적힌 만남 정보를 확인할 수 있어요.</li>
          <li>문자를 보내지 않기로 하면 계획은 LookAfter 서버에 남아요.</li>
          <li>기관 요청을 처리하려면 신원과 법적 근거를 확인하고 접근 기록을 남기는 절차가 필요해요.</li>
          <li>안전 확인 뒤에도 잠긴 사본은 24시간 보호해요.</li>
          <li>24시간이 지나기 전에는 잠긴 사본을 먼저 지울 수 없어요.</li>
          <li>현재 패턴 잠금은 브라우저 데모이며 안전한 계정 인증이 아닙니다.</li>
        </SimpleList>
      </Section>

      <Section title="서비스가 멈출 수 있어요" englishTitle="AVAILABILITY AND FAILURES">
        <p>
          인터넷, 브라우저, 운영체제, 호스팅, 문자 회사 또는 사용자의 휴대전화 문제로 계획·문자·삭제가
          늦거나 실패할 수 있습니다. LookAfter 하나만 믿고 위험한 장소로 가지 마세요. 즉시 위험하면 이
          앱이 아니라 현재 지역의 응급기관에 직접 연락하세요.
        </p>
        <p lang="en">
          Network, device, hosting, or messaging failures can delay or stop the service. Do not rely
          on LookAfter as your only safety measure. Contact local emergency services directly when
          danger is immediate.
        </p>
      </Section>

      <Section title="그만 쓰기와 삭제" englishTitle="ENDING USE AND DELETION">
        <SimpleList>
          <li>안전 확인을 기록하고, 예약 문자가 있으면 문자 회사에 취소를 요청해요.</li>
          <li>문자 회사가 취소됐거나 이미 끝난 문자라고 알려 주기 전에는 멈췄다고 말하지 않아요. 이미 간 문자는 지울 수 없어요.</li>
          <li>준비된 잠긴 사본은 바로 지우지 않고 24시간 보호해요.</li>
          <li>24시간이 지나기 전에는 잠긴 사본을 먼저 지울 수 없어요.</li>
          <li>24시간이 지나면 자동 정리 대상이 돼요. 실패하면 <code>delete_pending</code>(삭제 다시 시도 필요)으로 표시하고 다시 시도해요.</li>
          <li>오래된 운영 기록과 잠긴 사본을 자동으로 지우는 기능은 만들었지만, 실제 서비스의 기록과 백업도 제때 지워지는지는 확인해야 해요.</li>
          <li>법적 보존 의무가 있으면 일부 기록을 정해진 기간 동안 보관할 수 있으며 미리 알려야 합니다.</li>
        </SimpleList>
        <p lang="en">
          Check-in records withdrawal and asks the messaging provider to cancel any scheduled SMS.
          LookAfter does not say that a scheduled message is gone until the provider confirms a
          canceled or other terminal status. A terminal message may already have been sent. Ready
          ciphertext is held for 24 hours and cannot be deleted early. After the deadline it
          becomes eligible for retention cleanup.
          Deployed cleanup and backup deletion are not guaranteed until they are proven.
        </p>
      </Section>

      <Section title="질문과 분쟁" englishTitle="QUESTIONS AND DISPUTES">
        <p>
          실제 약관에는 운영자 법인, 준거법, 소비자 권리, 불만 처리 방법이 들어가야 합니다. 이 정보가
          아직 없으므로 공개 출시가 차단되어 있습니다.
[truncated — 107 more characters]
```

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