Ultimate — the Stepo revamp design
The merged winner of the 2026-07 design review. Ten screens inresults/ultimate/:feed·journey·step·profile·profile-steps·badges·supporters·search·notifications·signup. This doc is the plan for the Flutter build: what each screen is for, the system behind it, what ships in V1, and the answers to the questions that came up during review.
What it is, in one paragraph
Stepo's UI is built around one mechanic: a step is live for 24 hours, and stepping with it while it's live is how support becomes recognition (Starter · Companion · Celebrator). The design makes that mechanic the loudest read everywhere — live cards glow and count down, past cards stay warm but go quiet, and every badge is presented as a person you stepped with, never a number. The skin is warm-premium: paper white, ink, and a sunset taxonomy inside the brand's orange family.
Lineage (what was merged, from where)
| Element | Source concept |
|---|---|
| On air / Earlier feed structure, countdown rings, badge-named-at-action copy | broadcast-orange |
| Serif-italic journey titles, "Needs you now", people carried on journey cards | alongside |
| Inverted carbon final-step card (re-warmed with rose ripple rings) | pulse-orange |
| Windows-open avatar rail, tiered step-with bursts (puff / rise / settle) | cadence |
| Rule-teaching prompt lines ("Heart, comment or share in the next 9h…") | signal |
| "Who you showed up for / Who shows up for you" framing | waymark-2 / broadcast |
The system
Palette — the sunset taxonomy. All recognition colors are siblings of the brand orange:
| Token | Hex | Means |
|---|---|---|
--live | #FF6B3C | The 24h window, interactivity, Companion (fills, borders, numerals) |
--live-text | #C4441C | Darkened contrast variant — sub-12px text on tinted chips only |
--starter | #EF9400 (marigold) | First steps, Starter recognition |
--celeb | #F0457E (rose) | Final steps, Celebrator recognition, achievement |
--carbon | #1A1512 | Ink; also the inverted "moment" card |
--paper | #F6F4F1 | Canvas |
Rule: one accent per card. A card is marigold OR tangerine OR rose, set by its step type; never mixed.
Type. Archivo (display, numerals, buttons) · Inter (body) · Fraunces italic for the authored voice: journey titles, the verbatim recognition copy ("You're the one"), the stepped with verb in prose lines, and brand taglines (the signup headline's one word). Never UI labels, never chrome. The test: if a human could have said it, it may be serif; if the interface is saying it, it may not.
Craft rules. Cards lift (dual-layer warm shadow), chrome stays flat. Live cards add an accent halo on top of the base shadow. Real photos always; SVG status bars; numbers agree across screens.
The signature moment. While live, Heart · Comment · Share light up in the card's accent and each shows a count (three equal peers). Tapping one fires a tiered particle burst: regular = radial puff, first step = rises up, final step = falls & settles (confetti logic). After 24h all three go flat monochrome — counts stay, color leaves.
Past content — "dim the mechanics, not the memories." Past steps remain full cards: vivid photo, full note, faces, all counts. Only the interaction chrome goes quiet (flat buttons, "From earlier" tag, no glow, no countdown). Photos are never desaturated — a grayed photo reads as broken, and the step is still someone's proud moment.
The screens — one dominant job each
Feed — "Who needs me now?"
On air zone first: live cards with countdown rings beside the actions and prompt lines that sell the badge at the moment of action ("Step with her first step and you become a Starter"). The final-step card is the celebration: inverted carbon with rose ripple rings. Then Earlier: full-but-quiet past cards, infinitely scrollable — the feed must feel alive even when zero windows are open (pre-launch reality). The "While you were away" digest is the only place steps collapse into rows — it's a catch-up affordance, not an archive.
Profile — "Who did I support / who supports me?"
The two door tiles are the heroes: Badges Earned ("Who you showed up for") and Support Received ("Who shows up for you") — faces land before numbers. Below, journey cards carry a media filmstrip of recent steps (steps are photos; show them — the live step gets the tangerine ring + hours pill, the final step gets rose) plus the people line ("Maya and 39 others stepped with you here"). The "your step is on air" strip closes the creator's own loop.
Journey — "Who walked this with me?"
The screen recognition was built for. Hero (authored serif title, owner, meta), then — if the finish is live — one banner that carries everything about now: countdown ring, Celebrator prompt, your own progress line with the verbatim voice ("You're the one — don't miss the final step!"), and the lit action buttons as the only CTA. Then The people of this journey: Companions (with tier names), Starters (facepile — "You" tagged if you're one), Celebrators ("31 so far" while the window is open). Then the step trail: first step (marigold node) → steps → final step (rose node, elevated).
Badges — "Who did I show up for?"
Opens on a person, mirroring Supporters: "You've stepped with Priya 14 times — more than anyone," with the arithmetic (12 = 3 Companion + 5 Starter + 4 Celebrator) kept quiet below it. Then the screen's action: Needs you now — the one place recognition status converts into the next tap. Then the people list under the density rule.
Supporters — "Who shows up for me?"
Opens on the podium: the one person who shows up most, with a crown and a human sentence. Then the ranked list (highest recognition chip per person, ranked Companion > Starter > Celebrator > Step-with), then the four "by kind" buckets as facepiles.
Step — "The conversation"
The step at top (keeping its live glow and countdown if the window is open), comments below, sticky composer above the tab bar. Comment rules render the spec: top-level comments inside the 24h window carry the "Stepped with" chip; replies indent one level (two max) and never carry it; a reply to a level-2 comment stays level-2 with an @mention. The composer is a conversion surface: "Commenting now counts as stepping with Maya · 9h left."
Profile / Steps tab — "Everything I've posted"
Same profile header; the Steps tab is an Instagram-style 3-column media grid carrying the ultimate's semantics — the live step ringed tangerine with an hours pill, first steps ringed marigold, final steps ringed rose, occasional quiet heart-count overlays. This is the media grid that answers the old calendar view (see Q&A below).
Search — "Find people and journeys"
People results lead with the relationship, not popularity ("You stepped with her 8 times", "Follows you"); journey results are authored serif titles with owner + step count. Private accounts show Requested state; private journeys don't appear (footer says so). No trending, no hashtags.
Notifications — "What happened while I was away"
New (tinted, dotted) vs Earlier (flat), per the SeenAt/ReadAt model. Rows are step-withs, comments, follows, follow-requests (Accept/Decline inline), and the emotional peaks — recognition rows ("You became a Celebrator on…", "Priya became a Die-Hard Fan on your…") with their taxonomy chip. Batched actors get a face-stack row.
Sign-up — "The first impression"
The product sells itself: miniature feed-card fragments (a live card, a stepped-with face-pile, a Journey-achieved chip) floating on the paper gradient, then the promise ("Every step deserves someone cheering") and the auth block — Apple / Google / Facebook / email OTP, no passwords. Matches the shipped auth stack.
Why Badges and Supporters diverge on purpose: they mirror the data but not the emotion. Received support is a gift → the page celebrates (podium, gratitude). Given support is agency → the page activates ("Needs you now"). Both open on a face; they part ways at what you're supposed to feel.
The density rule (Badges / Supporters / Journey lists): dense in people, light in encoding. Every row = face + name + context + ONE chip + ONE number + chevron. Starter /celebrator breakdowns, per-journey detail, and progress bars live one tap deep — that detail view is ~80% of the journey screen's supporter panel, built once.
Questions answered during review
Why a trail on the journey screen instead of the current calendar / grid? A journey is a story, not a schedule. The trail keeps the two recognition anchors visible (first step = where Starters were made, final step = where Celebrators are made) and shows who joined where — a calendar can't carry any of that. The deeper reason: a date-grid visually rewards daily regularity, which is streak-thinking — and Stepo explicitly has no streaks. A month view full of empty days punishes exactly the user Stepo exists to encourage. A media grid view mode (Instagram-style, for 30+-step journeys) is compatible and scoped V2; a calendar is not recommended in any version — if a time-based surface is ever wanted, reframe it as a "monthly recap" (what happened), never a grid of days (what's missing).
The mirror-door formula (one number per door, same math both ways). Both doors are badge sums, per the spec: Badges Earned = badges you earned on others' journeys (12 = 3 Companion + 5 Starter + 4 Celebrator); Support Received = badges others earned on yours (19 = 3 + 9 + 7). Raw step-with tallies ("87 given", "29 received") are captions, never door numbers — step-with is the currency, badges are what it buys, and the doors count what was bought.
Where do journey/step counts live on the profile? On the segmented tabs ("Journeys · 3" / "Steps · 41") — inventory counts belong on the inventory. The big numerals stay reserved for the two doors (people-stats outrank content-stats in the hierarchy).
Navigation contract. Five-slot bottom bar, Threads-style: Feed · Search · [Step] · Activity · Profile. Search and Activity are destinations, not drill-ins — no back arrows. The top app bar carries zero icons in V1 (wordmark only on Feed; gear on own Profile; back arrows on true drill-ins: journey, step, badges, supporters). The unread badge lives as a dot on the Activity tab's bell.
Tab-active rule: each of the four tabs lights on its own screen; Profile stays lit on its drill-ins (badges, supporters); content drill-ins (journey, step) light no tab — they're reachable from anywhere, so claiming a tab would lie about where you are.
Where does chat go when it exists? The single top-right icon on Feed only — the Instagram DM pattern, landing in space that's deliberately empty in V1 (so adding it is purely additive: no reflow, no re-learning). Not a tab: a chat tab would compete with Feed as "where conversations happen" and break one-job-per-screen. Chat is V2; no dead icon shipped until then.
Why full past cards instead of the collapsed timeline broadcast-orange had? Cold start: for months most sessions will have 0–3 live windows; if past steps collapse, the feed is empty paper. And per the spec, late interactions are still valuable — a step is still tappable, heartable, commentable after its window. Collapsed rows survive only as the digest.
Scope
V1 — ships
- Feed: On air / Earlier zones, live cards (3 step types incl. carbon finale), countdown
rings, prompt lines, tiered bursts, full-quiet past cards
- Profile: doors, followers bar, on-air strip, journey cards (filmstrip + people line)
- Journey: hero, live-finish banner w/ inline personal progress, people panel, step trail
- Badges: person-first summary, Needs you now, people list
- Supporters: podium, ranked list, by-kind buckets
- Step detail: comments with Stepped-with chips, two-level replies, sticky composer
- Profile Steps tab: media grid with semantic rings
- Search: people + journeys, privacy-gated
- Notifications: New/Earlier, recognition rows, follow-request actions
- Sign-up: Apple / Google / Facebook / email OTP
- Verbatim gamification voice everywhere it appears
V2 — visible in mocks, deliberately deferred
| Item | Where | Why deferred |
|---|---|---|
| Windows-open avatar rail | Feed top | Redundant with On air cards until users follow enough people that live cards outnumber the viewport; earns its place at scale |
| "While you were away" digest | Feed | Needs session-gap detection + volume to matter |
| Weekly pulse strip ("Maya, Ray and 2 others drove 21 step-withs this week") | Supporters | Needs aggregation infra; nice-to-have on top of the podium |
| Companion-progress card (standalone) | Badges | Its job is covered by "Needs you now" + row chevrons in V1 |
| Media grid view toggle | Journey | Valuable for long journeys; trail is enough at launch sizes |
| Discover feed tab | Feed | Follows the 70/30 spec but needs content volume |
| Chat / DMs | Feed's top-right (empty in V1) | Whole feature is V2; the slot is reserved and additive |
| Step-content search | Search | Spec supports it; people + journeys carry V1, step results need density thinking |
Explicitly out (any version)
Streaks, day-counts, "N more to unlock" counters, global ranks/leaderboards, calendar day-grids. Recognition is per-journey; the voice is the shipped copy in recognition_overview.md ("One day or day one" → "Almost there!" → "You're the one" → "What a friend!").
Demo data is a state catalog
The mocks deliberately show many states at once so one screen documents them all: the feed holds all three live step types plus past cards; the journey is an Achievement whose final window is still open; profile shows both an ongoing live journey and a finished one. Numbers are consistent across screens (12/19 doors, Marco's 341/58/19, 5h/6h/9h windows) — if a number disagrees between screens it's a bug, but coexisting states are intentional. Don't "fix" them, and don't treat any single screen as the only state.
Building new ultimate screens (agent checklist)
Future screens (create-step, create-journey, onboarding, chat, …) join this set by following the recipe that built the existing ten:
1. feed.html is the source of truth. Copy its :root verbatim (all tokens incl. --live-text), device frame, SVG status bar (never emoji), fonts. Pure CSS — no Tailwind. 2. One dominant job per screen. Write the job as a question ("Who needs me now?") before designing; everything that doesn't serve it is V2 or cut. One CTA per screen. 3. Sunset taxonomy, one accent per card/row/tile. Marigold = first/Starter, tangerine = live/Companion, rose = final/Celebrator. --live-text for sub-12px chip text. Serif = authored voice only (the "could a human have said it" test). 4. People before numbers. Faces land first; big numerals only for the two door stats; counts elsewhere are captions. Density rule: dense in people, light in encoding — one chip + one number per row, detail one tap deep. 5. The 24h window is the loudest read wherever steps appear: live = accent + ring + badge-named-at-action prompt; past = full card, vivid media, quiet chrome (never grayscale). Bursts are tiered: puff / rise / settle. 6. Spec-check before inventing against StepoBackend/docs/project/*_overview.md. Verbatim gamification voice only. No streaks, day-counts, unlock counters, global ranks. Real vocabulary: Heart · Comment · Share, step with, Starter/Celebrator/ Companion (+ its four tier names). 7. Numbers must reconcile with the existing state catalog (12/19 doors, Marco's guitar journey 14 steps · 341/58/19 · 5h left, Chau's cast of people). Reuse the cast; don't mint contradicting data. 8. Wire the nav per the contract (5-slot bar, active-tab rule, drill-ins get back arrows) and update the gallery (scripts/build-gallery.py FEATURE_BLURBS + ORDER), then re-run it. 9. Verify before done: tag balance, every href resolves, no banned patterns, real photos everywhere.
For the Flutter port
- Motion: burst particles (tiered), live-pill blink, and card entrance rise are
prototyped in the HTML — those timings are the spec. Countdown rings are static state, not animation: fill fraction = hours-left / 24 (the stroke-dashoffset math in the SVGs); they update as time passes, never visibly sweep.
- The 8.5–10px micro-labels in the mocks need a legibility pass (≥11px) and testing with
Vietnamese diacritics.
- Dark theme: not designed yet; the carbon "moment" card is the natural seed for it.
- Accent halos on live cards =
box-shadowcomposition (soft ring + colored glow + base
shadow) — see .hero in feed.html.