Playground

Golden Hour

A weekend-planning app that turns "I don't know, what do you want to do?" into an actual, timed plan — designed end-to-end in continuous collaboration with an AI pairing partner.

Role
Product Engineer
Team
Solo
Tools
Claude (Cowork), HTML/CSS/JS, React
Deliverable
Web app
The problem

Deciding is the hardest part of a weekend.

Every Friday, some version of the same conversation happens: "What do you want to do this weekend?" "I don't know — what do you want to do?"

The tools that already exist solve adjacent problems, not this one. Review apps and event listings tell you what's happening nearby, but leave the actual sequencing — what fits before what, how long driving takes, whether the place will even be open when you get there — entirely up to you. Maps apps are great once you already know where you're going. Nothing sits in the gap between a vague feeling ("something outdoorsy, not too far, back in time for dinner") and a plan you can actually walk out the door and follow.

Golden Hour is a swipe-based planner that closes that gap: swipe on activities that match your mood, and it assembles them into a real itinerary — drive times, opening hours, sunset cutoffs, and all — that two people can look at and just go.

The goal wasn't another list of things to do nearby. It was turning "we should do something" into a plan with a time on it.
Who it's for

Two people, one plan.

Golden Hour is built around a simple asymmetry: one person usually initiates the plan, and someone else usually just wants to know where to show up.

Primary — the planner

Wants momentum, not more research

Has a rough mood in mind ("outdoorsy," "low-key," "somewhere new") but doesn't want to open six tabs to turn it into a plan. Values speed and a sense that the logistics are already handled.

Secondary — the partner

Wants clarity, not control

Gets invited into a plan already in motion. Doesn't need edit access — needs to know the schedule, the address, and what to wear. Once a plan is locked, their view should read like a itinerary, not a workspace.

Process

From brief to a real, clickable product.

I didn't start in a design tool. I started by writing down the problem, then moved as fast as possible into something I could actually interact with — because for a flow this dependent on state (swiped stops, drive-time math, locked vs. editable views), static frames hide more problems than they reveal.

1

Framed the problem & the system

Wrote a PRD and a technical plan first: what "done" looks like, what data a plan actually needs (stops, drive segments, timing, locked state), and where the product could get complicated later.

weekend-app-prd.md

PRD — Weekend Planner (working name: "Golden Hour")

Version: MVP v1 · Author: Sakura · Date: 2026-07-28 · Platform: PWA (Vercel), mobile-first


1. Problem

Weekends get wasted not from lack of ideas, but from decision friction, mood mismatch, couple coordination, and Saturday-morning inertia. Existing tools solve discovery; none solve deciding, agreeing, and actually going.

Target user: Couples (and solo users) in a city they've "exhausted," who want low-effort, mood-matched weekend plans.

2. Goals & success metrics

Metric Target
Weekends with a locked plan ≥ 3 of 4 per month
Follow-through (locked → went) ≥ 70%
Time from check-in to locked plan < 5 min
Both partners complete Thursday check-in ≥ 80% of weeks

3. MVP scope — the core loop

Thursday check-in → cards generated → swipe → match → confirm → Saturday nudge → Sunday recap.

In scope: accounts + partner linking, mood check-in, AI-generated cards + personal backlog, swipe-to-match, confirmation flow, leave-by notification, lightweight recap. Out of scope (v2+): photo journal, seasonality engine, streaks/gamification, veto tokens, API integrations (Places/Eventbrite), groups > 2.


4. Onboarding

Principle: ask only what can't be learned later. Target < 1 minute, 3 screens. Preferences are mostly learned from swipes and skip-reasons, not asked upfront.

Step Screen Collect Why
1 Welcome + sign up Email/Apple/Google, first name Identity, partner sync
2 Home base Home location (city or pin) All drive-time math
3 Partner invite Share link/code — skippable ("solo mode, invite later") Collaboration without blocking signup

Do NOT ask at onboarding: budget, energy defaults, detailed preference rankings, schedule. These come from weekly check-ins and behavior.

Partner linking: invitee gets a trimmed onboarding (step 1 only — home base inherits from the couple, editable). A couple = shared "space"; each person keeps a private taste profile.


5. Weekly planning flow

5a. Thursday check-in (both flows, ~30 seconds)

