A cinematic, AI-powered weather experience built for the modern web.
Live Demo: windly-weather.vercel.app Β |Β Backend: windly-backend Β |Β Status:
Most weather apps show you numbers. Windly makes you feel the weather.
Every condition β a thunderstorm rolling in, a heatwave baking the afternoon, a clear starlit night β is translated into a living, breathing visual experience. Rain falls across your screen. Lightning flickers in the dark. The sky shifts gradients as the sun traces its arc. An ML model reads the atmosphere and tells you what's coming before it arrives.
Windly was built on a simple idea: weather is cinematic, and your app should be too.
No frameworks. No bloat. Just the browser, raw JavaScript, and a backend doing real machine learning.
A Random Forest ML model analyses 5 real atmospheric inputs β temperature, humidity, pressure, wind speed, and cloud cover β and returns a calibrated rain probability. The result is visualised as a live animated gauge, a spectrum risk marker, and individual contributing factor bars, all updating in a single requestAnimationFrame pass to avoid layout thrashing.
A full-screen GPU-composited stage renders the weather as it actually feels:
- Rain β individual drop elements with tilted variants for wind-driven storms
- Snow β drifting flake particles with sinusoidal horizontal sway
- Lightning β full-screen flash overlay + SVG bolt strikes with double-flash timing
- Wind β flowing gradient lines blowing across the viewport
- Heat β a pulsating blazing sun corona fixed in the corner
- Night β animated shooting stars across a deep space gradient
The entire UI repaints to match the sky: sunny, sunny-extreme, rainy, cloudy, night, snowy, stormy-dark, and stargazer. Background gradients, card tones, and text contrast all shift as one.
A real-time sunrise-to-sunset arc animates the sun's current position across the sky, calculated from the API's actual sunrise and sunset timestamps.
City suggestions appear as you type, debounced and cached for 15 minutes on the backend. Results never duplicate and degrade gracefully on network failure.
Weather-matched audio β heavy rain, light rain, thunder, wind, birds, crickets, snow footsteps, water drops β toggled by a single button and tied to live condition flags in app state.
An interactive graph and a 7-day expandable forecast card, with flip-in row animations and collapsible day detail panels.
Switch between Β°C and Β°F instantly β every displayed value, including the AI card factor bars, recalculates without a network call.
Breakpoints at 1050px, 768px, and 480px. The AI card repositions itself between desktop and mobile layouts at runtime via a MutationObserver. Safe-area insets support notched iPhones. Reduced-motion media queries respected throughout.
Windly is built without React, Vue, or any framework β and that's a deliberate engineering choice, not a limitation.
| Advantage | Detail |
|---|---|
| Zero build step | No Webpack, Vite, or compiler. Open index.html and it runs. |
| Direct browser performance | No VDOM diff overhead. DOM updates happen exactly when and where needed. |
| Lightweight delivery | No framework runtime shipped to the client. The entire JS payload is your app. |
| Trivial deployment | Any static host works β Vercel, Netlify, GitHub Pages, or a plain CDN. |
| Full control | Animation timing, DOM batching, and render cycles are owned entirely by the app, not a framework scheduler. |
ES Modules (import/export) give clean code organisation without the complexity of a bundler.
Windly treats the browser's rendering pipeline as a first-class concern. Every animation-heavy feature was profiled and optimised.
The weather stage uses contain: strict so all particle repaints are isolated from the rest of the page β a layout change inside the stage cannot trigger a full-page reflow. Individual particles carry will-change: transform so the browser promotes them to dedicated compositor layers, keeping animations on the GPU thread.
All visual updates on the AI card β the gauge arc, spectrum marker, and five factor bars β are batched into a single requestAnimationFrame call. Spreading them across separate frames would cause multiple style recalculations per paint cycle. One frame = one pass = no jank.
The probability percentage counter uses a cubic ease-out easing function inside requestAnimationFrame, not setInterval. This produces silky 60fps deceleration rather than mechanical ticking.
On screens β€768px, backdrop-filter blur radius is halved, the sun corona is downsized, and cloud particle blur is reduced from 25px to 18px. Mid-range Android GPUs handle these passes significantly better without a visible quality loss.
Scroll and touch listeners are registered as { passive: true } so the browser never waits on JavaScript before committing a scroll frame β critical for smooth mobile interactions.
Render's free tier sleeps after ~10 minutes of inactivity. Windly handles this with a 50-second AbortController timeout and a neutral waiting message shown after 7 seconds β so users aren't left staring at a blank screen wondering if the app is broken.
frontend/
βββ index.html
βββ style.css # Imports all CSS modules
βββ css/
β βββ base.css # Reset, typography, body
β βββ themes.css # Weather-driven backgrounds + safe-area insets
β βββ layout.css # Responsive grid & breakpoints (1050 / 768 / 480px)
β βββ components.css # Cards, buttons, inputs, forecast rows
β βββ animations.css # Particle keyframes, lightning, sun, shooting stars
β βββ enhancements.css # Polish & micro-interactions
βββ js/
β βββ main.js # Entry point, event listeners
β βββ api.js # Weather fetch, AI prediction, error handling
β βββ ui.js # DOM rendering β conditions, forecast, hourly
β βββ state.js # Global app state (single source of truth)
β βββ config.js # Base URL β dev/prod auto-detection
β βββ features.js # Particle spawning, ambient sound, animation flags
β βββ graph.js # Hourly temperature/rain graph
β βββ ai-manager.js # AI card desktopβmobile placement + collapse observer
βββ sound/
βββ heavy-rain.mp3
βββ light-rain.mp3
βββ thunder.mp3
βββ wind.mp3
βββ bird.mp3
βββ crickets.mp3
βββ water-drop.mp3
βββ floraphonic-foot-step-snow-*.mp3
βββββββββββββββββββββββββββββββββββ
β User's Browser β
β Vanilla JS + CSS (Vercel) β
ββββββββββββββββββ¬βββββββββββββββββ
β HTTPS
βΌ
βββββββββββββββββββββββββββββββββββ
β FastAPI Backend (Render) β
β β’ WeatherAPI proxy (TTL cache) β
β β’ /predict β ML inference β
β β’ GZip compression β
β β’ Shared async httpx client β
ββββββββββββ¬βββββββββββββββ¬ββββββββ
β β
βΌ βΌ
ββββββββββββββββ βββββββββββββββββββββ
β WeatherAPI β β Random Forest β
β (forecast, β β Classifier β
β search) β β (random_forest β
β β β .pkl β sigmoid β
β β β calibrated) β
ββββββββββββββββ βββββββββββββββββββββ
The frontend never touches WeatherAPI directly β all calls go through the FastAPI proxy, which handles caching, key security, and error normalisation.
| Desktop UI | ![]() |
| Mobile UI | ![]() |
| AI Atmospheric Analysis Card | ![]() |
| Hourly Forecast Graph | ![]() |
| Storm Theme | ![]() |
| Night / Stargazer Theme | ![]() |
Windly is built mobile-first, not mobile-adapted.
- Safe-area insets β
env(safe-area-inset-*)padding on all sides for notched iPhones (iPhone X+) and Android. Requiresviewport-fit=coverin the meta tag. - Three responsive breakpoints β 1050px (tablet/small laptop), 768px (mobile), 480px (small phone). Each breakpoint has intentional layout changes, not just scaled-down desktop styles.
- Touch-optimised interactions β hover states are disabled on touch screens (
pointer: noneguard). The AI card relocates to the Forecast tab panel on mobile so it's always visible in context. - Reduced-motion support β
@media (prefers-reduced-motion: reduce)disables all particle animations and shortens transitions. The app remains fully functional; it just stops moving. - OLED battery consideration β The
stormy-darktheme lightens slightly on mobile to reduce pure-black pixel stress on OLED screens during long sessions.
No build step. No package install. Just serve and run.
# Clone the repo
git clone https://github.com/nitinmohan18/windly-frontend.git
cd windly-frontend
# Serve with any static server
npx serve .Open http://localhost:3000. That's it.
The backend should be running on port
8000locally.config.jspoints there automatically when it detectslocalhost.
js/config.js auto-detects environment β no .env file needed on the frontend:
const isDev = location.hostname === 'localhost'
|| location.hostname === '127.0.0.1'
|| location.hostname.startsWith('192.168.')
...
export const CONFIG = {
BASE_URL: isDev
? `http://${location.hostname}:8000` // local FastAPI
: 'https://windly-backend.onrender.com', // production
};- Push this repo to GitHub
- Import on Vercel
- No build command β set output directory to
.(root) - Deploy
Vercel serves the static files directly. All API calls route to the Render backend via config.js.
| Language | Vanilla JavaScript (ES Modules) |
| Styling | CSS3 β modular, no preprocessor |
| Animations | CSS keyframes + requestAnimationFrame |
| State | Single shared appState object (no store library) |
| Build | None β runs directly in the browser |
| Framework | FastAPI |
| Server | Uvicorn |
| HTTP Client | httpx (async, connection-pooled) |
| Caching | In-memory TTL dict (5 min forecast, 15 min search) |
| Compression | GZipMiddleware (β₯500 byte responses) |
| Algorithm | Random Forest Classifier |
| Calibration | Sigmoid (CalibratedClassifierCV, 5-fold CV) |
| Library | scikit-learn |
| Training Data | Global Weather Repository (Kaggle) |
| Features | Temperature, Humidity, Pressure, Wind, Cloud |
| Frontend | Vercel |
| Backend | Render |
| Weather Data | WeatherAPI.com |
MIT β fork it, extend it, make it your own.
Built with obsessive attention to detail.
Feel the Weather .





