Graduate thesis · 2026

Fresh Greens

A wayfinding app for Black drivers in America, built solo for my grad thesis. Most navigation weighs time and distance. Fresh Greens weighs safety too, and it treats what a driver knows about a road as seriously as the official map data. Six interviews shaped what it looks for.

Fresh Greens cover
Problem
Navigation weighs time and distance, not what a driver knows about a road's safety.
Role
Solo, design and engineering
Timeline
Sep 2025 – Jun 2026
Stack
React Native, Expo, TypeScript, Supabase
Outcome
26+ screens shipped, held to a reserved-color rule

Key moves

  • Ran community safety reports through the same pipeline as OpenStreetMap, DOT-511, OSRM, and SunCalc, weighted the same way.
  • Built the en-route screen around one-thumb reach: turn card, 3D map, and a safety column.
  • Shaped the routing signals from six driver interviews.
The full breakdown ↓

The problem I set out to solve.

The Green Book was a routing system built on community knowledge, because no institutional one existed.

For a Black driver, a route isn't only time and distance. It's whether the roads are lit, whether a town feels safe to stop in, where police tend to sit. Nav apps don't weigh any of that. Fresh Greens brings that kind of knowledge back into the route.

Listening to six drivers.

Six interviews with Black drivers across the Southern US, anonymized in synthesis and led with joy and fear before any product questions. The timeline was tight, so the synthesis stayed lean: I pulled the recurring trends into four routing markers.

Moments of joy and fear have a lasting effect on how Black drivers interpret the spaces they inhabit. They stick.
Thesis · Fresh Greens, 2026
  • Light

    6/6

    People time trips around daylight and read lighting as safety. It came up in every interview.

    • Always leaving in the morning.
    • I wouldn't feel comfortable driving at night.
    • The street lights were sparse.
  • Police presence

    5/6

    Police proximity is a live fear, managed with behaviors taught by family and community.

    • Biggest fear is interacting with police.
    • If the app said there's cops here, we're going around that.
    • Wallet out, phone out, everything visible.
  • Wildlife

    3/6

    Deer at dusk reroute people off certain roads after dark.

    • Deer at night, so I'd avoid those roads once evening hit.
    • Deer-heavy areas.
  • Road conditions

    5/6

    Road size, quality, and flooding change the route people are willing to take.

    • Narrow backroads that can't fit two cars.
    • A lot of places get flooded.
    • I cared about road size and road quality.

Community knowledge

Drivers trust people over institutions. That is why community reports carry real weight in the routing, not a footnote next to the official data.

  • I'd listen to family over the statistic. The powers that be aren't honest.
  • For a new area, I'd ask friends who'd been there.
  • I'd call someone who's already at the spot.

They asked for it, unprompted

  • Show the gradient of light, bright where you are and dark where you arrive. Your projected light coverage.The daylight-graded route.
  • Poor road conditions highlighted along the route.The road-conditions marker.
  • If the app said there's cops here, we're going around that.Police presence in the route score.
The zone-flow storyboard, done by hand. The layered route stroke marking a wildlife zone in the last panel was too dense to read at a glance, so it got simplified into the daylight gradient the app uses now.

My first instinct was to stack every safety layer onto the screen. But the interviews also said driving already takes focus, so I pulled most of it back. The safety toolkit stays hidden until a driver reaches for it.

Turning the interviews into a scoring system.

Community reports and public data share one adapter and one scoring function, with a single audit trail behind both.

Every route is scored on four things the interviews kept raising: light, police presence, wildlife, and road conditions. A community report feeds those same four markers. It's weighted exactly like the data from OpenStreetMap or SunCalc and runs through the same pipeline as everything else. Each score carries its source, so a driver can see whether a segment scored low from the sun angle or from a report.