Push notification Thursday 6pm → 4 taps:

  1. Energy: Drained 😮‍💨 / Normal 🙂 / Restless ⚡
  2. Craving: Something new / Comfort & familiar / Surprise me
  3. Time window: Sat / Sun / both / one evening only
  4. Vibe modifier (optional): outdoorsy, foodie, cultural, cozy, active

Weather for the weekend is fetched automatically — never asked.

5b. Card generation

5–7 cards per weekend, generated from: check-in answers × taste profile × weather × drive range × saved backlog (backlog items matching current conditions get priority + a "you saved this" badge).

Anatomy of a card:

  • Title + one-line pitch + hero image
  • Drive time from home ("55 min") and effort tier: 🟢 Chill / 🟡 Half-day / 🔴 Big weekend
  • Cost estimate ($ / $$ / $$$) and booking flag ("book ahead" / "just show up" / "needs overnight stay")
  • Timing nudge when true: "Last month of wildflower season," "Sunset 8:41pm — latest of the year"
  • Suggested leave-by time

5c. Solo flow — 3 click-stops

  1. Check-in (4 taps)
  2. Browse cards — tap ❤️ to shortlist, ✕ to skip. Skip asks one-tap reason: too far / too tired / weather / done it / not feeling it (feeds the learning loop)
  3. Pick & confirm — choose one from shortlist → confirmation screen (§6)

5d. Couple flow — 3 click-stops each, async

  1. Both check in independently. Card set is generated from the intersection: energy = the lower of the two, cravings blended. If check-ins conflict hard (drained × restless), the deck explicitly includes 1–2 compromise cards (low-effort for one, novel for the other).
  2. Both swipe the same deck on their own phones, blind to each other's swipes (prevents anchoring). Match = both ❤️. Matches reveal with a small celebratory moment — this is the fun beat, protect it.
  3. Confirm together: first partner to open the match proposes ("Lock in Saturday?") → other gets one-tap Accept / Suggest Sunday instead / Counter with other match. One accept = locked. No endless negotiation loop: after one counter, it's accept-or-pass.

No-match fallback (critical path): if zero mutual ❤️: (a) show each person their partner's top pick with "meet them here?" one-tap yes; (b) if still nothing, turn-taking: "It's Alex's pick this week" — tool tracks whose turn, chooser picks from their own likes, partner is notified, no veto. Turn-taking is the pressure-release valve that keeps no-match weeks from becoming no-plan weeks.

Same flow, different stops? Same 3-stop skeleton for solo and couple — solo just skips the sync/accept mechanics. One codebase path, a partner_linked flag branches steps 1 and 3.


6. Confirmation ("Lock it in")

Confirmation converts intention → commitment. Screen shows:

  1. Day + leave-by time (pre-filled, editable) → adds to phone calendar (both calendars for couples)
  2. Prep checklist (auto-generated per card): "Book table for 2," "Pack layers — 12°C at summit," "Download offline map." Checkable Thu–Sat.
  3. Micro-commitment prompt: if the card has a bookable element, surface it now — "Reserve for $10? Booked plans are 3× more likely to happen." (Deep-link out; no in-app payments in MVP.)
  4. Locked state visible to both partners: "📍 Saturday: Tide-pooling at Half Moon Bay — leave by 9:40."

Saturday morning: one notification, 45 min before leave-by, with only the first step: "Leave by 9:40 — coffee stop at Verve on the way ☕." Not a checklist. Specificity beats inertia.

Cancel/reschedule — friction by design:

  • "Running late" → shifts leave-by, recalcs.
  • "Can't today" → offers downgrade first ("Rain? Here's the indoor backup card") before full cancel.
  • Full cancel requires a one-tap reason and notifies partner. Reasons feed the learning loop; repeated cancels of 🔴 cards → generate more 🟢.

Sunday 7pm recap (30 sec): "Did you go?" → 👍/👎 rating → optional 1–3 photos → "That's weekend 7 of 2026 well spent." Skippable, never nagging (one notification, no re-prompts).


7. Backlog ("Someday list")

Anytime, either partner saves an idea (free text, or share-sheet a link/screenshot into the app). Each item auto-tagged: effort, season, weather-dependency, drive time. Items resurface as cards only when conditions match — "You saved this rainy-day museum in March. It's raining Saturday."

