Weather App with HTML, CSS & JavaScript
20 DAYS 20 PROJECT CHALLENGE
Day #01
Project Overview
A compact Weather App that fetches and displays real-time weather data for any city using a public weather API. It demonstrates fetch() with async/await, JSON parsing, error handling, and simple UI updates (temperature, conditions, icon, humidity, wind, and local time). The project is beginner-friendly and easy to extend (forecast, geolocation, caching).
Note: the demo uses OpenWeatherMap‘s Current Weather endpoint. You will need a free API key from https://openweathermap.org/ — put it into script.js (the API_KEY constant).
Key Features
- Search by city name (case-insensitive).
- Shows temperature, weather description, icon, humidity, wind speed, and sunrise/sunset times.
- Error handling (city not found / network issues).
- Loading state and keyboard support (press Enter to search).
- Uses
async/awaitand clean DOM updates. - Responsive, minimal UI
HTML Code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Day 1 — Weather App</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<main class="card" role="main" aria-labelledby="title">
<header class="top">
<!-- User-uploaded image used as logo/header image -->
<div>
<h1 id="title">Day 1: Weather App</h1>
<p class="lead">Fetch real-time weather for any city using an API (OpenWeatherMap).</p>
</div>
</header>
<section class="search">
<label class="sr-only" for="city">City</label>
<input id="city" type="search" placeholder="Enter city (e.g. London)" autocomplete="off" />
<button id="searchBtn" class="btn">Search</button>
</section>
<section id="result" class="result hidden" aria-live="polite">
<div class="row">
<div class="left">
<h2 id="cityName">—</h2>
<div class="meta">
<span id="desc">—</span> · <span id="localTime">—</span>
</div>
<div class="temp-row">
<div class="temp">
<span id="temp">—</span><small>°C</small>
</div>
<div class="icon-wrap">
<img id="icon" src="" alt="" />
</div>
</div>
<div class="stats">
<div>Humidity: <strong id="humidity">—</strong>%</div>
<div>Wind: <strong id="wind">—</strong> m/s</div>
<div>Sunrise: <strong id="sunrise">—</strong></div>
<div>Sunset: <strong id="sunset">—</strong></div>
</div>
</div>
<div class="right">
<div id="mapPlaceholder" class="map">Map / extra content</div>
</div>
</div>
</section>
<div id="status" class="status muted">Enter a city and press Search.</div>
<details style="margin-top:14px">
<summary>How it works (short)</summary>
<p class="small">`script.js` builds the API URL, fetches the current weather JSON, extracts relevant fields and updates the DOM. It uses `async/await`, handles common errors, and formats times using the timezone offset returned by the API.</p>
</details>
</main>
<script src="script.js"></script>
</body>
</html>
CSS Code
:root {
--bg: #071026;
--card: #0b1220;
--accent: #06b6d4;
--muted: #9aa4b2;
--white: #e6eef6;
font-family: Inter, system-ui, -apple-system, 'Segoe UI', Roboto, Arial;
}
* {
box-sizing: border-box
}
html,
body {
height: 100%;
margin: 0;
background: #002252;
color: var(--white)
}
body {
display: flex;
align-items: center;
justify-content: center;
padding: 28px
}
.card {
width: min(920px, 96%);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.01));
border-radius: 12px;
padding: 18px;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
border: 1px solid rgba(255, 255, 255, 0.03);
}
.top {
display: flex;
gap: 12px;
align-items: center
}
.logo {
width: 60px;
height: 60px;
border-radius: 10px;
object-fit: cover
}
h1 {
margin: 0;
font-size: 20px
}
.lead {
margin: 2px 0 8px;
color: var(--muted)
}
.search {
display: flex;
gap: 8px;
margin-top: 14px
}
.search input {
flex: 1;
padding: 10px 12px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.05);
background: transparent;
color: inherit;
outline: none
}
.btn {
padding: 10px 14px;
border-radius: 8px;
border: 0;
background: linear-gradient(90deg, var(--accent), #7c3aed);
color: white;
font-weight: 600;
cursor: pointer
}
.status {
margin-top: 12px;
color: var(--muted)
}
.hidden {
display: none
}
.result {
margin-top: 14px;
padding: 14px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.03)
}
.row {
display: flex;
gap: 18px;
align-items: flex-start
}
.left {
flex: 1
}
.right {
width: 240px
}
.city {
font-size: 18px
}
.meta {
color: var(--muted);
margin-bottom: 8px
}
.temp-row {
display: flex;
align-items: center;
gap: 12px
}
.temp {
font-family: ui-monospace, monospace;
font-size: 48px;
font-weight: 700
}
.icon-wrap img {
width: 88px;
height: 88px
}
.stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
margin-top: 10px;
color: var(--muted)
}
.map {
height: 140px;
background: rgba(255, 255, 255, 0.02);
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
color: var(--muted)
}
.small {
font-size: 13px;
color: var(--muted)
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0
}
@media (max-width:720px) {
.row {
flex-direction: column
}
.right {
width: 100%
}
} Javascript Code
// Day 1 — Weather App
// Replace with your OpenWeatherMap API key:
const API_KEY = '7bf3f09e01f4eabee9483d21e48df0ed'; // <-- get a free key at openweathermap.org
const ENDPOINT = 'https://api.openweathermap.org/data/2.5/weather';
// DOM refs
const cityInput = document.getElementById('city');
const searchBtn = document.getElementById('searchBtn');
const statusEl = document.getElementById('status');
const resultSection = document.getElementById('result');
const cityNameEl = document.getElementById('cityName');
const descEl = document.getElementById('desc');
const tempEl = document.getElementById('temp');
const iconEl = document.getElementById('icon');
const humidityEl = document.getElementById('humidity');
const windEl = document.getElementById('wind');
const sunriseEl = document.getElementById('sunrise');
const sunsetEl = document.getElementById('sunset');
const localTimeEl = document.getElementById('localTime');
// helpers
function setStatus(msg, isError = false) {
statusEl.textContent = msg;
statusEl.style.color = isError ? '#ffbaba' : '';
}
function showResult() {
resultSection.classList.remove('hidden');
}
function hideResult() {
resultSection.classList.add('hidden');
}
// format unix timestamp + timezone offset to local time string
function formatTime(tsSeconds, tzOffsetSeconds) {
// tsSeconds is in UTC, tzOffsetSeconds is offset from UTC in seconds
const d = new Date((tsSeconds + tzOffsetSeconds) * 1000);
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
// format local time in city using timezone offset
function cityLocalTime(tzOffsetSeconds) {
const nowUtcSec = Math.floor(Date.now() / 1000);
return formatTime(nowUtcSec, tzOffsetSeconds);
}
// build icon URL from OpenWeatherMap
function iconUrl(iconCode) {
return `https://openweathermap.org/img/wn/${iconCode}@2x.png`;
}
// fetch weather for a city
async function fetchWeather(city) {
try {
setStatus('Loading…');
hideResult();
const url = `${ENDPOINT}?q=${encodeURIComponent(city)}&units=metric&appid=${API_KEY}`;
const res = await fetch(url);
if (!res.ok) {
if (res.status === 404) throw new Error('City not found');
throw new Error(`Network error ${res.status}`);
}
const data = await res.json();
return data;
} catch (err) {
throw err;
}
}
// render data to UI
function renderWeather(data) {
// data structure: see OpenWeatherMap docs
const { name, sys, weather, main, wind, timezone } = data;
const w = weather && weather[0];
cityNameEl.textContent = `${name}, ${sys && sys.country ? sys.country : ''}`;
descEl.textContent = w ? (w.description || '').replace(/\b\w/g, s => s.toUpperCase()) : '';
tempEl.textContent = main ? Math.round(main.temp) : '—';
iconEl.src = w ? iconUrl(w.icon) : '';
iconEl.alt = w ? w.description : 'weather';
humidityEl.textContent = main ? main.humidity : '—';
windEl.textContent = wind ? (wind.speed + ' m/s') : '—';
sunriseEl.textContent = sys && sys.sunrise ? formatTime(sys.sunrise, timezone) : '—';
sunsetEl.textContent = sys && sys.sunset ? formatTime(sys.sunset, timezone) : '—';
localTimeEl.textContent = cityLocalTime(timezone);
showResult();
setStatus('Weather loaded.');
}
// main search
async function doSearch() {
const q = cityInput.value.trim();
if (!q) {
setStatus('Please enter a city.', true);
return;
}
try {
const data = await fetchWeather(q);
renderWeather(data);
} catch (err) {
console.error(err);
setStatus(err.message || 'Failed to fetch weather.', true);
}
}
// events
searchBtn.addEventListener('click', doSearch);
cityInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') doSearch();
});
// quick demo: optional - load a default city on start
// (uncomment to auto-load)
// window.addEventListener('load', () => { cityInput.value = 'Mumbai'; doSearch(); });
Related Projects
Day 3 : Form Validation
Validates a signup/login form for email, password, etc., before submission.
Concepts: Regular expressions, DOM events.
Day 4 : Stop-Watch App
A simple stopwatch with start, stop, and reset buttons.
Concepts: setInterval(), clearInterval(), time logic.
Day 5 : Digital Piano
Play sound notes when user clicks keys or presses keyboard buttons.
Concepts: Audio API, keyboard events.