You are browsing as a guest. Sign up (or log in) to start making projects!

darshjain

@darshjain

Joined June 6th, 2026

  • 22Devlogs
  • 6Projects
  • 2Ships
  • 30Votes
High school nerd interested in shipping software
Open comments for this post

2h 29m 23s logged

starwick - meet vesp

second devlog. last time starwick was an empty glowing void. now it’s got soft twinkling stars, a proper wispy nebula, and a companion floating next to you humming its own little tune. starting to feel less like a screensaver and more like a place.

the cosmos got real

the stars used to be blocky squares (default particle, no texture). swapped them for soft round points i generate in code - a radial gradient sprite - plus a twinkle layer that fades stars in and out so the field shimmers. bumped the bloom so the bright ones actually glow.

the nebula was the fight. first pass was a few clean soft blobs, looked like lens flares. so i made it noise-based - fractal perlin clouds with a soft falloff - and overshot the other way, came out dark and basically invisible lol. third try: dropped the noise threshold and roughly doubled the color and alpha, and now it’s vivid purple-and-blue wisps sweeping across the whole sky. took three goes to land that one.

vesp

the companion. a small violet mote that orbits near you, bobs around, and plays a soft synthesized motif - a little bell arpeggio - that layers over the ambient drone. no samples, the melody’s built from sine waves like everything else.

first attempt vesp came out as a giant white sun, lol. i’d picked a bright purple but cranked the hdr way too high, so between that and the bloom the core blew out to pure white and it hogged the whole frame like a second star. dialed the color back, shrank it, pushed it further out, and rebuilt it from a hard sphere into a cluster of soft glowing dots. now it reads like a little spirit drifting with you instead of a death star.

where it stands

empty void -> twinkling stars + vivid nebula -> a companion that floats beside you and sings. all still procedural, no art or audio files anywhere.

next up is the actual game verb: tracing constellations to relight stars, and being able to reach out and interact with vesp.

starwick - meet vesp

second devlog. last time starwick was an empty glowing void. now it’s got soft twinkling stars, a proper wispy nebula, and a companion floating next to you humming its own little tune. starting to feel less like a screensaver and more like a place.

the cosmos got real

the stars used to be blocky squares (default particle, no texture). swapped them for soft round points i generate in code - a radial gradient sprite - plus a twinkle layer that fades stars in and out so the field shimmers. bumped the bloom so the bright ones actually glow.

the nebula was the fight. first pass was a few clean soft blobs, looked like lens flares. so i made it noise-based - fractal perlin clouds with a soft falloff - and overshot the other way, came out dark and basically invisible lol. third try: dropped the noise threshold and roughly doubled the color and alpha, and now it’s vivid purple-and-blue wisps sweeping across the whole sky. took three goes to land that one.

vesp

the companion. a small violet mote that orbits near you, bobs around, and plays a soft synthesized motif - a little bell arpeggio - that layers over the ambient drone. no samples, the melody’s built from sine waves like everything else.

first attempt vesp came out as a giant white sun, lol. i’d picked a bright purple but cranked the hdr way too high, so between that and the bloom the core blew out to pure white and it hogged the whole frame like a second star. dialed the color back, shrank it, pushed it further out, and rebuilt it from a hard sphere into a cluster of soft glowing dots. now it reads like a little spirit drifting with you instead of a death star.

where it stands

empty void -> twinkling stars + vivid nebula -> a companion that floats beside you and sings. all still procedural, no art or audio files anywhere.

next up is the actual game verb: tracing constellations to relight stars, and being able to reach out and interact with vesp.

Replying to @darshjain

0
1
Open comments for this post

2h 12m 58s logged

Starwick - a cosmos

first devlog. starwick’s a procedural cosmic adventure i’m building

what’s in it so far

right now it’s just the bootable cosmos. no art, no audio files, nothing downloaded, it’s all generated in code. 700 stars on a sphere around the camera, bloom and vignette, and an ambient drone synthesized at runtime (few detuned sines + a slow lfo).

Starwick - a cosmos

first devlog. starwick’s a procedural cosmic adventure i’m building

what’s in it so far

right now it’s just the bootable cosmos. no art, no audio files, nothing downloaded, it’s all generated in code. 700 stars on a sphere around the camera, bloom and vignette, and an ambient drone synthesized at runtime (few detuned sines + a slow lfo).

Replying to @darshjain

0
1
Ship

Wanderpin is a 3D-globe trip planner where the journey is the product, not the spreadsheet. Drop pins on a spinning night-earth, watch your route stitch together, then hit Play and fly the whole trip as a cinematic reel - gliding across the globe for long hops and dropping into real 3D terrain when two stops are close.

The hardest part was making the globe-to-3D handoff feel continuous. Two heavy engines (three.js for the globe, MapLibre for terrain) had to crossfade without a tile-load flash, so I warm the 3D view in the background and only commit once it's actually ready. I also caught the play-through reel re-rendering the entire app - globe included - on every caption tick; moving the caption into its own useSyncExternalStore so only it updates made the fly-through visibly smoother.

I'm proud that it feels like a journey instead of a form: the cinematic reel, ~40k curated places that each carry a vibe and a fun fact, and one-tap sharing as a postcard, a public link, or an embeddable live globe.