8. Data model (sketch)

User (taste profile, home, range) · Couple (users[], turn_tracker) · Card (activity, drive_time, effort, cost, booking_flag, nudge, source: generated|backlog) · CheckIn (user, week, energy, craving, window) · Swipe (user, card, decision, skip_reason) · Plan (card, day, leave_by, status: proposed|locked|done|cancelled, rating, photos)

9. Open questions

  1. Thursday vs. Friday check-in — test which gets higher completion.
  2. Card images in MVP: AI-generated, stock, or none? (None may be fine; nudge text carries the card.)
  3. How aggressively should low ratings prune categories vs. just re-rank?
  4. Solo users: does turn-taking logic need an equivalent ("commit to your own top pick") nudge?

10. V2 parking lot

Shared photo journal + Sunday auto-recap reel · seasonality/scarcity engine ("3 golden-larch weekends left") · "12 free weekends before winter" counter · real event/places APIs · groups of 3+ · veto tokens & picker's-choice mechanics · preference-drift insights ("you both save food ideas but never go").

↑ scroll to read the full PRD

2

Defined the design system before the screens

Color tokens, type scale, spacing — decided as reusable variables, not one-off values, specifically so the product could evolve its look without a full rebuild later. (This decision mattered more than I expected — more on that below.)

design-system.md

Design System — Weekend Planner

Reference images: mood-tracker (warm cream base, grain-gradient orb, sage accent pill, elevated yellow chart card) + Trove (soft lavender backdrop, floating white cards with visible elevation, rounded phone-native UI). Vibe: playful, lightweight, exploratory, exciting, chill.

Color tokens

Token Hex Use
--bg-page #F7F1E3 App background, warm cream (not stark white)
--bg-section #EDE7D6 Secondary/grouped sections, subtle depth
--surface-card #FFFDF7 Card background — warm white, floats above page
--text-primary #211F1A Headlines, body — warm black, never pure #000
--text-secondary #6B675C Meta text, timestamps, captions
--border #E4DCC8 Hairline dividers, unfilled input borders
--accent-primary #F4F396 Pastel lemon-yellow — primary buttons, active states, progress, nav highlight
--accent-primary-hover #E8E570 Pressed/hover state
--on-accent-primary #3A3910 Text/icon on yellow fills — warm near-black, not white (contrast)
--accent-secondary #6F8452 Sage green — demoted to secondary: success states, "trust"-style confirmations, outdoorsy accents

Yellow is now the dominant, recognizable color — primary CTA buttons, active nav item, progress bars/rings, selected states, the swipe-right "yes" affordance. Use it generously and confidently, not as a small accent dot. Sage green steps back to a supporting role.

Gradient orb (hero moments only — Thursday check-in intro, match celebration, loading states): conic blend #F2C94C → #E8935A → #6FC3E0 → #7FB56B with subtle grain/noise overlay, like the reference. Used sparingly — it's the "delight" moment, not wallpaper. Never behind body text.

Vibe badges

Color-coded, pill-shaped (border-radius: 999px), tinted background + darker text of the same hue — never black text on a colored pill.

Vibe Background Text/icon Icon
Outdoorsy #E1EAD3 #4C6B34 tree / mountain
Foodie #F6E0C8 #A85A22 bowl / utensils
Cultural #E9DFF2 #6B4A94 ticket / palette
Cozy #F1DFDA #A8604A flame / cup
Active #FADADA #C23B34 bolt

Effort tiers reuse this language: 🟢 Chill = outdoorsy green tint · 🟡 Half-day = foodie amber tint · 🔴 Big weekend = active coral tint. Keeps the palette closed instead of adding a third color system.

Typography

Inter, throughout — no serif, no secondary display font.

Style Weight Size Use
Hero 800 32–40px "A brighter day ahead" moments — check-in intro, match celebration
Headline 700 22–24px Card titles, screen titles
Body 500 16px Card copy, buttons
Meta/label 600, uppercase, tracked +0.02em 12–13px Drive time, effort tier, timestamps

Elevation (card style)

