FindMyETH · brand language

Two tones, one light source

The app icon splits the teal pin along one soft curve: a lighter tone where light falls from the upper left, the brand teal below. This page sticks to what FindMyETH already does and asks how it could look and move more boldly: the app screens as they are, five daring restyles, new ways to show the stats, and feature-by-feature alternatives for panels, buttons, chips and motion. No new features, all live, all made for touch.

light #4E95B0 base #337691 dark light #8DB8CA dark base #70A0B5
Light · shipped
Dark
Tinted

The rule

Every brand surface gets two tones of the same teal. The lighter tone always sits upper left, divided by the same gentle curve as the icon. Status colours (green, amber, red) never get the split, so meaning stays clear. In the app this is one token: tintLight next to tint, already added in light, dark and Marauder.

In the app today

The app, screen by screen

Eleven screens of features FindMyETH has today, drawn with the two-tone rule, in the order you would meet them. The tags under each screen name the features it shows. Screens follow your light or dark setting.

New · five bold restyles

The same app, five more daring looks

Each phone restyles screens that already exist in FindMyETH. No new features, only new motion, panels, buttons and type. All of them are live and made for touch: tap, drag, swipe and hold.

Statistics · reimagined

Twelve ways to show the stats we already have

Every design uses numbers the app already computes: hours on campus this week (yours and together), hours per day, the leaderboard, On campus now, the four profile tiles, the streak, the swipe result. Same mock data as the phones. All of them play when they scroll into view; tap or swipe to replay.

23.5h this week23.5 hof Maya's 31.2 h3/4on campus now6 dayscampus streak

Activity rings

Tap to replay

Your hours, who is on campus now and the streak as three closing rings, with the hour count rolling up in the middle.

Gauge(value:).gaugeStyle(.accessoryCircular) or Circle().trim(from: 0, to: v) · .animation(.spring.delay(i * 0.15)) · contentTransition(.numericText())
23.5
2
AYou
31.2
1
MMaya
21.0
3
NNoah
17.4
4
SSam

Rising podium

Tap to replay

The weekly leaderboard as a podium. Bars rise one after the other, numbers count up, the leader gets the pin as a crown and your bar is outlined.

ForEach bars · scaleEffect(y:, anchor: .bottom) with .spring.delay(rank * 0.12) · PinLogo crown with .symbolEffect(.bounce)
Maya
M31.2 h
You
A23.5 h
Noah
N21.0 h
Sam
S17.4 h

Race lanes

Tap to rerun

Leaderboard rows become running lanes. Everyone starts at zero and runs to their hours this week; the finish line is the top member.

LeaderboardRow with an avatar offset(x: width * hours / maxHours) · .animation(.easeOut(duration: 1.2).delay(i * 0.08))
6day streak
MTWTFSS

Tap a day. Five weekdays in a row last week, plus today.

Streak flame + campus days

Tap a day

The streak tile grows into a calendar of campus days, darker for longer days. The current streak is outlined and the flame flickers while it is alive.

Charts RectangleMark(x: weekday, y: week) with foregroundStyle(by: hours) · flame as a two-path Shape with PhaseAnimator flicker
h on campus this week

Odometer

Tap You / Together

The big hours number as a mechanical counter. Each digit rolls on its own drum, so switching between your hours and the group total feels physical.

Text(value, format: .number.precision(.fractionLength(1))).contentTransition(.numericText(value:)) · monospacedDigit · withAnimation(.snappy)
23.5 hLongest day · Wednesday 7.5 h

Bars that grow

Tap a bar

Hours per day from the profile stats, but each bar springs up in two tones when it scrolls into view. The longest day keeps a light cap; tap any day to read it.

Charts BarMark(x: day, y: hours) · .chartOverlay tap → selection · foregroundStyle LinearGradient tintLight→tint · animate a 0→1 scale on appear
MTWTFSS23.5 hthis week

Radial week

Tap a petal

The same hours per day as a flower: one petal per weekday, longer for longer days. Petals unfold clockwise from Monday; tap one to read it in the centre.

Charts SectorMark(angle: .value("Day", 1), outerRadius: .value("Hours", h), innerRadius: .ratio(0.25)) · chartAngleSelection
Maya31.2 hYou23.5 hNoah21.0 hSam17.4 h

92.1 h together this week. Tap a bubble.

Avatar bubbles

Tap a bubble

Each member is a bubble in their avatar colour, sized by hours. They drift slightly, like a group standing together; tapping one pops it forward.