OpenStreetMaplighting · landuse · parksOSRMroute geometrySunCalcsolar geometryMapbox SearchdestinationsDOT-511state traffic feedsMapbox incidentsdriving-traffic eventsOpen-Meteoweather + visibilityCommunity reportsanon device-UUID · Postgres + RLSAdapter layerTyped contracts. Each source speaks one shape.Scoring layerPure deterministic function.Same inputs → same routing decision. Reproducible. Inspectable.Screen layerRenders the result. Does not invent it.
Eight public data sources feed an adapter, a deterministic scoring layer, then the screen. Community reports are one source, authenticated with an anonymous device UUID and held in Postgres under row-level security, the same data routed to the moderation queue. Scroll the diagram to read it all.

The en-route screen is where it all lands: a turn card, a 3D map that drags with the drive, and a safety column within thumb's reach.

The en-route screen in motion.

Designing for the pulled-over moment.

The interface asks before it assumes, so a driver stays in control under pressure.

Every prompt in a safety moment is set in Libre Franklin Regular, not Bold. The safety modal asks "What's going on?" The share sheet asks "What's the situation?" A driver who just got pulled over doesn't need "REPORT INCIDENT" shouted at them in a heavier weight than their own thoughts.

That came from the interviews too. People said the moments the app matters most call for a companion. So Bold appears only on facts the app is sure of, like ETA and the /emergency countdown.

The Held-Question Rule in situ. /safety opens on the driver's own question.
/pulled-over opens on reassurance in the reserved serif, one of the six emotional moments the app spends it on. Recording has already started, quietly.
The Held-Question in action. "Are you armed?" set in Regular, with three composed options where a Bold prompt would have read as an accusation.
The one exception red is allowed to make: the recording indicator. "You're not alone." is another of the six serif moments, and the trusted contact is one tap away.

The other safety surfaces (/roadside, /unfamiliar, /share-location, /emergency) use the same voice, each a first-class route that works with no signal.

The direction I tried first.

The first pass was a Google Maps feature.

  1. First pass

    At first I imagined Fresh Greens as a feature inside Google Maps, so v1 wore Google's turn banner and map chrome, with my safety controls down the side. That moved fast, and it capped the whole idea at Google Maps with a safety layer bolted on.

  2. The break

    Rebuilding it as its own app is what let the safety signals become the interface. The route preview grades the road by daylight, reads safety at a glance, flags a station you trust, and carries its own glyphs on the warm surfaces.

Choosing type and color.

Warm surfaces and a reserved serif give type and color a job at each phase of a trip.

The onboarding illustrations, drawn by hand. They set the warm, human register the app opens on, before a single safety signal appears.

I swapped iOS's cool grays for five warm surfaces, all built in OKLCH on the brand-green hue, so the whole app shares one tonal source.

Type took three tries. Jost first, then Space Grotesk, then Libre Franklin, which carries the whole hierarchy now. DM Serif Display shows up in exactly six emotional moments, like the emergency reassurance line and the "Thanks for sharing" on /trip-summary. Reserving it for those six is what keeps them landing. Type and color both shift across the session, calm at entry, heightened en-route, resolved at the trip summary.

Color, and the job each one holds

Green carries the work

  • freshgreen#41AD49primary CTA, in-flow links
  • wiltedgreen#326936secondary CTA, headers
  • burntgreen#003F04deep accents

Four reserved safety signals

  • orange#FF9500hazard
  • red#FF3B30alert
  • yellow#FFCC00caution
  • navy#041E49safety affordances

Warm surfaces

  • surfacePage#F4F4EDpage, warm paper
  • surfaceCard#FEFDFBcard surface

Spacing, a 4pt ramp

  • xs4
  • sm8
  • md16
  • lg24
  • xl32
  • xxl48
The tokens themselves, pulled straight from theme/colors.ts and theme/spacing.ts. The spacing scale started implicit and drifted to stragglers at 5, 6, 13, and 18. Making the 4pt ramp explicit is what made that drift easy to catch.

Holding four colors in reserve.

Four colors and the daylight gradient are held to safety signals, with documented carve-outs.

Green carries every button and link. Red, orange, yellow, and navy are reserved for safety signals, each tied to one meaning, so a red dot always points to something specific. Across 26+ screens, the rule holds, with documented carve-outs.

