Creating a weather web Project application using HTML, CSS, and JavaScript involves integrating a weather API to fetch and display weather information
Table of Contents
Weather Web Application
A weather web application created using HTML, CSS, and JavaScript allows users to check the weather conditions for a specific location. This application uses the OpenWeatherMap API to fetch weather data and display it on a web page. Here’s a breakdown of the components and functionality:

Sign Up for OpenWeatherMap API:
- Go to OpenWeatherMap and sign up for a free API key.
- Once you have your API key, you can start building your weather web application.
CSS (style.css):
- The CSS file is responsible for styling the web page. In this example, it provides basic formatting, such as centering text and setting margins.
JavaScript (script.js):
- The JavaScript file contains the logic for fetching weather data and updating the web page with the retrieved information.
- It defines a
getWeather
function that is called when the “Get Weather” button is clicked. - The function uses the
fetch
API to make a request to the OpenWeatherMap API with the user’s entered location. - Upon receiving a response, it parses the JSON data and updates the content on the web page with details like city name, temperature, and weather description.
- Error handling is implemented to handle situations where the API request fails.
Weather Web Project for Index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- primary meta tags --> <title>weatherio</title> <meta name ="title" content="weatherio"> <meta name ="description" content="weatherio is a weather app made by codewithsadee"> <!-- favicon --> <link rel="shortcut icon" href="./favicon.svg" type="image/svg+xml"> <!--google font --> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@400;600&display=swap" rel="stylesheet"> <link rel="stylesheet" href="./assets/css/style.css"> <script src="./assets/js/route.js" type="module"></script> </head> <body> <header class="header"> <div class="container"> <a href="#" class="logo"> <img src="./assets/images/logo.png" width="364" height ="58" alt="logo"> </a> <div class="search-view " data-search-view> <div class="search-wrapper"> <input type="search" name="search" placeholder="search city..." autocomplete="off" class="search-field " data-search-field> <span class="m-icon leading-icon">search</span> <button class ="icon-btn leading-icon has-state" aria-label="close search" data-search-toggler> <span class="m-icon">arrow_back</span> </button> </div> <div class=" search-result" data-search-result></div> </div> <div class="header-actions"> <button class=" icon-btn has-state" aria-label="open search" data-search-toggler> <span class="m-icon icon">search</span> </button> <a href="#/current-location" class="btn-primary has-state" data-current-location-btn > <span class="m-icon">my_location</span> <span class="span">Current Location</span> </a> </div> </div> </header> <main> <article class="container" data-container> <div class="content-left"> <section class="section current-weather" aria-label="current weather" data-current-weather> </section> <!-- section --> <section class="section forecast" aria-labelledby="forecast-lable" data-5-day-forecast></section> </div> <!-- --> <div class="content-right"> <!-- HIGHLIGHTs --> <section class="section highlights" aria-labelledby="highlights-label" data-highlights></section> <!-- per houre forecast --> <section class="section hourly-forecast" aria-label="hourly forecast" data-hourly-forecast></section> <!-- #FOOTER --> <!-- <div class="card card-lg"> --> <!-- </div> --> <footer class="footer"> <p class="body-3">Copyright 2023 rkcoder.tech. All Right Reserved</p> <p class="body-3"> Powered by <a href="https://openweathermap.org/api" rel="noopener" title="Free Openweather Api" target="_blank"> <img src="./assets/images/openweather.png" width="150" height="30" loading="lazy" alt="Openweather" > </a> </p> </footer> </div> <div class="loading" data-loading></div> </article> </main> <!-- #404 --> <section class="error-content" data-error-content> <h2 class="heading">404</h2> <p class="body-1">Page not found !</p> <a href="#/weather?lat=25.4381302&lon=81.8338005" class="btn-primary"> <span class="span"> Go Home</span> </a> </section> </body> </html>
Weather Web Project
Weather Web Project for Api.js
'use strict'; const api_key = '7775811b5bc22ad000d8cbdc6e86bda0'; // fetch data from server export const fetchData = function (URL, callback) { fetch(`${URL}&appid=${api_key}`) .then(res => res.json()) .then(data => callback(data)) } export const url = { currentWeather(lat, lon) { return `https://api.openweathermap.org/data/2.5/weather?${lat}&${lon}&units=metric` }, forecast(lat, lon) { return `https://api.openweathermap.org/data/2.5/forecast?${lat}&${lon}&units=metric` }, airPollution(lat, lon) { return `https://api.openweathermap.org/data/2.5/air_pollution?${lat}&${lon}` }, reverseGeo(lat, lon) { return `https://api.openweathermap.org/geo/1.0/reverse?${lat}&${lon}&limit=5` }, /** * @param {string} query Search query eg.: 'London', 'New york' **/ geo(query) { return `https://api.openweathermap.org/geo/1.0/direct?q=${query}&limit=5` } }
App.js
"use strict"; import { fetchData, url } from "./api.js"; import * as module from "./module.js"; /** * Add event listener on multiple elements * @param {NodeList} elements Elements node Array * @param {string} eventType event type eg: "click", "mouseover" * @param {fucntion} callback callback function */ const addEventOnElements = (elements, eventType, callback) => { for (const element of elements) element.addEventListener(eventType, callback); }; /** * Toggle search in mobile devices */ const searchView = document.querySelector("[data-search-view]"); const searchTogglers = document.querySelectorAll("[data-search-toggler]"); const toggleSearch = () => searchView.classList.toggle("active"); addEventOnElements(searchTogglers, "click", toggleSearch); /** * SEARCH INTEGRATION */ const searchField = document.querySelector("[data-search-field]"); const searchResult = document.querySelector("[data-search-result]"); let searchTimeout = null; const searchTimeoutDuration = 500; searchField.addEventListener("input", function () { searchTimeout ?? clearTimeout(searchTimeout); if (!searchField.value) { searchResult.classList.remove("active"); searchResult.innerHTML = ""; searchField.classList.remove("searching"); } else { searchField.classList.add("searching"); } if (searchField.value) { searchTimeout = setTimeout(() => { fetchData(url.geo(searchField.value), (locations) => { searchField.classList.remove("searching"); searchResult.classList.add("active"); searchResult.innerHTML = ` <ul class="view-list" data-search-list></ul> `; const /** {NodeList} | [] */ items = []; for (const { name, lat, lon, country, state } of locations) { const searchItem = document.createElement("li"); searchItem.classList.add("view-item"); searchItem.innerHTML = ` <span class="m-icon">location_on</span> <div> <p class="item-title">${name}</p> <p class="label-2 item-subtitle">${state || ""} ${country}</p> </div> <a href="#/weather?lat=${lat}&lon=${lon}" class="item-link has-state" aria-label="${name} weather" data-search-toggler></a> `; searchResult.querySelector("[data-search-list]").appendChild(searchItem); items.push(searchItem.querySelector("[data-search-toggler]")); } addEventOnElements(items, "click", () => { toggleSearch(); searchResult.classList.remove("active"); }) }); }, searchTimeoutDuration); } }); const container = document.querySelector("[data-container]"); const loading = document.querySelector("[data-loading]"); const currentLocationBtn = document.querySelector("[data-current-location-btn]"); const errorContainer = document.querySelector("[data-error-content]"); /** * * @param {number} lat latitude * @param {number} lon longitude */ export const updateWeather =function (lat ,lon ){ // loading.style.display= "grid"; // container.style.overflowY= "hidden"; // container.classList.remove("fade-in"); errorContainer.style.display= "none"; const currentWeatherSection = document.querySelector("[data-current-weather]"); const highlightSection = document.querySelector("[data-highlights]"); const hourlySection = document.querySelector("[data-hourly-forecast]"); const forecastSection = document.querySelector("[data-5-day-forecast]"); currentWeatherSection.innerHTML = ""; highlightSection.innerHTML = ""; hourlySection.innerHTML = ""; forecastSection.innerHTML = ""; if (window.location.hash === "#/current-location") { currentLocationBtn.setAttribute("disabled", ""); } else { currentLocationBtn.removeAttribute("disabled"); } // CURRENT WEATHER SECTION fetchData(url.currentWeather(lat, lon), function(currentWeather){ const { weather, dt:dateUnix, sys: {sunrise: sunriseUnixUTC, sunset: sunsetUnixUTC }, main: { temp, feels_like, pressure, humidity }, visibility, timezone }= currentWeather; const [{description,icon}] =weather; const card = document.createElement("div"); card.classList.add("card", "card-lg", "current-weather-card"); card.innerHTML = ` <h2 class="title-2 card-title">Now</h2> <div class="weapper"> <p class="heading">${parseInt(temp)}°c<sup></sup></p> <img src="./assets/images/weather_icons/${icon}.png" alt="${description}" width="64" height="64" class="weather-icon"> </div> <p class="body-3">${description}</p> <ul class="meta-list"> <li class="meta-item"> <span class="m-icon">calendar_today</span> <p class="title-3 meta-text">${module.getDate(dateUnix,timezone)}</p> </li> <li class="meta-item"> <span class="m-icon">location_on</span> <p class="title-3 meta-text" data-location></p> </li> </ul> `; fetchData(url.reverseGeo(lat, lon), function([{ name, country }]) { card.querySelector("[data-location]").innerHTML = `${name}, ${country}` }); currentWeatherSection.appendChild(card); // TODAY"S HIGHLIGHTS fetchData(url.airPollution(lat, lon), (airPollution) => { const [{ main: {aqi}, components: { no2, o3, so2, pm2_5} }] = airPollution.list; const card = document.createElement("div"); card.classList.add("card", "card-lg"); card.innerHTML = ` <h2 class="title-2" id="highlights-label">Todays Highlights</h2> <div class="highlight-list"> <div class="card card-sm highlight-card one"> <h3 class="title-3">Air Quality Index</h3> <div class="wrapper"> <span class="m-icon">air</span> <ul class="card-list"> <li class="card-item"> <p class="title-1">${pm2_5.toPrecision(3)}</p> <p class="label-1">PM<sub>2.5</sub></p> </li> <li class="card-item"> <p class="title-1">${so2.toPrecision(3)}</p> <p class="label-1">SO<sub>2</sub></p> </li> <li class="card-item"> <p class="title-1">${no2.toPrecision(3)}</p> <p class="label-1">NO<sub>2</sub></p> </li> <li class="card-item"> <p class="title-1">${o3.toPrecision(3)}</p> <p class="label-1">O<sub>3</sub></p> </li> </ul> </div> <span class="badge aqi-${aqi} label-${aqi}" title="${module.aqiText[aqi].message}"> ${module.aqiText[aqi].level} </span> </div> <div class="card card-sm highlight-card two"> <h3 class="title-3">Sunrise & Sunset</h3> <div class="card-list"> <div class="card-item"> <span class="m-icon">clear_day</span> <div> <p class="label-1">Sunrise</p> <p class="title-1">${module.getTime(sunriseUnixUTC, timezone)}</p> </div> </div> <div class="card-item"> <span class="m-icon">clear_night</span> <div> <p class="label-1">Sunset</p> <p class="title-1">${module.getTime(sunsetUnixUTC, timezone)}</p> </div> </div> </div> </div> <div class="card card-sm highlight-card"> <h3 class="title-3">Humidity</h3> <div class="wrapper"> <span class="m-icon">humidity_percentage</span> <p class="title-1">${humidity}<sub>%</sub></p> </div> </div> <div class="card card-sm highlight-card"> <h3 class="title-3">Pressure</h3> <div class="wrapper"> <span class="m-icon">airwave</span> <p class="title-1">${pressure}<sub>hPa</sub></p> </div> </div> <div class="card card-sm highlight-card"> <h3 class="title-3">Visibility</h3> <div class="wrapper"> <span class="m-icon">visibility</span> <p class="title-1">${visibility / 1000}<sub>km</sub></p> </div> </div> <div class="card card-sm highlight-card"> <h3 class="title-3">Feels Like</h3> <div class="wrapper"> <span class="m-icon">thermostat</span> <p class="title-1">${parseInt(feels_like)}°<sup>c</sup></p> </div> </div> </div> `; highlightSection.appendChild(card) }); // 24H FORECAST fetchData(url.forecast(lat, lon), (forecast) => { const { list: forecastList, city : { timezone } } = forecast; hourlySection.innerHTML = ` <h2 class="title-2">Today at</h2> <div class="slider-container"> <ul class="slider-list" data-temp></ul> <ul class="slider-list" data-wind></ul> </div> `; for (const [index, data] of forecastList.entries()) { if (index > 7) break; const { dt: dateTimeUnix, main : { temp }, weather, wind : { deg: windDirection, speed: windSpeed } } = data; const [{ icon, description }] = weather; const tempLi = document.createElement("li"); tempLi.classList.add("slider-item"); tempLi.innerHTML = ` <div class="card card-sm slider-card"> <p class="body-3">${module.getHours(dateTimeUnix, timezone)}</p> <img src="./assets/images/weather_icons/${icon}.png" alt="${description}" class="weather-icon" width="48" height="48" loading="lazy" title="${description}"> <p class="body-3">${parseInt(temp)}°</p> </div> `; hourlySection.querySelector("[data-temp]").appendChild(tempLi) const windLi = document.createElement("li"); windLi.classList.add("slider-item"); windLi.innerHTML = ` <div class="card card-sm slider-card"> <p class="body-3">${module.getHours(dateTimeUnix, timezone)}</p> <img src="./assets/images/weather_icons/direction.png" alt="direction" class="weather-icon" width="48" height="48" loading="lazy" title="" style="transform: rotate(${windDirection - 180}deg)"> <p class="body-3">${parseInt(module.mps_to_kmh(windSpeed))} km/h</p> </div> `; hourlySection.querySelector("[data-wind]").appendChild(windLi) } // 5 DAY FORECAST forecastSection.innerHTML = ` <h2 class="title-2" id="forecast-label">5 Days Forecast</h2> <div class="card card-lg forecast-card"> <ul data-forecast-list></ul> </div> `; for (let i = 7, len = forecastList.length; i < len; i += 8) { const { main: { temp_max }, weather, dt_txt } = forecastList[i]; const [{ icon, description }] = weather; const date = new Date(dt_txt); const li = document.createElement("li"); li.classList.add("card-item"); li.innerHTML = ` <div class="icon-wrapper"> <img src="./assets/images/weather_icons/${icon}.png" alt="${description}" class="weather-icon" width="36" height="36" title="${description}"> <span class="span"> <p class="title-2">${parseInt(temp_max)}°</p> </span> </div> <p class="label-1">${date.getDate()} ${module.monthNames[date.getUTCMonth()]}</p> <p class="label-1">${module.weekDayNames[date.getUTCDay()]}</p> `; forecastSection.querySelector("[data-forecast-list]").appendChild(li); } loading.style.display = "none"; container.style.overflowY = "overlay"; container.classList.add("fade-in"); }); }); } export const error404 = () => errorContent.style.display = "flex";
Module.js
'use strict'; export const weekDayNames = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ] export const monthNames = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ] /** * * @param {number} dateUnix Unix date in seconds * @param {number} timezone timezone shift from UTC in seconds * @returns {string} Date string, format: 'Sunday 10, Jan' */ export const getDate = function (dateUnix, timezone) { const date = new Date((dateUnix + timezone) * 1000) const weekDayName = weekDayNames[date.getUTCDay()] const monthName = monthNames[date.getUTCMonth()] return `${weekDayName} ${date.getUTCDate()}, ${monthName}` } /** * @param {number} timeUnix Unix time in seconds * @param {number} timezone timezone shift from UTC in seconds * @returns {string} Time string, format: 'HH:MM AM?PM' */ export const getTime = function (timeUnix, timezone) { const date = new Date((timeUnix + timezone) * 1000) const hours = date.getUTCHours() const minutes = date.getUTCMinutes() const period = hours >= 12 ? "PM" : "AM" return `${hours % 12 || 12}:${minutes} ${period}` } /** * @param {number} timeUnix Unix time in seconds * @param {number} timezone timezone shift from UTC in seconds * @returns {string} Time string, format: 'HH AM/PM' */ export const getHours = function (timeUnix, timezone) { const date = new Date((timeUnix + timezone) * 1000) const hours = date.getUTCHours() const period = hours >= 12 ? "PM" : "AM" return `${hours % 12 || 12} ${period}` } /** * @param {number} mps Metre per second * @returns {number} kilometre per hour */ export const mps_to_kmh = mps => { // mph = mps * 3600 return (mps * 3600) / 1000 } export const aqiText = { 1: { level: "Good", message: "Air quality is considered satisfactory, and air pollution poses little or no risk." }, 2: { level: "Fair", message: "Air quality is acceptable; however, for some pollutants there may be a moderate health concern for a very small number of people who are unusually sensitive to air pollution." }, 3: { level: "Moderate", message: "Members of sensitive groups may experience health effects. The general public is not likely to be affected" }, 4: { level: "Poor", message: "Everyone may begin to experience health effects; members of sensitive groups may experience more serious health effects." }, 5: { level: "Very Poor", message: "Health warnings of emergency conditions. The entire population is more likely to be affected." } }
Route.js
‘use strict’;
// import { query } from ‘express’;
import {updateWeather, error404 } from ‘./app.js’;
const defaultLocation = “#/weather?lat=25.4381302&lon=81.8338005”
const currentLocation = function() {
window.navigator.geolocation.getCurrentPosition(res=> { const { latitude, longitude } = res.coords; updateWeather(`lat=${latitude}`,`lon=${longitude}`); }, err => { window.location.hash = defaultLocation; });
}
Route.js
'use strict'; // import { query } from 'express'; import {updateWeather, error404 } from './app.js'; const defaultLocation = "#/weather?lat=25.4381302&lon=81.8338005" const currentLocation = function() { window.navigator.geolocation.getCurrentPosition(res=> { const { latitude, longitude } = res.coords; updateWeather(`lat=${latitude}`,`lon=${longitude}`); }, err => { window.location.hash = defaultLocation; }); } /** * * @param {string} query Searched query */ const searcheLocation = query => updateWeather(...query.split('&')); const routes = new Map([ ["/current-location", currentLocation], ["/weather", searcheLocation] ]); const checkHash = function () { const requestURL = window.location.hash.slice(1); const [route, query] = requestURL.includes ? requestURL.split('?') : [requestURL]; routes.get(route) ? routes.get(route)(query) : error404(); } window.addEventListener("hashchange", checkHash); window.addEventListener("load", function () { if(!window.location.hash) { window.location.hash = "#/current-location"; } else { checkHash(); } });
Weather Web Project for
Style.css
:root { /* COLOR */ --primary: #b5a1e5; --on-primary: #100e17; --background: #131214; --on-background: #eae6f2; --surface: #1d1c1f; --on-surface: #dddae5; --on-surface-variant: #7b7980; --on-surface-variant-2: #b9b6bf; --outline: #3e3d40; --bg-aqi-1: #89e589; --on-bg-aqi-1: #1f331f; --bg-aqi-2: #e5dd89; --on-bg-aqi-2: #33311f; --bg-aqi-3: #e5c089; --on-bg-aqi-3: #332b1f; --bg-aqi-4: #e58989; --on-bg-aqi-4: #331f1f; --bg-aqi-5: #e589b7; --on-bg-aqi-5: #331f29; --white: hsl(0, 0%, 100%); --white-alpha-4: hsla(0, 0%, 100%, 0.04); --white-alpha-8: hsla(0, 0%, 100%, 0.08); --black-alpha-10: hsla(0, 0%, 0%, 0.1); /* gradient */ --gradient-1: linear-gradient( 180deg, hsla(270, 5%, 7%, 0) 0%, hsla(270, 5%, 7%, 0.8) 65%, hsl(270, 5%, 7%) 100% ); --gradient-2: linear-gradient( 180deg, hsla(260, 5%, 12%, 0) 0%, hsla(260, 5%, 12%, 0.8) 65%, hsl(260, 5%, 12%) 100% ); /* TYPOGRAPHY */ /* font family */ --ff-nunito-sans: "Nunito Sans", sans-serif; /* font size */ --heading: 5.6rem; --title-1: 2rem; --title-2: 1.8rem; --title-3: 1.6rem; --body-1: 2.2rem; --body-2: 2rem; --body-3: 1.6rem; --label-1: 1.4rem; --label-2: 1.2rem; /* font weight */ --weight-regular: 400; --weight-semiBold: 600; /* BOX SHADOW */ --shadow-1: 0px 1px 3px hsla(0, 0%, 0%, 0.5); --shadow-2: 0px 3px 6px hsla(0, 0%, 0%, 0.4); /* BORDER RADIUS */ --radius-28: 28px; --radius-16: 16px; --radius-pill: 500px; --radius-circle: 50%; /* TRANSITION */ --transition-short: 100ms ease; } /*-----------------------------------*\ #RESET \*-----------------------------------*/ *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } li {list-style: none;} a, img, span, input, button { display: block; } a { color: inherit; text-decoration: none;} img { height: auto;} input, button { background: none; border: none; color: inherit; font: inherit; } input { width: 100%; } button { cursor: pointer; } sub { vertical-align: baseline; } sup { vertical-align: top; } sub, sup { font-size: 0.75em;} html { font-family: var(--ff-nunito-sans); font-size: 10px; scroll-behavior: smooth; } body { background-color: var(--background); color: var(--on-background); font-size: var(--body-3); overflow: hidden; } :focus-visible { outline: 2px solid var(--white); outline-offset: 2px; } ::selection { background-color: var(--white-alpha-8);} ::-webkit-scrollbar { width: 6px; height: 6px; /*for horizontal scrollbar*/ } ::-webkit-scrollbar-thumb { background-color: var(--white-alpha-8); border-radius: var(--radius-pill); } /*-----------------------------------*\ #MATERIAL ICON \*-----------------------------------*/ @font-face { font-family: 'Material Symbols Rounded'; font-style: normal; font-weight: 400; src: url('../font/material-symbol-rounded.woff2') format('woff2'); } .m-icon { font-family: 'Material Symbols Rounded'; font-style: normal; font-weight: normal; font-size: 2.4rem; line-height: 1; letter-spacing: normal; text-transform: none; white-space: nowrap; word-wrap: normal; direction: ltr; font-feature-settings: 'liga'; -webkit-font-feature-settings: 'liga'; -webkit-font-smoothing: antialiased; height: 1em; width: 1em; overflow: hidden; } /*-----------------------------------*\ #REUSED STYLE \*-----------------------------------*/ .container { max-width: 1600px; width: 100%; margin-inline: auto; padding: 16px; } .icon-btn { background-color: var(--white-alpha-8); width: 48px; height: 48px; display: grid; place-items: center; border-radius: var(--radius-circle); } .has-state { position: relative; } .has-state:hover { box-shadow: var(--shadow-1); } .has-state:is(:focus, :focus-visible) { box-shadow: none; } .has-state::before { content: ""; position: absolute; inset: 0; border-radius: inherit; clip-path: circle(100% at 50% 50%); transition: var(--transition-short); } .has-state:hover::before { background-color: var(--white-alpha-4); } .has-state:is(:focus, :focus-visible)::before { background-color: var(--white-alpha-8); animation: ripple 250ms ease forwards; } .btn-primary { background-color: var(--primary); color: var(--on-primary); height: 48px; line-height: 48px; max-width: max-content; display: flex; align-items: center; gap: 16px; padding-inline: 16px; border-radius: var(--radius-pill); } .btn-primary .span { font-weight: var(--weight-semiBold); } .btn-primary[disabled] { background-color: var(--outline); color: var(--on-surface-variant); cursor: not-allowed; } .btn-primary[disabled]::before { display: none; } .card { background-color: var(--surface); color: var(--on-surface); } .card-lg { border-radius: var(--radius-28); padding: 20px; } .card-sm { border-radius: var(--radius-16); padding: 16px; } .heading { color: var(--white); font-size: var(--heading); line-height: 1.1; } .title-1 { font-size: var(--title-1); } .title-2 { font-size: var(--title-2); margin-block-end: 12px; } .title-3 { font-size: var(--title-3); font-weight: var(--weight-semiBold); } .body-1 { font-size: var(--body-1); } .body-2 { font-size: var(--body-2); font-weight: var(--weight-semiBold); } .body-1 { font-size: var(--body-3); } .label-1 { font-size: var(--label-1); } .label-2 { font-size: var(--label-2); } /* .fade-in { animation: fade-in 25ms ease forwards; } @keyframes fade-in{ 0%{ opacity:0; } 100%{ opacity: 1;} } */ /*-----------------------------------*\ #HEADER \*-----------------------------------*/ .header .btn-primary .span { display: none; } .logo img { width: 158px; } .header .container, .header-actions { display: flex; align-items: center; } .header .container { justify-content: space-between; } .header-actions { gap: 16px; } .header .btn-primary { padding-inline: 12px; } .search-view { position: fixed; top: 0; left: 0; width: 100%; height: 100vh; height: 100svh; /*for mobile browser*/ background-color: var(--surface); color: var(--on-surface); clip-path: circle(4% at calc(100% - 102px) 5%); opacity: 0; visibility: hidden; z-index: 4; transition: clip-path 500ms ease; } .search-view.active { opacity: 1; visibility: visible; clip-path: circle(130% at 73% 5%); } .search-wrapper { position: relative; border-block-end: 1px solid var(--outline); } .search-wrapper::before { content: ""; position: absolute; top: 50%; transform: translateY(-50%); right: 16px; width: 24px; height: 24px; border: 3px solid var(--on-surface); border-block-start-color: transparent; border-radius: var(--radius-circle); animation: loading 500ms linear infinite; display: none; } .search-wrapper:has(.searching)::before { display: block; } .search-field { height: 80px; line-height: 80px; padding-inline: 56px 16px; outline: none; } .search-field::placeholder { color: var(--on-surface-variant-2); } .search-field::-webkit-search-cancel-button { display: none; } .search-wrapper .leading-icon { position: absolute; top: 50%; left: 28px; transform: translate(-50%, -50%); } .search-wrapper > .m-icon { display: none; } .search-wrapper .icon-btn { background-color: transparent; box-shadow: none; } .search-view .view-list { padding-block: 8px 16px; } .search-view .view-item { position: relative; height: 56px; display: flex; justify-content: flex-start; align-items: center; gap: 16px; padding-inline: 16px 24px; } .search-view .view-item :is(.m-icon, .item-subtitle) { color: var(--on-surface-variant); } .search-view .view-item .item-link { position: absolute; inset: 0; box-shadow: none; } /*-----------------------------------*\ #MAIN \*-----------------------------------*/ main { height: calc(100vh - 80px); height: calc(100svh - 80px); /*for mobile browsers*/ overflow: hidden; } article.container { position: relative; display: grid; grid-template-columns: minmax(0, 1fr); gap: 20px; height: 100%; overflow-y: auto; /*for firefox*/ overflow-y: overlay; } article.container::-webkit-scrollbar-thumb { background-color: transparent; } article.container:is(:hover, :focus-within)::-webkit-scrollbar-thumb { background-color: var(--white-alpha-8); } article.container::-webkit-scrollbar-button { height: 10px;} article.container::before { content: ""; position: fixed; bottom: 0; left: 0; width: 100%; height: 40px; background-image: var(--gradient-1); pointer-events: none; z-index: 1; } .section:not(:last-child) { margin-block-end: 16px;} /*-----------------------------------*\ #CURRENT WEATHER \*-----------------------------------*/ .current-weather-card .wrapper { margin-block: 12px; display: flex; gap: 8px; align-items: center; } .current-weather-card .weather-icon { margin-inline: auto; } .current-weather-card > .body-3 { text-transform: capitalize; } .current-weather-card .meta-list { margin-block-start: 16px; padding-block-start: 16px; border-block-start: 1px solid var(--outline); } .current-weather-card .meta-item { display: flex; align-items: center; gap: 8px; } .current-weather-card .meta-item:not(:last-child) { margin-block-end: 12px; } .current-weather-card .meta-text { color: var(--on-surface-variant);} /*-----------------------------------*\ #HIGHLIGHTS \*-----------------------------------*/ .forecast-card .title-2 { margin-block-end: 0; } .forecast-card :is(.card-item, .icon-wrapper) { display: flex; align-items: center; } .forecast-card .card-item:not(:last-child) {margin-block-end: 12px;} .forecast-card .icon-wrapper { gap: 8px; } .forecast-card .label-1 { color: var(--on-surface-variant); font-weight: var(--weight-semiBold); } .forecast-card .card-item > .label-1 { width: 100%; text-align: right; } /*-----------------------------------*\ #HOURLY FORECAST \*-----------------------------------*/ .highlights .m-icon { font-size: 3.2rem; } .highlight-list { display: grid; gap: 20px; } .highlight-list .title-3 { color: var(--on-surface-variant); margin-block-end: 20px; } .highlights .card-sm { background-color: var(--black-alpha-10); position: relative; } .highlight-card :is(.wrapper, .card-list, .card-item) { display: flex; align-items: center; } .highlight-card .wrapper { justify-content: space-between; gap: 16px; } .highlight-card .card-list { flex-wrap: wrap; flex-grow: 1; row-gap: 8px; } .highlight-card .card-item { width: 50%; justify-content: flex-end; gap: 4px; } .highlight-card .label-1 { color: var(--on-surface-variant); } .badge { position: absolute; top: 16px; right: 16px; padding: 2px 12px; border-radius: var(--radius-pill); font-weight: var(--weight-semiBold); cursor: help; } .badge.aqi-1 { background-color: var(--bg-aqi-1); color: var(--on-bg-aqi-1); } .badge.aqi-2 { background-color: var(--bg-aqi-2); color: var(--on-bg-aqi-2); } .badge.aqi-3 { background-color: var(--bg-aqi-3); color: var(--on-bg-aqi-3); } .badge.aqi-4 { background-color: var(--bg-aqi-4); color: var(--on-bg-aqi-4); } .badge.aqi-5 { background-color: var(--bg-aqi-5); color: var(--on-bg-aqi-5); } .highlight-card.two .card-item { justify-content: flex-start; flex-wrap: wrap; gap: 8px 16px; } .highlight-card.two .label-1 { margin-block-end: 4px;} /*-----------------------------------*\ #FORECAST \*-----------------------------------*/ .slider-container { overflow-x: auto; margin-inline: -16px; } .slider-container::-webkit-scrollbar { display: none; } .slider-list { display: flex; gap: 12px; } .slider-list:first-child { margin-block-end: 16px; } .slider-list::before, .slider-list::after { content: ""; min-width: 4px; } .slider-item { min-width: 110px; flex: 1 1 100%; } .slider-card { text-align: center; } .slider-item .weather-icon { margin-inline: auto; margin-block: 10px; } /*-----------------------------------*\ #LOADING \*-----------------------------------*/ .loading { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: var(--background); display: grid; place-items: center; z-index: 1; display: none; } .loading::before { content: ""; width: 48px; height: 48px; border: 4px solid var(--on-background); border-block-start-color: transparent; border-radius: var(--radius-circle); animation: loading 500ms linear infinite; } /*-----------------------------------*\ #ERROR SECTION \*-----------------------------------*/ .error-content { position: fixed; top: 0; left: 0; width: 100%; height: 100vh; height: 100svh; /* for mobile browsers*/ background-color: var(--background); display: flex; flex-direction: column; justify-content: center; align-items: center; display: none; z-index: 8; } .error-content .btn-primary { margin-block-start: 20px; } /*-----------------------------------*\ #FOOTER \*-----------------------------------*/ .footer, .footer .body-3:last-child { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; } .footer { color: var(--on-surface-variant); text-align: center; gap: 12px 24px; margin-block-start: 20px; display: none; } .fade-in .footer { display: flex; } .footer .body-3:last-child { gap: 6px; } /*-----------------------------------*\ #ANIMATION \*-----------------------------------*/ @keyframes ripple { 0% { clip-path: circle(0% at 50% 50%); } 100% { clip-path: circle(100% at 50% 50%); } } @keyframes loading { 0% { transform: translateY(-50%) rotate(0); } 100% { transform: translateY(-50%) rotate(1turn); } } @keyframes fade-in { 0% { opacity: 0; } 100% { opacity: 1; } } /*-----------------------------------*\ #MEDIA QUERIES \*-----------------------------------*/ /* for >768px screens */ @media (min-width: 769px) { /* REUSED STYLE */ .container { padding: 24px; } .title-1 {--title-1: 2.4rem} .section > .title-2 { margin-block-end: 16px; } .card-lg { padding: 24px; } .card-sm { padding: 20px; display: grid; grid-template-rows: min-content 1fr; } .badge { top: 20px; right: 20px; } /* HEADER */ .header-actions { gap: 24px; } .header .btn-primary { padding-inline: 16px 24px; } .header .btn-primary .span { display: block; } .search-view { clip-path: circle(3% at calc(100% - 273px) 6%);} /* MAIN */ main { height: calc(100vh - 96px); height: calc(100svh - 96px); } article.container { padding-block-start: 0; grid-template-columns: 280px minmax(0, 1fr); align-items: flex-start; gap: 24px; } .content-left { position: sticky; top: 0; } .section:not(:last-child) { margin-block: 20px; } .forecast-card .card-item:not(:last-child) { margin-block-end: 16px;} .highlight-list { grid-template-columns: 1fr 1fr;} .highlight-card:nth-child(-n+2) { grid-column: span 2; height: 160px; } .highlight-card:nth-child(n+3) { height: 120px; } .highlights .m-icon {font-size: 3.6rem; } .highlight-card.one .card-item { width: 25%; flex-direction: column-reverse; gap: 8px; } .slider-container { margin-inline: 0 -24px; border-bottom-left-radius: var(--radius-16); border-top-left-radius: var(--radius-16); } .slider-list::before { display: none; } .slider-list::after { min-width: 12px; } .hourly-forecast .card-sm { padding: 16px; } } /* for >1200px screens */ @media (min-width: 1200px) { /* CUSTOM PROPERTY */ :root { /* font-size */ --heading: 8rem; --title-2: 2rem; } /* REUSED STYLES */ .container { padding: 40px; } .card-lg { padding: 36px; } .card-sm { padding: 24px; } .title-1 { --title-1: 3.6rem; } .highlight-card.two .card-item { column-gap: 24px; } /* HEADER */ .header .icon-btn { display: none; } .logo img { width: 200px; } .header { position: relative; height: 120px; z-index: 4; } .header .container { padding-block: 0; height: 100%; } .search-view, .search-view.active { all: unset; display: block; position: relative; width: 500px; animation: none; } .search-wrapper { border-block-end: none; } .search-wrapper > .m-icon { display: block; } .search-field, .search-view .view-list { background-color: var(--surface);} .search-field { height: 56px; border-radius: var(--radius-28); } .search-result, .search-view:not(:focus-within) .search-result { display: none; } .search-view:focus-within .search-result.active { display: block; } .search-view:has(.search-result.active):focus-within .search-field { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } .search-view .view-list { position: absolute; top: 100%; left: 0; width: 100%; max-height: 360px; border-radius: 0 0 var(--radius-28) var(--radius-28); border-block-start: 1px solid var(--outline); overflow-y: auto; /* for firefox */ overflow-y: overlay; } .search-view .view-list:empty { min-height: 120px; } .search-view .view-list::-webkit-scrollbar-button { height: 20px; } .search-view :is(:hover, :has(.view-list):hover) { filter: drop-shadow(var(--shadow-1)); } .search-view :is(:focus-within, :has(.view-list):focus-within) { filter: drop-shadow(var(--shadow-2)); } /* MAIN */ main { height: calc(100vh - 120px); height: calc(100svh - 120px); } article.container { grid-template-columns: 360px minmax(0, 1fr); gap: 40px; } .currrent-weather .weather-icon { width: 80px; } .forecast-card .title-2 { --title-2: 2.2rem; } .highlight-card:nth-child(-n+2) { height: 200px; } .highlight-card:nth-child(n+3) { height: 150px; } .highlight-card .m-icon { font-size: 4.8rem; } .slider-list { gap: 16px; } } /* for >1400px screens */ @media (min-width: 1400px) { .highlight-list { grid-template-columns: repeat(4, 1fr); } }
Fantastic web site. Lots of useful information here. I’m sending it to several friends ans also sharing in delicious. And of course, thanks for your effort!
Excellent beat ! I wish to apprentice whilst you amend your web site, how could i subscribe for a weblog site? The account aided me a acceptable deal. I have been a little bit familiar of this your broadcast provided brilliant clear idea