Canvas or ZStack of Circles sized by sqrt(hours), positions from a simple circle pack · .scaleEffect on selection · TimelineView drift
Most visitedCAB G 61room
Longest dayWed · 7.5 hthis week
Streak6 campus daysin a row
BuildingHauptgebäudeHG, most hours

Swipeable stat cards

Swipe sideways

The four profile tiles (most visited, longest day, streak, building) as big two-tone cards in a snapping carousel, one number at a time.

ScrollView(.horizontal) { LazyHStack } · .scrollTargetBehavior(.viewAligned) · .scrollTransition { scaleEffect / opacity by phase }
Mensa Polyterrasse4/4 like it
MM B · 3 min
food&lab3/4 like it
CAB H · 4 min
Tannenbar2/4 like it
ML E · 5 min

Vote race

Tap to replay

The swipe result as a race: each canteen's likes fill in, the winner crosses 4/4 first and a burst of small pins goes off.

BarMark(x: likes, y: canteen) with .animation per option · PhaseAnimator for the burst · sensoryFeedback(.success) on the winner
This week Alex walked the castle for23½ hourssix days in a row

Marauder ink

Plays by itself

In Marauder's Map mode the hours stat is written in ink: the number draws itself stroke by stroke and the streak walks past as six footprints.

Text with .font(.custom("IMFellEnglish-Italic")) masked by a trim-animated path, or a TextRenderer (iOS 18) that reveals glyphs over time
3/4
on campus now · Lunch crew
MSJN
Noah arrived · CAB

Arrivals counter

Plays by itself

The On campus now tile as a live counter: when a friend arrives, their avatar slides in, the number rolls up and a small toast says where.

contentTransition(.numericText()) on the count · avatars in an HStack with .transition(.move(edge: .trailing).combined(with: .opacity))
Feature by feature

Wilder looks for what the app already does

Every block below is a feature that exists in FindMyETH today. Each card is one alternative look or motion for it, live and made for touch. The one-line "Today" note says how it works now.

Launch, loading, refresh, empty

Today: the pin drops in on launch, a light sweep while loading, the pin stretches on pull to refresh, empty screens show the pin.

FindMyETH

Ink-stamp launch

Tap to replay

The pin slams down like a rubber stamp, two rings ripple out on the two haptic beats, then the wordmark slides out from under it.

PhaseAnimator([.up, .slam, .settle]) · scaleEffect 1.6→0.92→1 · two Circle strokes scaling out · sensoryFeedback(.impact) ×2

Liquid loader

Plays by itself

Instead of a sweep, the pin fills up with two tones of teal like a glass of water, the waves sliding past each other until the content is ready.

Shape with a sine path, phase from TimelineView(.animation) · clipped to PinLogo · fill level = progress
Pull down here

Slingshot refresh

Drag down

Pulling stretches the pin like a rubber band. Let go past the line and it shoots up, spins once, and lands with a flash of light.

.refreshable with a custom indicator · scaleEffect(y: 1 + pull / 120) · on release: offset(y: -40) + rotationEffect(360°) spring
zzNo lectures on Friday. Tap to wake the pin.

Sleepy empty state

Tap

The empty-state pin dozes: it leans, breathes, and floats little z's. Tapping wakes it with a hop, a cheap moment of delight on an empty day.

EmptyState(PinLogo) · rotationEffect + scaleEffect breathing (3 s) · .onTapGesture → keyframeAnimator hop

Map markers

Today: a two-tone you-are-here pin with a pulse and a light cone, friend avatars with clusters, brand pins for places.

Sonar you-are-here

Plays by itself

Three soft sonar rings instead of one pulse, and the light cone slowly sweeps a little to each side, as if the pin were looking around.

MeMarker: three staggered Circle strokes (scale 1→2.4, opacity 1→0) · LightCone rotation ± 12° with .easeInOut.repeatForever
NJLN+2Noah, Jonas, Lea · CAB

Cluster bloom

Tap

A cluster of friends opens like a flower: the +2 bubble splits into the three avatars on a spring, each with a live ring, and folds back on a second tap.

FriendMapMarker: ForEach(members) offset on a circle, .spring(response: 0.4, dampingFraction: 0.6) · matchedGeometryEffect from the lead avatar

Pin rain

Tap a pin

When a filter changes, the brand pins fall in one after another and squash on landing. The selected pin grows and slides a name tag out from under it.

Annotation { BrandPin } · .transition(.offset(y: -60).combined(with: .opacity)) with a stagger · selected: scaleEffect(1.3, anchor: .bottom) + label .move(edge: .top)