Green carries every CTA, link, and affordance — the only non-reserved color. Four colors are held to safety-signal work; the documented carve-outs are below.

RedAlert
  • Live audio-capture indicator A pulsing dot for the active recording state on /pulled-over.
  • Destructive-action labels Remove, unpublish, and sign-out.
  • Error copy on light Swaps to the darker severityCritical token for AA (~5.6 : 1 vs red’s ~3.5 : 1).
  • iOS red on dark auth The contrast argument inverts, so default red passes there.
OrangeHazard · caution
  • Community-report pin Marks community observations apart from the institutional feeds.
  • Report FAB The same orange — the contribute-back affordance.
  • Route-preview hazard chips Police presence and low-light segments.
YellowCaution
  • General caution teardrops Map hazards and weather advisories.
  • Trusted-station gold star A documented carve-out from the caution role.
NavySafety affordance
  • En-route Shield Safety mode itself; never data state or sync.
  • /emergency SOS disc Kept distinct from the destructive-action red.

And where color is the data:

DaylightRoute grade
  • Daylight polyline Color IS the data — a per-segment daylight score. A solid → dashed → dotted cadence carries it for WCAG 1.4.1.
The reserved palette holding on a real screen: navy for the safety Shield, red on the alert dot, orange on the hazard, a moon glyph carrying the daylight-arrival cue, and green everywhere else.

The daylight gradient is the one non-reserved color the system allows itself. On /route-preview, a sun-to-moon dashed band traces what the light will do along the route.

The daylight indicator, held to one job: telling the driver what light they can expect, from now until arrival.

Where color is the signal, a second channel rides with it. On /report, severity pairs a filled warning glyph with the color, so the cue survives for anyone who can't rely on hue (WCAG 1.4.1).

Severity pairs color with the filled WarningDiamond glyph.

Keeping community reports trustworthy.

The pipeline only works if community reports can be trusted like public data. Bad-faith and mistaken reports have to be caught without falling back to distrusting community data. That's what /moderation is for.

Every report enters a queue with an investigation panel: the source device, prior and nearby reports, and coordination checks for duplicate IPs and devices. Nothing publishes without a human decision, and every publish and unpublish is logged.

Enters

A report joins the queue

Investigation panel

  • Source device
  • Prior reports at the same spot
  • Nearby reports
  • Coordination: duplicate IPs and devices

Human decision

Published or held, every action logged

It's where community reports and public data meet the same review. A planned transparency page will publish the outcomes so the queue is auditable from outside.

What shipped, and what didn't.

The honest split between what's in the build and what's still on the list.

Shipped

  • Equal-weighting routing pipeline across OpenStreetMap, DOT-511, OSRM, SunCalc, and community reports
  • Six-surface safety toolkit, with /pulled-over carrying ACLU-sourced guidance, on-device audio capture, and the Held-Question voice
  • Warm surface ramp and reserved-color discipline holding across 26+ screens and 300+ accessibility attributes
  • /moderation queue with per-report investigation panels, coordination detection for IP and device duplicates, and a hold-to-remove destructive gesture
  • Design system published as a Figma library: 62 variables at 1:1 parity with theme/colors.ts, scoped per token role

Also on the list:

  • Daylight-graded route polyline with a WCAG dash pattern
  • Six-category report picker
  • Four-layer Mapbox fallback chain (Mapbox → OSRM → cache → mock)
  • Two-font Franklin plus reserved DM Serif Display
  • Departure-reminder local notifications
  • Delight layer of arrival and community-confirmation moments
  • Supabase community cloud with anonymous device-UUID auth and Postgres row-level security

Next, v2

  • Moderator-role bootstrap automation (currently a manual SQL insert in prod)
  • Push notifications for new moderator-queue items (local notifications ship today, remote push doesn't yet)
  • Public transparency page for /moderation activity
  • Broader on-device test matrix beyond iPhone

What comes next is mostly about accountability. The transparency page is the one I care about most. It puts the /moderation queue's decisions in public, so the trust the whole system runs on can be checked from outside.

More work