To try it: open the live link, search a city or just click the globe to drop pins (or start from a template like India's Golden Triangle), then hit Play. Zoom all the way into a pin to watch it cross into street-level 3D. Built with React 19 + TypeScript, three.js, MapLibre GL, and Vercel serverless.

  • 9 devlogs
  • 40h
Try project → See source code →
Open comments for this post

3h 56m 5s logged

Wanderpin - the first five seconds, final touchups

Everything in this batch is about the moment someone lands: a blank globe is intimidating, so I built a real first-run, made it personal, and fixed a jank I’d been ignoring.

A front door instead of an empty globe

New visitors get a welcome dialog: Plan a trip, or Remember one you took. Either way there are starter templates (a week in Italy, around the world, a California road trip) you can drop in with one tap and start dragging stops around.

Coachmarks that follow the actual UI

A short guided tour spotlights real elements: search, the view toggle, Play. It measures each target’s live bounding rect, re-measures on resize and focus, flips above/below by screen half, and hides itself the moment you focus the search box so it never fights you while typing. Dismissals persist so it doesn’t nag on return visits.

Where it stands

Build’s green, all staged, not yet committed. Next: watch real first sessions, do people tap through a template or skip straight to search?

Wanderpin - the first five seconds, final touchups

Everything in this batch is about the moment someone lands: a blank globe is intimidating, so I built a real first-run, made it personal, and fixed a jank I’d been ignoring.

A front door instead of an empty globe

New visitors get a welcome dialog: Plan a trip, or Remember one you took. Either way there are starter templates (a week in Italy, around the world, a California road trip) you can drop in with one tap and start dragging stops around.

Coachmarks that follow the actual UI

A short guided tour spotlights real elements: search, the view toggle, Play. It measures each target’s live bounding rect, re-measures on resize and focus, flips above/below by screen half, and hides itself the moment you focus the search box so it never fights you while typing. Dismissals persist so it doesn’t nag on return visits.

Where it stands

Build’s green, all staged, not yet committed. Next: watch real first sessions, do people tap through a template or skip straight to search?

Replying to @darshjain

0
1
Open comments for this post

4h 1m 2s logged

Wanderpin - getting routes to leave the ground

Live: https://wanderpin-ecru.vercel.app/

One update, mostly one stubborn visual I’d twice given up on: real lifted route arcs on the 3D map.

Arcs that finally hug the globe

deck.gl’s ArcLayer drifts on a MapLibre globe (it only knows Web Mercator), and my earlier hand-rolled layer compiled, ran, threw zero errors, and painted nothing. The fix came from reading MapLibre’s own shaders: inject its projection prelude and call projectTileFor3D(pos, elevation).

The thing I’d missed every time - elevation is in meters above the sphere, fed through five per-frame uniforms, not a raw matrix. Then the polish fights: a z-fight speckle (fixed by not writing depth), a 50 km minimum lift that made short hops launch like fireworks, and a fat vertical wall at city zoom (now fades out past ~zoom 10).

An optional day timeline

New “Days” view in the trip panel. Weekend trip? You’ll never see it. Month-long? Hit “Organize into days” and stops group into collapsible days you can split, reorder, fit the globe to, or play one at a time. A day is a contiguous slice of the route, so “move to Day 3” repositions the stop and re-normalizes - the route and itinerary stay honest. Days travel on share links too.

Where it stands

Live at site, (Also squashed a deploy bug: one .tsx API route made Vercel type-check the whole api/ folder with no tsconfig - a tiny api/tsconfig.json fixed it.)

Wanderpin - getting routes to leave the ground

Live: https://wanderpin-ecru.vercel.app/

One update, mostly one stubborn visual I’d twice given up on: real lifted route arcs on the 3D map.

Arcs that finally hug the globe

deck.gl’s ArcLayer drifts on a MapLibre globe (it only knows Web Mercator), and my earlier hand-rolled layer compiled, ran, threw zero errors, and painted nothing. The fix came from reading MapLibre’s own shaders: inject its projection prelude and call projectTileFor3D(pos, elevation).

The thing I’d missed every time - elevation is in meters above the sphere, fed through five per-frame uniforms, not a raw matrix. Then the polish fights: a z-fight speckle (fixed by not writing depth), a 50 km minimum lift that made short hops launch like fireworks, and a fat vertical wall at city zoom (now fades out past ~zoom 10).

An optional day timeline

New “Days” view in the trip panel. Weekend trip? You’ll never see it. Month-long? Hit “Organize into days” and stops group into collapsible days you can split, reorder, fit the globe to, or play one at a time. A day is a contiguous slice of the route, so “move to Day 3” repositions the stop and re-normalizes - the route and itinerary stay honest. Days travel on share links too.

Where it stands

Live at site, (Also squashed a deploy bug: one .tsx API route made Vercel type-check the whole api/ folder with no tsconfig - a tiny api/tsconfig.json fixed it.)

Replying to @darshjain

0
3
Open comments for this post

5h 0m 49s logged

Wanderpin - the globe looked dumb on a 20 km hop, so I taught it to zoom

This batch turns a Wanderpin trip from a thing on your screen into a thing you can send to a friend - with a real link preview, an embeddable globe, and a reel that finally looks good on short hops.

Share a journey, for real

Hit Share and you get a public link (/t/slug) in a box, plus Copy, the native share sheet, and WhatsApp / Instagram / X buttons. The interesting part is what happens when someone opens that link:

  • /t/:slug is server-rendered (api/share.ts) and serves a dynamically generated Open Graph image (api/og.tsx via @vercel/og) - so pasting a Wanderpin link into a chat unfurls into a proper preview card instead of a blank rectangle.
  • There’s an embeddable globe too: /embed/:slug renders just the spinning globe (a tiny route check in main.tsx swaps the whole app for EmbedView), and the share dialog hands you the <iframe> snippet.
  • Shared trips auto-expire after 7 days via a pg_cron job, so the table stays small and old links quietly die.

The reel got smarter - 3D for the short hops

The globe fly-through looks flat when two stops are 20 km apart - the arc is a stub and the camera barely moves. So now the Play reel checks each leg: anything under 200 km crosses into the 3D map (terrain + buildings) with a zoom picked from the distance, longer legs stay as globe arcs. It asks once before playing, and video recording stays globe-only so captures never go blank.

What bit me

  • Instagram has no web “share a link” intent (unlike WhatsApp/X). So the IG button copies the link and opens Instagram - the native share sheet handles it properly on phones.
  • The slug kept vanishing from the URL. A shared trip was calling replaceState("/") the instant it loaded, so /t/slug disappeared mid-playback and you couldn’t re-share what you were watching. Moved that reset onto the “Create yourslongs.
  • The reel switch reused the globe↔3D handoff plumbing, and the type-checker caught a flyTo signature mismatch before it shipped - the map handle wants { zoom }, not a bare number.

Where it stands

It all builds clean, Try it: https://wanderpin-ecru.vercel.app/

Wanderpin - the globe looked dumb on a 20 km hop, so I taught it to zoom

This batch turns a Wanderpin trip from a thing on your screen into a thing you can send to a friend - with a real link preview, an embeddable globe, and a reel that finally looks good on short hops.

Share a journey, for real

Hit Share and you get a public link (/t/slug) in a box, plus Copy, the native share sheet, and WhatsApp / Instagram / X buttons. The interesting part is what happens when someone opens that link:

  • /t/:slug is server-rendered (api/share.ts) and serves a dynamically generated Open Graph image (api/og.tsx via @vercel/og) - so pasting a Wanderpin link into a chat unfurls into a proper preview card instead of a blank rectangle.
  • There’s an embeddable globe too: /embed/:slug renders just the spinning globe (a tiny route check in main.tsx swaps the whole app for EmbedView), and the share dialog hands you the <iframe> snippet.
  • Shared trips auto-expire after 7 days via a pg_cron job, so the table stays small and old links quietly die.

The reel got smarter - 3D for the short hops

The globe fly-through looks flat when two stops are 20 km apart - the arc is a stub and the camera barely moves. So now the Play reel checks each leg: anything under 200 km crosses into the 3D map (terrain + buildings) with a zoom picked from the distance, longer legs stay as globe arcs. It asks once before playing, and video recording stays globe-only so captures never go blank.

What bit me

  • Instagram has no web “share a link” intent (unlike WhatsApp/X). So the IG button copies the link and opens Instagram - the native share sheet handles it properly on phones.
  • The slug kept vanishing from the URL. A shared trip was calling replaceState("/") the instant it loaded, so /t/slug disappeared mid-playback and you couldn’t re-share what you were watching. Moved that reset onto the “Create yourslongs.
  • The reel switch reused the globe↔3D handoff plumbing, and the type-checker caught a flyTo signature mismatch before it shipped - the map handle wants { zoom }, not a bare number.

Where it stands

It all builds clean, Try it: https://wanderpin-ecru.vercel.app/

Replying to @darshjain

0
1
Open comments for this post

6h 9m 57s logged

Wanderpin - zoom into the world

Two big things landed this week: the map went from 26 hand-picked places to ~40,000, and zooming into the globe now drops you into a real 3D city.

From 26 places to 40,000 - and they all have a vibe

Wanderpin used to know about 26 famous spots. Now it knows ~40k: cities, towns, UNESCO sites, landmarks, and the world’s most-visited attractions - each tagged with a one-word vibe (Coastal, Alpine, Arid, Heritage, Sacred, Volcanic…).

The fun part is the vibes aren’t hand-typed. They’re derived at build time from real data: a Köppen climate raster (so Cairo reads “Arid” and Athens “Mediterranean”), distance to an actual coastline for “Coastal”, GeoNames elevation/feature codes for peaks and castles, and Wikidata’s annual-visitor numbers to rank “most visited”. Facts come from Wikipedia and UNESCO descriptions. Search is now instant and offline - type “Firenze” and Florence shows up.

The fun part: zoom in and the globe becomes a city

The home view is a space globe (three.js). The close-up is a different engine entirely - maplibre with 3D terrain and buildings. I wired up a level-of-detail handoff between them: zoom into a pin and it crosses from globe to 3D automatically, and zoom back out and it returns to the globe.

The trick to making it not feel janky: when you start zooming in, it quietly mounts the 3D map hidden and lets it pull tiles in the background. Only once the tiles are warm does it crossfade. No blank loading flash.

What broke (twice)

  1. It hung forever in preload. The crossfade waited on maplibre’s idle event to know tiles were ready - but that event sometimes never fires. The globe just kept zooming into black. Fix: commit after a 2.2s fallback even if idle is silent.
  2. The way back never worked. Zooming out in 3D wouldn’t return to the globe. Turned out map.on("zoom") captured the callback once at mount - back when the view was still “globe” - so its if (view !== "3d") return guard killed every event forever. Fix: call the latest callback through a ref.

Both are real bugs that would’ve bitten actual users on slow connections, not just test artifacts.

Where it stands

Globe -> 3D -> globe round-trips cleanly on both laptop and mobile, with zero console errors. Try it: https://wanderpin-ecru.vercel.app/

Wanderpin - zoom into the world

Two big things landed this week: the map went from 26 hand-picked places to ~40,000, and zooming into the globe now drops you into a real 3D city.

From 26 places to 40,000 - and they all have a vibe

Wanderpin used to know about 26 famous spots. Now it knows ~40k: cities, towns, UNESCO sites, landmarks, and the world’s most-visited attractions - each tagged with a one-word vibe (Coastal, Alpine, Arid, Heritage, Sacred, Volcanic…).

The fun part is the vibes aren’t hand-typed. They’re derived at build time from real data: a Köppen climate raster (so Cairo reads “Arid” and Athens “Mediterranean”), distance to an actual coastline for “Coastal”, GeoNames elevation/feature codes for peaks and castles, and Wikidata’s annual-visitor numbers to rank “most visited”. Facts come from Wikipedia and UNESCO descriptions. Search is now instant and offline - type “Firenze” and Florence shows up.

The fun part: zoom in and the globe becomes a city

The home view is a space globe (three.js). The close-up is a different engine entirely - maplibre with 3D terrain and buildings. I wired up a level-of-detail handoff between them: zoom into a pin and it crosses from globe to 3D automatically, and zoom back out and it returns to the globe.

The trick to making it not feel janky: when you start zooming in, it quietly mounts the 3D map hidden and lets it pull tiles in the background. Only once the tiles are warm does it crossfade. No blank loading flash.

What broke (twice)

  1. It hung forever in preload. The crossfade waited on maplibre’s idle event to know tiles were ready - but that event sometimes never fires. The globe just kept zooming into black. Fix: commit after a 2.2s fallback even if idle is silent.
  2. The way back never worked. Zooming out in 3D wouldn’t return to the globe. Turned out map.on("zoom") captured the callback once at mount - back when the view was still “globe” - so its if (view !== "3d") return guard killed every event forever. Fix: call the latest callback through a ref.

Both are real bugs that would’ve bitten actual users on slow connections, not just test artifacts.

Where it stands

Globe -> 3D -> globe round-trips cleanly on both laptop and mobile, with zero console errors. Try it: https://wanderpin-ecru.vercel.app/

Replying to @darshjain

0
1
Open comments for this post

3h 18m 50s logged

Wanderpin - Blog 5 - Update press play, watch your trip turn into a movie

Live: https://wanderpin-ecru.vercel.app/

You drop a few pins on a globe and hit Play - and instead of a checklist, you get a cinematic fly-through you can actually share. This update was about chasing one idea until the app felt alive: the journey is the hero, the logistics hide in a drawer.

The payoff

Play used to just stop. Now it lands: the camera pulls back to frame the route, the arcs light up, and a date-stamped card counts up your distance, stops, countries and time. A little classifier scores the trip and picks the camera - a city hop zooms in, a round-the-world trip shows the marble. You also pick how you travel each leg (walk/train/flight…), which bends both the arc shape and the trip’s duration.

Then three swings: export the fly-through as a video (recorded straight off the globe canvas, no server), “been there” pins that glow gold once visited, and a living globe - a warm daylight terminator plus each stop’s local time and current weather.

What broke

Geocode 500’d in prod, every call. Not a geocoding bug at all: type: "module" means Vercel runs the API as native ESM, and Node demands file extensions on imports. from "./_nominatim" crashed at load. One .js fixed it.

The terminator looked gloomy. Darkening the night side just made an already-night map muddy. I flipped it - glow the sunlit side instead - and it finally read as “half the globe is lit.”

Two suns. Every clear-sky stop showed ☀️ 06:28 · ☀️ 15° - my day marker next to the weather glyph.

ngl used AI to build this image 😛

Wanderpin - Blog 5 - Update press play, watch your trip turn into a movie

Live: https://wanderpin-ecru.vercel.app/

You drop a few pins on a globe and hit Play - and instead of a checklist, you get a cinematic fly-through you can actually share. This update was about chasing one idea until the app felt alive: the journey is the hero, the logistics hide in a drawer.

The payoff

Play used to just stop. Now it lands: the camera pulls back to frame the route, the arcs light up, and a date-stamped card counts up your distance, stops, countries and time. A little classifier scores the trip and picks the camera - a city hop zooms in, a round-the-world trip shows the marble. You also pick how you travel each leg (walk/train/flight…), which bends both the arc shape and the trip’s duration.

Then three swings: export the fly-through as a video (recorded straight off the globe canvas, no server), “been there” pins that glow gold once visited, and a living globe - a warm daylight terminator plus each stop’s local time and current weather.

What broke

Geocode 500’d in prod, every call. Not a geocoding bug at all: type: "module" means Vercel runs the API as native ESM, and Node demands file extensions on imports. from "./_nominatim" crashed at load. One .js fixed it.

The terminator looked gloomy. Darkening the night side just made an already-night map muddy. I flipped it - glow the sunlit side instead - and it finally read as “half the globe is lit.”

Two suns. Every clear-sky stop showed ☀️ 06:28 · ☀️ 15° - my day marker next to the weather glyph.

ngl used AI to build this image 😛

Replying to @darshjain

0
16
Open comments for this post

4h 33m 56s logged

Wanderpin - I deleted the spreadsheet

You open the app or you can :) (check it out - https://wanderpin-ecru.vercel.app/ ). A night-side Earth, city lights glowing, a slow spin. You drop a pin in Paris. Another in Tokyo. The arc draws across the globe, and right next to all of that, a panel quietly tells you your dream weighs 2.2 tonnes of CO₂.

That panel was killing the magic. This phase was about fixing it.

The shift

I stopped showing the numbers on the dream surface. Same numbers, just hidden one click deep behind a “Trip details” disclosure. In their place: one warm line that grows with the trip - “4 stops · 4 countries · An adventure taking shape.” You build a journey. The app reacts like it’s excited about it.

Surprise me, the most fun thing the app could do, was buried under a dice icon. It’s the front door now: Take me somewhere. The empty state stopped being homework - “Your map’s a blank canvas, click to start” - and became an invitation: “Where are you dreaming of?”

Drop your very first pin and the app whispers back: “Your journey begins in Santorini ✨”. Hover over a stop and a small card appears: “Why go - its iconic caldera is the rim of a colossal volcanic eruption.” Hit Play and the screen goes cinematic. Full-screen overlay, place name, vibe, progress dots, the route lighting up beneath.

The trip used to be a spreadsheet. Now it’s a memory you haven’t made yet.

The mobile catch

Almost shipped a bug that would’ve made this look broken. On a phone, the empty-state “Take me somewhere” lives inside a bottom sheet. Tap it, and the surprise card was rendering behind the still-open sheet. To the user, nothing happened. To me, a testing pass on an iPhone-shaped viewport caught it the morning of. One-line fix. The kind that disappears in the diff but would have left every mobile user thinking the button was broken.

Where it stands

Empty state, surprise card, first pin, the reel - all warm now. The numbers are still there for the planners, behind one small toggle. The dreamers don’t see them at all.

When Play ends, the camera pulls back, the whole route lights up, and the same numbers I just buried come surging back as a celebration - “You crossed 4 countries. Today, that’s a journey.” Same data, different feeling. The closing frame becomes a postcard you can share.

The whole phase is one belief: people don’t open a travel app because they want to know how many kilometres a trip is. They open it because they want, for a minute, to imagine going somewhere. So show them the somewhere.

Wanderpin - I deleted the spreadsheet

You open the app or you can :) (check it out - https://wanderpin-ecru.vercel.app/ ). A night-side Earth, city lights glowing, a slow spin. You drop a pin in Paris. Another in Tokyo. The arc draws across the globe, and right next to all of that, a panel quietly tells you your dream weighs 2.2 tonnes of CO₂.

That panel was killing the magic. This phase was about fixing it.

The shift

I stopped showing the numbers on the dream surface. Same numbers, just hidden one click deep behind a “Trip details” disclosure. In their place: one warm line that grows with the trip - “4 stops · 4 countries · An adventure taking shape.” You build a journey. The app reacts like it’s excited about it.

Surprise me, the most fun thing the app could do, was buried under a dice icon. It’s the front door now: Take me somewhere. The empty state stopped being homework - “Your map’s a blank canvas, click to start” - and became an invitation: “Where are you dreaming of?”

Drop your very first pin and the app whispers back: “Your journey begins in Santorini ✨”. Hover over a stop and a small card appears: “Why go - its iconic caldera is the rim of a colossal volcanic eruption.” Hit Play and the screen goes cinematic. Full-screen overlay, place name, vibe, progress dots, the route lighting up beneath.

The trip used to be a spreadsheet. Now it’s a memory you haven’t made yet.

The mobile catch

Almost shipped a bug that would’ve made this look broken. On a phone, the empty-state “Take me somewhere” lives inside a bottom sheet. Tap it, and the surprise card was rendering behind the still-open sheet. To the user, nothing happened. To me, a testing pass on an iPhone-shaped viewport caught it the morning of. One-line fix. The kind that disappears in the diff but would have left every mobile user thinking the button was broken.

Where it stands

Empty state, surprise card, first pin, the reel - all warm now. The numbers are still there for the planners, behind one small toggle. The dreamers don’t see them at all.

When Play ends, the camera pulls back, the whole route lights up, and the same numbers I just buried come surging back as a celebration - “You crossed 4 countries. Today, that’s a journey.” Same data, different feeling. The closing frame becomes a postcard you can share.

The whole phase is one belief: people don’t open a travel app because they want to know how many kilometres a trip is. They open it because they want, for a minute, to imagine going somewhere. So show them the somewhere.

Replying to @darshjain

0
12
Open comments for this post

5h 20m 45s logged

Wanderpin - phase 3: real sharing, and the bug that ate an afternoon

This update gave Wanderpin proper short links, a saved-trips library, and a route optimizer. But the part I’ll remember is a 100 KB mystery.

Short links + a trip library

Sharing used to cram the whole trip into a giant #... URL. Now there’s a tiny Supabase backend: hit Share and you get a clean …/t/aB3xY9 link that anyone can open, no login. Reads come straight from the browser (a public read policy), and writes go through a serverless function with the secret key, so nobody can scribble on the database from the client. When someone opens a shared link, the route flies stop to stop and a little “Make your own” card invites them to start their own.

Click polish

Spinning the globe was dropping stray pins, because react-globe.gl fires a click even at the end of a drag. Added a pointer-distance guard (under ~5px = a real click). Clicking open ocean now offers an “Add anyway” instead of a useless pin, and there’s an “Optimize route” button that reorders stops with nearest-neighbour + 2-opt to cut total distance.

The bug: a 5-stop trip that was 400 KB

Share kept failing in the browser with a 413 (payload too large). But the function worked fine when I hit it with curl. Same 5-stop trip, totally different size.

I had the user dump their saved trip in the console. Five stops. 403,682 characters. Each stop had two mystery keys: __threeObjPoint and __threeObjLabel, ~100 KB each.

Turns out react-globe.gl mutates the data objects you hand it - it bolts the entire Three.js render object (meshes, geometry, materials) straight onto your data. I was passing my live trip array to the globe, so it was quietly fattening every stop by 100 KB, which then got saved to localStorage and sent on publish. The hash link looked fine only because it re-packs specific fields, so it never saw the junk.

Fix was two-fold: hand the globe throwaway copies so it scribbles on those instead of my real data, and sanitize trips to known fields on save/load (which also cleans up the bloated localStorage on next visit). Back to ~1 KB per trip.

Where it stands

Live at https://wanderpin-ecru.vercel.app/ - build a trip, optimize it, save it, share a short link, watch it fly.

Honest caveats: shared trips are snapshots (open a link, get a copy - not live collaboration), and the globe’s three.js bundle is still the heavy chunk (lazy-loaded, so it only hits the 3D view). Lesson logged: never trust a viz library not to mutate your state.

Wanderpin - phase 3: real sharing, and the bug that ate an afternoon

This update gave Wanderpin proper short links, a saved-trips library, and a route optimizer. But the part I’ll remember is a 100 KB mystery.

Short links + a trip library

Sharing used to cram the whole trip into a giant #... URL. Now there’s a tiny Supabase backend: hit Share and you get a clean …/t/aB3xY9 link that anyone can open, no login. Reads come straight from the browser (a public read policy), and writes go through a serverless function with the secret key, so nobody can scribble on the database from the client. When someone opens a shared link, the route flies stop to stop and a little “Make your own” card invites them to start their own.

Click polish

Spinning the globe was dropping stray pins, because react-globe.gl fires a click even at the end of a drag. Added a pointer-distance guard (under ~5px = a real click). Clicking open ocean now offers an “Add anyway” instead of a useless pin, and there’s an “Optimize route” button that reorders stops with nearest-neighbour + 2-opt to cut total distance.

The bug: a 5-stop trip that was 400 KB

Share kept failing in the browser with a 413 (payload too large). But the function worked fine when I hit it with curl. Same 5-stop trip, totally different size.

I had the user dump their saved trip in the console. Five stops. 403,682 characters. Each stop had two mystery keys: __threeObjPoint and __threeObjLabel, ~100 KB each.

Turns out react-globe.gl mutates the data objects you hand it - it bolts the entire Three.js render object (meshes, geometry, materials) straight onto your data. I was passing my live trip array to the globe, so it was quietly fattening every stop by 100 KB, which then got saved to localStorage and sent on publish. The hash link looked fine only because it re-packs specific fields, so it never saw the junk.

Fix was two-fold: hand the globe throwaway copies so it scribbles on those instead of my real data, and sanitize trips to known fields on save/load (which also cleans up the bloated localStorage on next visit). Back to ~1 KB per trip.

Where it stands

Live at https://wanderpin-ecru.vercel.app/ - build a trip, optimize it, save it, share a short link, watch it fly.

Honest caveats: shared trips are snapshots (open a link, get a copy - not live collaboration), and the globe’s three.js bundle is still the heavy chunk (lazy-loaded, so it only hits the 3D view). Lesson logged: never trust a viz library not to mutate your state.

Replying to @darshjain

0
102
Open comments for this post

3h 15m 9s logged

Wanderpin - phase 2: trips you can shape, share, and watch fly

Phase 1 let you drop pins on a globe. Phase 2 turns that into an actual trip planner: reorder stops, edit them, share a link, and hit Play to watch the camera fly your whole route.

Play: the globe earns its keep

The feature I’m happiest with. There’s now a Play button that flies the camera stop to stop along your route, advancing the highlighted pin as it goes. On the 3D globe it looks the way I wanted the whole app to feel.

The nice surprise: it was almost free. Back in phase 1 both the globe and the flat map exposed the same tiny flyTo handle. So the tour loop doesn’t know or care which view you’re on

Editing, sharing, exporting

  • Reorder by dragging (dnd-kit), with the route redrawing live. The drag handle is the only draggable bit, so dragging never fights tapping a stop to edit it.
  • Edit a stop inline — rename, swap the emoji, add a note. Undo on remove and “clear all” via the toast, because I deleted my own trip twice testing it.
  • Share builds a link with the whole trip compressed into the URL (lz-string), no backend. Open it anywhere and the trip loads. IDs are regenerated on import so a shared trip never collides with one you already have.
  • Export to JSON or GPX, so a route can open in Google Earth.

Where it stands

Live at https://wanderpin-ecru.vercel.app/. Every phase builds clean (tsc + Vite) and deploys to Vercel on push. The loop now: build a trip, reorder and annotate it, watch it fly, share the link.

Wanderpin - phase 2: trips you can shape, share, and watch fly

Phase 1 let you drop pins on a globe. Phase 2 turns that into an actual trip planner: reorder stops, edit them, share a link, and hit Play to watch the camera fly your whole route.

Play: the globe earns its keep

The feature I’m happiest with. There’s now a Play button that flies the camera stop to stop along your route, advancing the highlighted pin as it goes. On the 3D globe it looks the way I wanted the whole app to feel.

The nice surprise: it was almost free. Back in phase 1 both the globe and the flat map exposed the same tiny flyTo handle. So the tour loop doesn’t know or care which view you’re on

Editing, sharing, exporting

  • Reorder by dragging (dnd-kit), with the route redrawing live. The drag handle is the only draggable bit, so dragging never fights tapping a stop to edit it.
  • Edit a stop inline — rename, swap the emoji, add a note. Undo on remove and “clear all” via the toast, because I deleted my own trip twice testing it.
  • Share builds a link with the whole trip compressed into the URL (lz-string), no backend. Open it anywhere and the trip loads. IDs are regenerated on import so a shared trip never collides with one you already have.
  • Export to JSON or GPX, so a route can open in Google Earth.

Where it stands

Live at https://wanderpin-ecru.vercel.app/. Every phase builds clean (tsc + Vite) and deploys to Vercel on push. The loop now: build a trip, reorder and annotate it, watch it fly, share the link.

Replying to @darshjain

0
4
Open comments for this post

4h 40m 34s logged

Wanderpin - first build

Check it out yourself -> https://wanderpin-ecru.vercel.app/

A little travel app where you drop pins on a spinning 3D globe (or a flat map if you prefer), and it stitches them into a trip with distances and country counts. Here’s where it’s at after the first couple of sessions.

What it does so far

  • Globe + map, same data. A 3D night-earth globe (three.js via react-globe.gl) with glowing pins and animated arcs between stops, and a 2D Leaflet map with a dashed route line. One toggle swaps between them - they render off the same trip array.
  • Search and click to add. Type a city and it geocodes through OpenStreetMap’s Nominatim; or just click anywhere on the map and it reverse-geocodes the spot into a named pin.
  • Surprise me. A pile of hand-picked destinations, each with a fun fact and a vibe, for when you don’t know where you want to go.
  • It remembers. Trip, view, and “is this still the sample” all persist to localStorage. First-time visitors get a seeded Paris -> Rome -> Cairo route so the app isn’t empty on load.

Spent a bit making the Nominatim calls polite: searches are debounced so a fast typer doesn’t fire a request per keystroke.

Where it stands

The core loop works end to end: search or click -> pin lands on globe and map -> trip panel updates distance (haversine) and country count -> reload and it’s all still there. Next up is editing/reordering stops and tidying the mobile layout

Wanderpin - first build

Check it out yourself -> https://wanderpin-ecru.vercel.app/

A little travel app where you drop pins on a spinning 3D globe (or a flat map if you prefer), and it stitches them into a trip with distances and country counts. Here’s where it’s at after the first couple of sessions.

What it does so far

  • Globe + map, same data. A 3D night-earth globe (three.js via react-globe.gl) with glowing pins and animated arcs between stops, and a 2D Leaflet map with a dashed route line. One toggle swaps between them - they render off the same trip array.
  • Search and click to add. Type a city and it geocodes through OpenStreetMap’s Nominatim; or just click anywhere on the map and it reverse-geocodes the spot into a named pin.
  • Surprise me. A pile of hand-picked destinations, each with a fun fact and a vibe, for when you don’t know where you want to go.
  • It remembers. Trip, view, and “is this still the sample” all persist to localStorage. First-time visitors get a seeded Paris -> Rome -> Cairo route so the app isn’t empty on load.

Spent a bit making the Nominatim calls polite: searches are debounced so a fast typer doesn’t fire a request per keystroke.

Where it stands

The core loop works end to end: search or click -> pin lands on globe and map -> trip panel updates distance (haversine) and country count -> reload and it’s all still there. Next up is editing/reordering stops and tidying the mobile layout

Replying to @darshjain

0
4
Open comments for this post

1h 38m 49s logged

DoseGuard - Deployed

Last stretch was about making it real: hardening the app and getting it deployed where people can actually use it.

Made it correct

Shipped it

Deployed to a Hack Club box - FastAPI + Caddy under PM2, React build served static, the 250k-drug DB shipped up from local

The OCR fight

This ate the day. Fresh Ubuntu was missing native libs (libGL, libgomp), then PaddlePaddle crashed on an unimplemented oneDNN op, then segfaulted - turned out the PP-OCRv5 server models just don’t run on this CPU. Swapped to the mobile models and it finally worked: scan a strip → reads the salts -> flags the interaction, ~2s.

It’s live

Scan an “Aspirin + Clopidogrel” box and it correctly flags the interaction, end to end in the browser. Demo login + screenshots in the README.

DoseGuard - Deployed

Last stretch was about making it real: hardening the app and getting it deployed where people can actually use it.

Made it correct

Shipped it

Deployed to a Hack Club box - FastAPI + Caddy under PM2, React build served static, the 250k-drug DB shipped up from local

The OCR fight

This ate the day. Fresh Ubuntu was missing native libs (libGL, libgomp), then PaddlePaddle crashed on an unimplemented oneDNN op, then segfaulted - turned out the PP-OCRv5 server models just don’t run on this CPU. Swapped to the mobile models and it finally worked: scan a strip → reads the salts -> flags the interaction, ~2s.

It’s live

Scan an “Aspirin + Clopidogrel” box and it correctly flags the interaction, end to end in the browser. Demo login + screenshots in the README.

Replying to @darshjain

0
4
Open comments for this post

6h 7m 12s logged

This round was less about new features and more about making the existing loop trustworthy - multi-salt meds, an editable scan history, per-user timezones, and a pile of correctness bugs that were quietly skewing what people saw.

Multi-salt medicines

  • One medicine can now hold multiple active ingredients. “Combiflam” or a combination tablet resolves to all its salts (ibuprofen + paracetamol), not just the first one - so interaction checks actually see everything you’re taking.

Editable scan history

  • New PATCH /scans/{id} endpoint + an “Edit & re-check” modal — fix an OCR misread, add or remove a drug, and re-run the interaction check without re-scanning.
  • Adding a medication from a scan carries its salts through, and duplicate meds are blocked (case-insensitive).

Account + timezone

  • New change-password flow (/auth/change-password) — verifies the current password and signs out other devices.
  • Every user now has a timezone (defaults to Asia/Kolkata, editable on the Profile page). “Today”, the slot cutoffs (morning/afternoon/evening/night), adherence,

Where it stands

  • Full loop still works end to end: scan or search -> save as a multi-salt med -> schedule its course + slots -> track today -> see adherence + real interaction warnings -> and now it’s all timezone-correct and edit-friendly.

Fixing some bugs moving ahead

  • Night doses now actually go overdue (the old cutoff of 24 could never trigger).
  • Adherence only counts doses that were genuinely scheduled -> stray or leftover logs can’t push the percentage past what you were actually due.
  • Deleting a medication now also clears its dose logs, so old “taken” rows stop inflating adherence.
  • A medication is no longer half-created if RxNorm hiccups mid-add -> ingredients resolve before anything is saved.
  • One failed RxNorm lookup during a scan no longer kills the whole scan.
  • Drug search ranks an exact match above a loose substring product, and de-dupes.

This round was less about new features and more about making the existing loop trustworthy - multi-salt meds, an editable scan history, per-user timezones, and a pile of correctness bugs that were quietly skewing what people saw.

Multi-salt medicines

  • One medicine can now hold multiple active ingredients. “Combiflam” or a combination tablet resolves to all its salts (ibuprofen + paracetamol), not just the first one - so interaction checks actually see everything you’re taking.

Editable scan history

  • New PATCH /scans/{id} endpoint + an “Edit & re-check” modal — fix an OCR misread, add or remove a drug, and re-run the interaction check without re-scanning.
  • Adding a medication from a scan carries its salts through, and duplicate meds are blocked (case-insensitive).

Account + timezone

  • New change-password flow (/auth/change-password) — verifies the current password and signs out other devices.
  • Every user now has a timezone (defaults to Asia/Kolkata, editable on the Profile page). “Today”, the slot cutoffs (morning/afternoon/evening/night), adherence,

Where it stands

  • Full loop still works end to end: scan or search -> save as a multi-salt med -> schedule its course + slots -> track today -> see adherence + real interaction warnings -> and now it’s all timezone-correct and edit-friendly.

Fixing some bugs moving ahead

  • Night doses now actually go overdue (the old cutoff of 24 could never trigger).
  • Adherence only counts doses that were genuinely scheduled -> stray or leftover logs can’t push the percentage past what you were actually due.
  • Deleting a medication now also clears its dose logs, so old “taken” rows stop inflating adherence.
  • A medication is no longer half-created if RxNorm hiccups mid-add -> ingredients resolve before anything is saved.
  • One failed RxNorm lookup during a scan no longer kills the whole scan.
  • Drug search ranks an exact match above a loose substring product, and de-dupes.

Replying to @darshjain

0
1
Open comments for this post

4h 17m 44s logged

DoseGuard moved from a tiny hand-curated interaction list to a real, sourced drug-interaction database - plus a searchable medicine catalog and a calendar that shows adherence over time.

Real interaction data

  • Replaced the 8 curated pairs with the DDInter dataset (~150k real drug-drug interactions), graded Major / Moderate / Minor → severe / moderate / low.
  • New normalization + interaction engine so user meds and the dataset speak the same RxNorm vocabulary.

Searchable medicine catalog

  • Imported ~250k medicine products + ~3k drug concepts into searchable tables.
  • New /drugs/search endpoint (fuzzy match) and a debounced DrugSearch autocomplete component.

Medications page

  • Add meds by searching the catalog or importing from a scan (one scan -> one med, salts included).
  • Set a course length (3 days, ongoing, etc.) — adherence only counts days a med is actually active.

Calendar

  • New Calendar page: month grid with per-day dots (all-taken / partial / missed / today), a 2-week tile strip with dates, plus doses-taken, tracked-days, and a 12-week adherence trend.
  • New /tracking/history endpoint feeding day-by-day stats.

Where it stands

  • Full loop working: scan or search a medicine -> save as a multi-salt med -> schedule its course + slots -> track today -> see adherence + real interaction warnings, all backed by sourced data.

Fixing some bugs moving ahead

DoseGuard moved from a tiny hand-curated interaction list to a real, sourced drug-interaction database - plus a searchable medicine catalog and a calendar that shows adherence over time.

Real interaction data

  • Replaced the 8 curated pairs with the DDInter dataset (~150k real drug-drug interactions), graded Major / Moderate / Minor → severe / moderate / low.
  • New normalization + interaction engine so user meds and the dataset speak the same RxNorm vocabulary.

Searchable medicine catalog

  • Imported ~250k medicine products + ~3k drug concepts into searchable tables.
  • New /drugs/search endpoint (fuzzy match) and a debounced DrugSearch autocomplete component.

Medications page

  • Add meds by searching the catalog or importing from a scan (one scan -> one med, salts included).
  • Set a course length (3 days, ongoing, etc.) — adherence only counts days a med is actually active.

Calendar

  • New Calendar page: month grid with per-day dots (all-taken / partial / missed / today), a 2-week tile strip with dates, plus doses-taken, tracked-days, and a 12-week adherence trend.
  • New /tracking/history endpoint feeding day-by-day stats.

Where it stands

  • Full loop working: scan or search a medicine -> save as a multi-salt med -> schedule its course + slots -> track today -> see adherence + real interaction warnings, all backed by sourced data.

Fixing some bugs moving ahead

Replying to @darshjain

0
15
Open comments for this post
Reposted by @darshjain

5h 53m 3s logged

DoseGuard moved past “scan + check” into daily medication management schedule doses, track them, and see adherence alongside safety warnings.

Today page

  • New Today page showing scheduled doses split into morning / afternoon / evening / night.
  • Each dose shows a live status: taken, skipped, overdue, or upcoming.
  • Quick actions to mark a dose taken or skipped.

Tracking + safety together

  • New backend models for dose schedules and dose logs.
  • New API routes: today’s schedule, saved schedules, update med slots, log taken/skipped, 7-day adherence.

UI polish

  • Reworked pages on shared UI components.
  • Animated page transitions, staggered cards, animated stat numbers, cleaner headers.
  • Swapped emoji nav icons for lucide icons

Where it stands

  • Full loop working: scan medicines -> save as meds -> schedule doses -> track today -> see adherence + interaction warnings.

DoseGuard moved past “scan + check” into daily medication management schedule doses, track them, and see adherence alongside safety warnings.

Today page

  • New Today page showing scheduled doses split into morning / afternoon / evening / night.
  • Each dose shows a live status: taken, skipped, overdue, or upcoming.
  • Quick actions to mark a dose taken or skipped.

Tracking + safety together

  • New backend models for dose schedules and dose logs.
  • New API routes: today’s schedule, saved schedules, update med slots, log taken/skipped, 7-day adherence.

UI polish

  • Reworked pages on shared UI components.
  • Animated page transitions, staggered cards, animated stat numbers, cleaner headers.
  • Swapped emoji nav icons for lucide icons

Where it stands

  • Full loop working: scan medicines -> save as meds -> schedule doses -> track today -> see adherence + interaction warnings.

Replying to @darshjain

1
28
Open comments for this post

5h 53m 3s logged

DoseGuard moved past “scan + check” into daily medication management schedule doses, track them, and see adherence alongside safety warnings.

Today page

  • New Today page showing scheduled doses split into morning / afternoon / evening / night.
  • Each dose shows a live status: taken, skipped, overdue, or upcoming.
  • Quick actions to mark a dose taken or skipped.

Tracking + safety together

  • New backend models for dose schedules and dose logs.
  • New API routes: today’s schedule, saved schedules, update med slots, log taken/skipped, 7-day adherence.

UI polish

  • Reworked pages on shared UI components.
  • Animated page transitions, staggered cards, animated stat numbers, cleaner headers.
  • Swapped emoji nav icons for lucide icons

Where it stands

  • Full loop working: scan medicines -> save as meds -> schedule doses -> track today -> see adherence + interaction warnings.

DoseGuard moved past “scan + check” into daily medication management schedule doses, track them, and see adherence alongside safety warnings.

Today page

  • New Today page showing scheduled doses split into morning / afternoon / evening / night.
  • Each dose shows a live status: taken, skipped, overdue, or upcoming.
  • Quick actions to mark a dose taken or skipped.

Tracking + safety together

  • New backend models for dose schedules and dose logs.
  • New API routes: today’s schedule, saved schedules, update med slots, log taken/skipped, 7-day adherence.

UI polish

  • Reworked pages on shared UI components.
  • Animated page transitions, staggered cards, animated stat numbers, cleaner headers.
  • Swapped emoji nav icons for lucide icons

Where it stands

  • Full loop working: scan medicines -> save as meds -> schedule doses -> track today -> see adherence + interaction warnings.

Replying to @darshjain

1
28
Open comments for this post

5h 53m 58s logged

The web app (React + HeroUI) — In progress

  • A modern admin-style dashboard: sidebar nav, top bar, stat cards.
  • Pages for managing medications, scanning, viewing the dashboard, and editing your profile.
  • Scan history table where you can review past scans, edit the extracted drugs, and re-run the interaction check.
  • Camera + upload - take a photo live in the browser or upload one.
  • Made mobile-friendly (the sidebar collapses into a slide-out drawer on phones).

The hard part: reading the label (OCR)

This is where most of the recent effort went, and it’s been a real journey:

  • Started with Tesseract - it choked on real photos (returned garbage like “ry”).
  • Switched to PaddleOCR, then to its best models (PP-OCRv5 server detection + recognition, plus auto-rotation and image-unwarping). This reads real strips far better.
  • Improved the matching: combine adjacent words so multi-word salts (“lactic acid”) stay together, skip packaging words (tablet, mg), and tolerate OCR typos with fuzzy matching.

Where it stands

The full chain works end to end: scan a medicine -> read the salts -> check interactions -> see ranked warnings, with accounts, history, and a polished responsive UI.

The web app (React + HeroUI) — In progress

  • A modern admin-style dashboard: sidebar nav, top bar, stat cards.
  • Pages for managing medications, scanning, viewing the dashboard, and editing your profile.
  • Scan history table where you can review past scans, edit the extracted drugs, and re-run the interaction check.
  • Camera + upload - take a photo live in the browser or upload one.
  • Made mobile-friendly (the sidebar collapses into a slide-out drawer on phones).

The hard part: reading the label (OCR)

This is where most of the recent effort went, and it’s been a real journey:

  • Started with Tesseract - it choked on real photos (returned garbage like “ry”).
  • Switched to PaddleOCR, then to its best models (PP-OCRv5 server detection + recognition, plus auto-rotation and image-unwarping). This reads real strips far better.
  • Improved the matching: combine adjacent words so multi-word salts (“lactic acid”) stay together, skip packaging words (tablet, mg), and tolerate OCR typos with fuzzy matching.

Where it stands

The full chain works end to end: scan a medicine -> read the salts -> check interactions -> see ranked warnings, with accounts, history, and a polished responsive UI.

Replying to @darshjain

0
41
Open comments for this post

3h 39m 19s logged

Before this, the app could check drugs but it had no memory of who you were. So I added proper accounts.

Now the app has real users, remembers their medications, checks them safely, and protects their accounts to a standard I’d actually trust.

Before this, the app could check drugs but it had no memory of who you were. So I added proper accounts.

Now the app has real users, remembers their medications, checks them safely, and protects their accounts to a standard I’d actually trust.

Replying to @darshjain

0
1
Open comments for this post

1h 26m 42s logged

This is the part that actually makes DoseGuard useful - checking whether the medicines you’re on are dangerous together.

Up to now the app could recognise a drug and figure out its ingredient, but it couldn’t say anything about safety. So I built the interaction checker.

When you hand the app a list of medicines, it takes each one, boils it down to its real ingredient (using the part I built earlier), and then checks every possible pair against that database. If it finds a clash, it tells you which two drugs, how dangerous it is, and why.

I started the danger list small and curated by hand, just the well-known serious interactions, so I know it’s accurate.

This is the part that actually makes DoseGuard useful - checking whether the medicines you’re on are dangerous together.

Up to now the app could recognise a drug and figure out its ingredient, but it couldn’t say anything about safety. So I built the interaction checker.

When you hand the app a list of medicines, it takes each one, boils it down to its real ingredient (using the part I built earlier), and then checks every possible pair against that database. If it finds a clash, it tells you which two drugs, how dangerous it is, and why.

I started the danger list small and curated by hand, just the well-known serious interactions, so I know it’s accurate.

Replying to @darshjain

0
2
Loading more…

Followers

Loading…