Search bar and filter chips

Today: a floating search bar with voice search, and Food / Study places / Friends chips that light up in two tones.

Liquid chip

Tap a chip

One highlight for the whole row. On tap it first stretches across to the new chip, then snaps to its size, like a drop of liquid moving.

matchedGeometryEffect(id: "chip", in: ns) on a Capsule behind the selected chip · two-step: width anchor, then .spring(bounce: 0.35)
Search rooms, food, friends
HG F 33.1PolyterrasseMaya

Search that listens

Tap

Tapping the mic grows the bar into a wide glass panel; while listening a live waveform replaces the placeholder and recent searches slide up underneath.

VoiceSearch: matchedGeometryEffect bar → panel · waveform bars driven by the audio level (AVAudioRecorder.averagePower) · .symbolEffect(.variableColor)

Pin section markers

Plays by itself

Section titles in the list panel get a tiny two-tone pin as a bullet and a hairline that runs to the edge.

Label { Text(title) } icon: { PinLogo().frame(width: 14) } · Rectangle().frame(height: 0.5)

List panel and detail cards

Today: a draggable list panel with detents over the map, and detail cards with photo, facts and Guide me there.

Magnetic sheet

Drag the handle

The panel snaps to three detents with a rubber band past the ends. Each detent clicks in with a little overshoot, and the label says where it landed.

presentationDetents([.height(150), .medium, .large]) or DragGesture + snap · .interactiveSpring(response: 0.35, dampingFraction: 0.7) · sensoryFeedback(.selection)
Mensa PolyterrasseOpen until 19:30 · 1 minGuide me there

Card from the pin

Tap

The detail card grows out of the pin you tapped instead of sliding up from the bottom, so you always see where it came from.

matchedGeometryEffect(id: place.id) between BrandPin and the card · .transition(.scale(scale: 0.1, anchor: pinAnchor))

Buttons

Today: round flat-teal buttons that darken and shrink on press, and Guide me there with a centred arrow.

Lamp press

Plays by itself

The primary button carries the light tone in its upper left; on press the light grows across it like a lamp switching on.

ButtonStyle: tint + tintLight Ellipse clipped to the Capsule, scale on isPressed with .spring

Hold to guide

Press and hold

For the route button: light pours into the capsule while you hold, and when it is full the arrow takes off. Short taps still work for VoiceOver.

onLongPressGesture(minimumDuration: 0.6, perform:, onPressingChanged:) → animate a fill width · arrow offset + rotation keyframes

Launching arrow

Tap

On tap the arrow shoots off to the upper right with a short trail and comes back in from the left, a tiny send-off before the route opens.

keyframeAnimator(trigger:) { offset(x,y), opacity, scale } on Image(systemName: "location.fill")

Route sheet and floors

Today: a route sheet with steps, rolling minutes and metres, a simulated walk, step-free, and a floor pill with indoor floor plans.

Flowing route

Plays by itself

A deep teal line with a lighter dashed core that flows toward the goal, so the direction reads without arrows.

Two MapPolylines: tint (11 pt) + tintLight (5 pt, dashed, dashPhase from TimelineView)

Split-flap ETA

Plays by itself

The rolling minutes and metres become departure-board flaps: each digit folds down on its own when it changes.

contentTransition(.numericText(countsDown: true)) + rotation3DEffect(.degrees(flip), axis: (1, 0, 0), anchor: .center) per digit

Bead timeline

Plays by itself

Steps as a vertical line: a two-tone bead slides down it as you walk and finished steps tick off and fade.

VStack of steps over a Capsule track · bead offset(y:) from progress · .symbolEffect(.bounce) on the check

Elevator floor pill

Tap up / down

The floor pill as an elevator display: the letter rolls on a drum and the floor plan next to it slides up or down with it.

ScrollView with .scrollPosition(id:) and .scrollTargetBehavior(.paging) for the drum · plan .transition(.push(from: .bottom / .top))

Socials

Today: My Friends with group cards and a Contacts roll-out, a friend detail with Hungry? and Start swiping, the swipe deck and the result.

Contacts, 6SMNJLM

Contacts fan-out

Tap

The Contacts row deals its people out like a hand of cards, each avatar landing a beat after the last; tap again to gather them back.

DisclosureGroup content with ForEach avatars · .transition(.offset(x: -40 * i).combined(with: .scale)) · .spring.delay(i * 0.05)

Group aura

Plays by itself

