Stack overview
| Layer | Technology |
|---|---|
| Framework | React 18 + React Router 7 |
| Build tool | Vite 6 |
| Styling | Tailwind CSS 4 + Radix UI primitives |
| Maps | Leaflet + React-Leaflet |
| Auth & database | Supabase (PostgreSQL + Auth + RLS) |
| Animations | Motion (Framer Motion) |
| Transit data | EFA API (efa.sta.bz.it) |
| Hosting | Vercel |
Application layers
Persistence model
localStorage is the primary data store. Every read and write goes throughstorage.ts, which provides typed accessors for all persisted keys.
When a user is authenticated, every write to localStorage also triggers an async upsert to Supabase via syncKeyToCloud(). On login, syncAllFromSupabase() pulls the latest cloud state into localStorage.
This means:
- The app works fully offline without an account
- Cloud sync is additive — it never blocks the UI
- localStorage is always the fast path for reads
Event-driven state
Components do not share React context or a global store for persisted data. Instead,storage.ts dispatches custom window events on every write:
window.addEventListener and re-read from storage.ts when an event fires. This keeps components decoupled while still reacting to changes made by other parts of the app.
Routing
React Router 7 handles all client-side navigation viacreateBrowserRouter. The Layout component wraps all authenticated screens and renders the shared navigation shell.
| Path | Component | Purpose |
|---|---|---|
/ | HomeScreen | Route search |
/results | ResultsScreen | Connection results |
/detail/:id | RouteDetailScreen | Full trip detail |
/r/:id | RouteDetailScreen | Shared route link |
/map | LiveMapScreen | Interactive transit map |
/account | AccountScreen | User profile and settings |
/stoerungen | ServiceAlertsPage | Service disruptions |
/login | LoginScreen | Authentication |
/signup | SignUpScreen | Registration |
Transit data
All transit data comes from the South Tyrol EFA API athttps://efa.sta.bz.it/apb. The efa.ts service layer handles stop autocomplete, connection search, departure boards, and service alerts. It normalizes API inconsistencies and maps transport modes to a consistent set:
RAIL— trains (SAD, Trenitalia)BUS— buses (SASA, SAD coaches)GONDOLA— cable cars and aerial tramwaysWALK— walking segments
i18n
The app ships with four locales: German (de), Italian (it), English (en), and Ladin (lad). A custom hook reads the active language from localStorage and provides typed t() translation lookups. EFA API queries adapt to the selected language — German and Italian are queried directly; Ladin and English fall back to German for transit data.
Data persistence
localStorage keys, window events, and Supabase sync in detail.
Transit API
EFA integration, stop search, and transport mode mapping.