Skip to content
Builds
Native Lab experiment, designed and built solo

A globe that shows who's awake

Your people on a globe lit by the real Sun: scrub the next day to see who is awake, or tap once for a time that suits everyone.

React Native Skia · Reanimated · Gesture Handler · Expo UI (SwiftUI) · Expo Haptics · Expo Image

1Demo

  • 12:41 PM in London, and everyone is awake

Scripted simulator take: the finger is drawn, the clock is pinned, and the people are samples with generated faces. Not yet tested on an iPhone.

2Flow

  1. 1Spin the globeYoudrag it with a thumb; a flick keeps it turning
  2. 2Lit by the real SunAppbright day side, city lights at night, each person pinned with their local time
  3. 3Scrub the next 24 hoursYoudrag the ruler under the needle; a haptic tick each hour
  4. 4See who is awakeAppthe light sweeps across the dots; a green ring means awake
  5. 5Find a time for everyoneAppone tap: the dial runs to the middle of the first window when all of you are awake, then a success haptic

3How it fits

System
UI threadJS threadlat0, lon0offsetrun to bestpress,successeach hourtime, viewlat0,lon0every 15 minuniformseveryframeGlobe dragGesture.Pan + withDecayTime dialtime-dial.tsxFind a timebestTime in people.tsShared valueslat0 · lon0 · offsetHapticsfeedback.tsSun and viewsunAt in sky.tsGreat-circle arcsgreatCircle + project insky.tsReadoutreadoutAt, each quarterhourGlobe shaderglobe-shader.ts +land.tsArcs and pinsSkia paths · AnimatedviewsClock and facesNumericText (SwiftUI)
The thumb and the dial move three shared values; everything on screen is derived from them.

4Code

  float z = sqrt(1.0 - r2);
  float x = d.x;
  float y = -d.y;
  float y1 = y * cos(lat0) + z * sin(lat0);
  float z1 = -y * sin(lat0) + z * cos(lat0);
  float lat = asin(clamp(y1, -1.0, 1.0)) / DEG;
  float lon = (lon0 + atan(x, z1)) / DEG;
  lon = lon - 360.0 * floor((lon + 180.0) / 360.0);

  // Snap to the nearest dot: rows every \`spacing\` degrees, each row as many dots as fit around it.
  float row = floor(lat / spacing + 0.5);
  float latc = row * spacing;
  float count = max(1.0, floor(360.0 * cos(latc * DEG) / spacing));
  float lonStep = 360.0 / count;
  float col = floor((lon + 180.0) / lonStep + 0.5);
  float lonc = col * lonStep - 180.0;
  float dlat = lat - latc;
  float dlon = (lon - lonc) * cos(lat * DEG);
  float dist = length(float2(dlat, dlon));

  // Antialias across about one pixel, which spans more degrees toward the edge.
  float aa = 57.3 / (radius * max(z, 0.12));
  float dotRadius = spacing * 0.31;
  float inDot = smoothstep(dotRadius + aa * 0.6, dotRadius - aa * 0.6, dist);

  float2 m = float2((lonc + 180.0) / 360.0 * maskSize.x, (90.0 - latc) / 180.0 * maskSize.y);
  float isLand = land.eval(m).a;

  float3 P = toEarth(latc * DEG, lonc * DEG);
  float sunDot = dot(P, sun);
  float day = smoothstep(-0.06, 0.14, sunDot);
  float dusk = smoothstep(0.18, 0.0, abs(sunDot + 0.02));

  float3 dayLand = float3(0.93, 0.94, 0.96);
  float3 nightLand = float3(0.27, 0.27, 0.3);
  float3 col3 = mix(nightLand, dayLand, day);
  col3 = mix(col3, float3(1.0, 0.72, 0.5), dusk * 0.35);
  // City lights: a scatter of land dots glow warm on the night side.
  float city = step(0.86, hash(float2(row, col))) * (1.0 - day);
  col3 = mix(col3, float3(1.0, 0.74, 0.38) * 1.25, city);

Each pixel snaps to its nearest dot, measured on the sphere, so rows thin toward the poles and edge dots squash on their own.

5Decisions

  • One SkSL shader for every dot over a Skia circle per dot

    each pixel finds its nearest dot on the sphere, so dots foreshorten at the edge and light up one by one with no extra work

  • The Sun's position from the date and UTC time over a fixed day and night split

    the terminator moves as you scrub, and the formula is good to about a degree, finer than the dots

  • Fixed UTC offsets over a time zone database

    the time maths stays a plain addition the UI thread can do every frame (no daylight saving yet)

  • The middle of the first shared window over its first quarter hour

    nobody gets called at the very edge of their day

More builds

  • Dragging a bottle of Chianti off the receipt onto Everyone

    Split the bill by dragging

    Drag each item off the receipt onto whoever had it, and their share rolls up, service included.

    Native Lab experiment, designed and built soloDemo · Flow · System · Code

    24 Oct 2026

  • Buddy cheering the last task with confetti

    A mascot rigged in code, not animated

    A to-do list mascot who watches your finger, giggles when poked, flies when flung, leans when you tilt the phone and cheers your last task.

    Native Lab experiment, designed and built soloDemo · Flow · System · Code

    23 Oct 2026

Meer's mascot, waving a peace sign

Have an app in mind?

Tell me what you're building. Replies within 24h. It's in Chittagong.

Booking new projects for Q4

Free 30-minute call

Talk through your app with me. You leave with a plan and a fixed price.

Video callShown in your time zone in Chittagong