Group cards get a soft light corner whose strength follows how many members are on campus now, fading as people leave.

LinearGradient hard stop at 38 %, opacity = onCampus / members · animates when the count changes

Light follows the swipe

Plays by itself

Dragging a dish toward yes brightens the card with the light tone; toward no it dims.

Overlay tintLight gradient, opacity = max(0, drag.width) / 150 · black overlay for left swipes

Coin-flip result

Plays by itself

When everyone has swiped, the pin turns over like a coin and lands on the winning canteen.

rotation3DEffect(.degrees(180), axis: (0, 1, 0)) spring, swap content at 90°

Top-pick ribbon

Plays by itself

The winning card's header carries the two-tone curve with the voters' avatars, the light edge slowly drifting.

ZStack { tint; tintLight clipped to Path(curve) } · voters in an overlapping HStack

Profile and avatar

Today: Edit Profile with Name and Avatar tabs, initials, photo or sticker avatar, the avatar creator and a full-screen photo viewer.

Stretchy tab thumb

Tap

The Name / Avatar switch uses a two-tone thumb that stretches toward the new tab before it settles, so the switch feels like it has weight.

Picker(.segmented) replaced by a custom HStack · thumb matchedGeometryEffect + scaleEffect(x: 1.3) mid-flight

Avatar part carousel

Tap Hair / Outfit

In the avatar creator each tap on a part pops the sticker: the changed part springs, the colour cross-fades and a tiny shine runs over it.

AvatarComposer layer with .contentTransition(.interpolate) colour · scaleEffect pop via keyframeAnimator · sensoryFeedback(.selection)
Alex M.

Photo zoom

Tap

Tapping the profile photo zooms it to fill the screen from where it sits, with the background dimming behind; tap again and it flies back.

PhotoViewer: matchedGeometryEffect thumb ↔ full · .zIndex + background opacity · drag down to dismiss with scale

Tilt-reactive hero

Plays by itself

Tilting the phone shifts the light edge on the profile hero, as if a real lamp hung above it.

CMMotionManager attitude → tintLight path offset ± 3 pt + rotation3DEffect

Themes, tab bar, Marauder mode

Today: light, dark and system appearance, the tab bar, the hidden Marauder's Map mode, two-tone tokens, haptics.

Theme light wipe

Plays by itself

Switching appearance wipes the new look in from the upper-left corner, where the light comes from.

Snapshot overlay masked by a growing Circle anchored top-leading, 0.9 s

Tab icon hop

Plays by itself

The active tab icon fills with both tones and hops once; inactive icons stay grey.

.symbolRenderingMode(.palette).foregroundStyle(tint, tintLight) · .symbolEffect(.bounce, value: tab)
Navigate
Marauder's Map
tap anywhere: ink spreads from your finger

Ink spill toggle

Tap anywhere

Entering Marauder mode spills ink from exactly where you touched; leaving it soaks the ink back up into the same spot.

mask { Circle().frame(width: r * 2).position(touch) } with withAnimation(.easeOut(duration: 0.8)) · touch from SpatialTapGesture

Two-beat haptic, shown

Plays by itself

A light tap then a firmer one, like the two tones: for landing, route start and the match.

CHHapticEngine: transient 0.4 at 0 s, 0.9 at 0.09 s

Lock screen

Today: a route Live Activity with Dynamic Island, and a Next lecture widget.

4 min
Stairs up to floor GCAB · east staircase
tap the island

Island that breathes

Tap

The compact island keeps the pin and minutes; tapping it swells into the next step with a two-tone progress worm that crawls forward.

ActivityKit DynamicIsland { expanded / compactLeading / compactTrailing } · ProgressView with a LinearGradient tint → tintLight

Countdown widget

Plays by itself

The Next lecture widget gets a ring that drains toward leave time, and the minutes roll down each minute.

WidgetKit accessoryRectangular · Gauge(value:) circular · Text(timerInterval:countsDown:) for live minutes
Guardrails

Keep it a system, not decoration

Always

  • Light tone upper left, same curve direction everywhere.
  • Only brand and action surfaces get the split.
  • ETH letters and text stay crisp and flat.

Never

  • Split green, amber or red status elements.
  • Use more than one animated two-tone element per screen.
  • Add a third teal tone; two is the system.

Build order

  • Now: lamp-press buttons, liquid chips, flowing route, split-flap ETA (all small).
  • Then: growing stat bars, rising podium, coin-flip result, magnetic sheet.
  • If time: one restyle (R2 Journey is the cheapest), ink spill, fanned floors.