Cards float on the cream/lavender background with soft, warm-toned shadow — never a cold gray shadow.

  • Default card: background: var(--surface-card); border-radius: 24px; box-shadow: 0 4px 16px rgba(33,31,26,0.06), 0 1px 2px rgba(33,31,26,0.04);
  • Active/dragging (swipe card): box-shadow: 0 12px 28px rgba(33,31,26,0.14); — lifts further, like picking it up.
  • Floating mini-elements (notifications, badges-on-badges, like the Trove reference): smaller radius (16px), lighter shadow, same warm tint.
  • Large radius (20–28px) everywhere — no sharp corners; it's core to the "chill, exploratory" feel.

Glass/blur panel style was explored and dropped for MVP — sticking with the single solid warm-white card elevation everywhere, including nav bars and overlays.

Motion (implied, for later)

Swipe cards should spring, not snap — slight overshoot on release, matching the playful tone. Match celebration = brief gradient-orb pulse, not a modal interrupt.

↑ scroll to read the full spec

3

Built a component-level prototype

First pass as individual React components — swipe deck, itinerary timeline, stop detail — to work out the data model and interaction logic in isolation.

Swipe deck
Hurricane Ridge
Outdoorsy
Itinerary timeline
9:40 AM
Leave home
10:35 AM
Hurricane Ridge
1:15 PM
Bramble & Co
Stop detail
Bramble & Co
Hours11:00–21:00
Rating★★★★☆

Reconstructed from the original React components for this write-up — not literal screenshots of the app.

4

Merged it into one living prototype

Consolidated everything into a single interactive build covering the full flow — onboarding through check-in, swiping, itinerary, stop detail, and lock-in — so I could test the experience as a whole, not as disconnected screens.

5

Iterated in continuous conversation

From there, every design decision — a color, a line break, a font pairing, an interaction bug, an accessibility fix — happened directly on the running prototype, in dialogue with Claude, dozens of rounds, each one verified before moving to the next.

Key decisions & tradeoffs

What I chose, what I gave up, and what I got wrong first.

Design decision

Plans don't need to be "locked" to be seen

My first version required users to explicitly "lock in" a plan before it appeared in their itinerary — modeled loosely on a checkout flow. In practice that added a step with no payoff: the plan already exists the moment you've swiped on enough stops.

I redefined the model so any in-progress plan shows up immediately and stays editable. "Locking" became purely about finalizing a plan for sharing — it switches the view to read-only (no delete, no edit, invite becomes share) rather than gating visibility in the first place.

Tradeoff

Multiple dates meant a switcher, not a list

Once plans didn't require locking to exist, people could reasonably have two unfinished plans for two different weekend dates at once. Rather than surfacing every plan in a scrolling list (simple, but noisy for the 90% case of one active plan), I added a small dropdown next to the date header, with the closest unlocked plan shown by default.

The tradeoff: multi-plan support is one tap deeper than a flat list would be. I chose that because the common case — one plan — stays completely uncluttered.

How I worked with AI

Not "AI wrote my app" — a design partner that could also build.

The most valuable shift wasn't speed. It was working directly on something real instead of a representation of something real.

I never handed off a static mockup and waited for it to become interactive. Every design call — a spacing value, a font pairing, a color, an interaction — was made directly against a running prototype, so I was always evaluating the actual thing, not a stand-in for it. That collapsed a loop that's normally split across a design tool, a handoff doc, and an engineer: I could say "this gradient doesn't read" or "the shadow is still the old blue" and see the real fix in the same conversation.

I owned judgment. AI owned implementation and rigor.

I made the calls on what felt right, what was confusing, what wasn't accessible, down to "move this 12px down." Claude handled the code, and — critically — went looking for every other instance of a problem I'd only pointed at once, like tracking down all four leftover blue buttons after I'd only flagged one.

Every change was tested, not just eyeballed.

Each round ran through an automated regression suite simulating the full flow — check-in, swipe, itinerary, detail, delete, lock-in — plus syntax and byte-diff checks on the underlying code, before I ever saw the result. Fast iteration didn't mean fragile iteration.

Real debugging, not just styling.

The pointer-capture bug on the maps button wasn't something a prompt like "fix the button" could have solved by luck. It took reasoning through browser event order — the kind of debugging I'd normally need an engineer for.

I stayed in the feedback loop, not the codebase.

As a designer, not an engineer, that meant I could ship something with real state — locked vs. editable plans, multi-date logic, drive-time math — that would normally require a dev partner just to be able to critique properly, let alone finish.

interact with me!