diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f673a71 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5502 +} \ No newline at end of file diff --git a/assets/icons/.DS_Store b/assets/icons/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/assets/icons/.DS_Store differ diff --git a/assets/icons/01d.svg b/assets/icons/01d.svg new file mode 100644 index 0000000..2517c11 --- /dev/null +++ b/assets/icons/01d.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/01n.svg b/assets/icons/01n.svg new file mode 100644 index 0000000..125bf0d --- /dev/null +++ b/assets/icons/01n.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/02d.svg b/assets/icons/02d.svg new file mode 100644 index 0000000..0d7a8a3 --- /dev/null +++ b/assets/icons/02d.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/02n.svg b/assets/icons/02n.svg new file mode 100644 index 0000000..5ea8e7a --- /dev/null +++ b/assets/icons/02n.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/03d.svg b/assets/icons/03d.svg new file mode 100644 index 0000000..abc7442 --- /dev/null +++ b/assets/icons/03d.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/03n.svg b/assets/icons/03n.svg new file mode 100644 index 0000000..abc7442 --- /dev/null +++ b/assets/icons/03n.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/04d.svg b/assets/icons/04d.svg new file mode 100644 index 0000000..abc7442 --- /dev/null +++ b/assets/icons/04d.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/04n.svg b/assets/icons/04n.svg new file mode 100644 index 0000000..abc7442 --- /dev/null +++ b/assets/icons/04n.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/09d.svg b/assets/icons/09d.svg new file mode 100644 index 0000000..9c2db1f --- /dev/null +++ b/assets/icons/09d.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/09n.svg b/assets/icons/09n.svg new file mode 100644 index 0000000..9c2db1f --- /dev/null +++ b/assets/icons/09n.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/10d.svg b/assets/icons/10d.svg new file mode 100644 index 0000000..63a158e --- /dev/null +++ b/assets/icons/10d.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/10n.svg b/assets/icons/10n.svg new file mode 100644 index 0000000..c6f9e69 --- /dev/null +++ b/assets/icons/10n.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/11d.svg b/assets/icons/11d.svg new file mode 100644 index 0000000..674718b --- /dev/null +++ b/assets/icons/11d.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/11n.svg b/assets/icons/11n.svg new file mode 100644 index 0000000..674718b --- /dev/null +++ b/assets/icons/11n.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/13d.svg b/assets/icons/13d.svg new file mode 100644 index 0000000..66f0259 --- /dev/null +++ b/assets/icons/13d.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/13n.svg b/assets/icons/13n.svg new file mode 100644 index 0000000..66f0259 --- /dev/null +++ b/assets/icons/13n.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/50d.svg b/assets/icons/50d.svg new file mode 100644 index 0000000..c2732f4 --- /dev/null +++ b/assets/icons/50d.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/50n.svg b/assets/icons/50n.svg new file mode 100644 index 0000000..4b1247a --- /dev/null +++ b/assets/icons/50n.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dist/script.js b/dist/script.js new file mode 100644 index 0000000..89195cb --- /dev/null +++ b/dist/script.js @@ -0,0 +1,223 @@ +"use strict"; +const baseURL = "https://api.openweathermap.org/data/2.5/"; +const apiKEY = "dfd2a92cf6c2789182807260f210958f"; +// DOM Elements +const dailyForecast = document.getElementById("daily-forecast"); +const cityInput = document.getElementById("city-input"); +const searchBtn = document.getElementById("search-btn"); +const weeklyForecast = document.getElementById("weekly-forecast"); +const forecastIcon = document.getElementById("forecast-icon"); +const forecastDiv = document.getElementById("forecast"); +const showForecastBtn = document.getElementById("toggle-btn"); +const weekBtn = document.getElementById("week-btn"); +const body = document.body; +let data = []; + +document.addEventListener('DOMContentLoaded', function() { + const bodyElement = document.body; + bodyElement.classList.add('loading'); + + // Simulate page load completion + window.addEventListener('load', () => { + bodyElement.classList.remove('loading'); + }); + }); + + + +//Convert Unix timestamp to readable time with timezone +const formatTime = (timestamp, timezoneOffset) => { + const date = new Date((timestamp + timezoneOffset) * 1000); + return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", timeZone: "UTC" }); +}; +//Capital first letter function +const capitalFirst = (string) => { + return string.charAt(0).toUpperCase() + string.slice(1); +}; +const weatherIcons = { + "01d": { description: "clear sky (day)", file: "01d.svg" }, + "01n": { description: "clear sky (night)", file: "01n.svg" }, + "02d": { description: "few clouds (day)", file: "02d.svg" }, + "02n": { description: "few clouds (night)", file: "02n.svg" }, + "03d": { description: "scattered clouds (day)", file: "03d.svg" }, + "03n": { description: "scattered clouds (night)", file: "03n.svg" }, + "04d": { description: "broken clouds (day)", file: "04d.svg" }, + "04n": { description: "broken clouds (night)", file: "04n.svg" }, + "09d": { description: "shower rain (day)", file: "09d.svg" }, + "09n": { description: "shower rain (night)", file: "09n.svg" }, + "10d": { description: "rain (day)", file: "10d.svg" }, + "10n": { description: "rain (night)", file: "10n.svg" }, + "11d": { description: "thunderstorm (day)", file: "11d.svg" }, + "11n": { description: "thunderstorm (night)", file: "11n.svg" }, + "13d": { description: "snow (day)", file: "13d.svg" }, + "13n": { description: "snow (night)", file: "13n.svg" }, + "50d": { description: "mist (day)", file: "50d.svg" }, + "50n": { description: "mist (night)", file: "50n.svg" } +}; +const getWeatherDescription = (iconCode) => { + const weather = weatherIcons[iconCode]; + return weather ? weather.description : "unknown"; +}; +const fetchWeather = (city = "Stockholm", lat, lon) => { + let apiURL = ""; + if (lat !== undefined && lon !== undefined) { + apiURL = `${baseURL}weather?lat=${lat}&lon=${lon}&units=metric&APPID=${apiKEY}`; + } + else { + apiURL = `${baseURL}weather?q=${city}&units=metric&APPID=${apiKEY}`; + } + fetch(apiURL) + .then((response) => { + if (!response.ok) + throw new Error("The city was not found!"); + return response.json(); + }) + .then((data) => { + dayForecast(data); + updateBackground(Date.now() / 1000, data.sys.sunrise, data.sys.sunset); + }) + .catch(() => { + dailyForecast.innerHTML = `

Sorry, we have no weather data matching your search, please select another city.

`; + }); +}; +const dayForecast = (data) => { + const iconCode = data.weather[0].icon; + const weather = weatherIcons[iconCode]; + const iconUrl = `./assets/icons/${weather.file}`; + const description = capitalFirst(data.weather[0].main); + const timezoneOffset = data.timezone; + dailyForecast.innerHTML = ` +
+

${data.main.temp.toFixed(0)}°C

+

${data.name}

+

${description}

+
+
+

Sunrise ${formatTime(data.sys.sunrise, timezoneOffset)}

+

Sunset ${formatTime(data.sys.sunset, timezoneOffset)}

+
+`; + forecastIcon.innerHTML = `${description}`; +}; +const fetchForecast = (city = "Stockholm", lat, lon) => { + let apiURLForecast; + if (lat !== undefined && lon !== undefined) { + apiURLForecast = `${baseURL}forecast?lat=${lat}&lon=${lon}&units=metric&APPID=${apiKEY}`; + } + else { + apiURLForecast = `${baseURL}forecast?q=${city}&units=metric&APPID=${apiKEY}`; + } + fetch(apiURLForecast) + .then((response) => { + if (!response.ok) + throw new Error("The city was not found!"); + return response.json(); + }) + .then((data) => { + const timezoneOffset = data.city.timezone; + const forecastList = data.list + .filter((forecast) => forecast.dt_txt.endsWith("12:00:00")) + .slice(0, 4); + weeklyForecast.innerHTML = ""; + forecastList.forEach((forecast) => { + const date = new Date(forecast.dt * 1000); + const forecastDay = date.toLocaleDateString("en-US", { weekday: "short" }); + const iconCode = forecast.weather[0].icon; + const weather = weatherIcons[iconCode]; + const iconUrl = `./assets/icons/${weather.file}`; + weeklyForecast.innerHTML += ` +
  • +

    ${forecastDay}

    +
    + ${forecast.weather[0].description} +

    ${Math.round(forecast.main.temp)}°C

    +
    +
  • `; + }); + weeklyForecast.style.display = "none"; + weeklyForecast.classList.add("toggle-forecast-hide"); + }) + .catch((error) => { + weeklyForecast.innerHTML = `

    ${error.message}

    `; + }); +}; +const getLocation = () => { + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition(showPosition, showError); + } + else { + alert("Geolocation is not supported by this browser."); + fetchWeather(); + fetchForecast(); + } +}; +const showPosition = (position) => { + const lat = position.coords.latitude; + const lon = position.coords.longitude; + fetchWeather(undefined, lat, lon); + fetchForecast(undefined, lat, lon); +}; +const showError = (error) => { + switch (error.code) { + case error.PERMISSION_DENIED: + alert("User denied the request for Geolocation."); + break; + case error.POSITION_UNAVAILABLE: + alert("Location information is unavailable."); + break; + case error.TIMEOUT: + alert("The request to get user location timed out."); + break; + } + fetchWeather(); + fetchForecast(); +}; +searchBtn.addEventListener("click", () => { + const city = cityInput === null || cityInput === void 0 ? void 0 : cityInput.value.trim(); + if (city) { + fetchWeather(city); + fetchForecast(city); + } + else { + alert("Try again with a valid city"); + } +}); +getLocation(); +const updateBackground = (currentTime, sunrise, sunset) => { + if (currentTime >= sunrise && currentTime < sunset) { + body.classList.remove("night"); + } + else { + body.classList.add("night"); + } +}; +showForecastBtn.addEventListener("click", () => { + showForecastBtn.disabled = true; + const sunPosition = document.getElementById("sun-position"); + if (!weeklyForecast.classList.contains("toggle-forecast-show")) { + weeklyForecast.style.display = "block"; + setTimeout(() => { + forecastDiv.classList.add("compact"); + sunPosition.classList.add("compact-sun"); + weeklyForecast.classList.add("toggle-forecast-show"); + weekBtn.classList.add("btn-shift"); + }, 5); + setTimeout(() => { + showForecastBtn.textContent = "▼"; + showForecastBtn.disabled = false; + }, 600); + } + else { + weeklyForecast.classList.remove("toggle-forecast-show"); + forecastDiv.classList.remove("compact"); + sunPosition.classList.remove("compact-sun"); + weekBtn.classList.remove("btn-shift"); + setTimeout(() => { + weeklyForecast.style.display = "none"; + }, 300); + setTimeout(() => { + showForecastBtn.textContent = "▲"; + showForecastBtn.disabled = false; + }, 300); + } +}); diff --git a/index.html b/index.html new file mode 100644 index 0000000..4960ffb --- /dev/null +++ b/index.html @@ -0,0 +1,41 @@ + + + + + + + Weather App + + + + +
    + + +
    +
    +
    +
    +
    +
    +
    + +
    + +
    + +
    +
      +
    +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/script.ts b/script.ts new file mode 100644 index 0000000..aa7bf81 --- /dev/null +++ b/script.ts @@ -0,0 +1,255 @@ +const baseURL: string = "https://api.openweathermap.org/data/2.5/" +const apiKEY: string = "dfd2a92cf6c2789182807260f210958f" + +// DOM Elements +const dailyForecast = document.getElementById("daily-forecast") as HTMLElement; +const cityInput = document.getElementById("city-input") as HTMLElement; +const searchBtn = document.getElementById("search-btn") as HTMLElement; +const weeklyForecast = document.getElementById("weekly-forecast") as HTMLElement; +const forecastIcon = document.getElementById("forecast-icon") as HTMLElement; +const forecastDiv = document.getElementById("forecast") as HTMLElement; +const showForecastBtn = document.getElementById("toggle-btn") as HTMLButtonElement; +const weekBtn = document.getElementById("week-btn") as HTMLDivElement; +const body = document.body as HTMLBodyElement; +let data: any[] = []; + +//Convert Unix timestamp to readable time with timezone +const formatTime = (timestamp: number, timezoneOffset: number): string => { + const date = new Date((timestamp + timezoneOffset) * 1000); + return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", timeZone: "UTC" }); +}; + +//Capital first letter function +const capitalFirst = (string: string): string => { + return string.charAt(0).toUpperCase() + string.slice(1) +}; + +type WeatherIconCode = + | "01d" | "01n" + | "02d" | "02n" + | "03d" | "03n" + | "04d" | "04n" + | "09d" | "09n" + | "10d" | "10n" + | "11d" | "11n" + | "13d" | "13n" + | "50d" | "50n"; + +interface WeatherIcon { + description: string; + file: string; +} + +const weatherIcons: Record = { + "01d": { description: "clear sky (day)", file: "01d.svg" }, + "01n": { description: "clear sky (night)", file: "01n.svg" }, + "02d": { description: "few clouds (day)", file: "02d.svg" }, + "02n": { description: "few clouds (night)", file: "02n.svg" }, + "03d": { description: "scattered clouds (day)", file: "03d.svg" }, + "03n": { description: "scattered clouds (night)", file: "03n.svg" }, + "04d": { description: "broken clouds (day)", file: "04d.svg" }, + "04n": { description: "broken clouds (night)", file: "04n.svg" }, + "09d": { description: "shower rain (day)", file: "09d.svg" }, + "09n": { description: "shower rain (night)", file: "09n.svg" }, + "10d": { description: "rain (day)", file: "10d.svg" }, + "10n": { description: "rain (night)", file: "10n.svg" }, + "11d": { description: "thunderstorm (day)", file: "11d.svg" }, + "11n": { description: "thunderstorm (night)", file: "11n.svg" }, + "13d": { description: "snow (day)", file: "13d.svg" }, + "13n": { description: "snow (night)", file: "13n.svg" }, + "50d": { description: "mist (day)", file: "50d.svg" }, + "50n": { description: "mist (night)", file: "50n.svg" } +}; + +const getWeatherDescription = (iconCode: string): string => { + const weather = weatherIcons[iconCode as keyof typeof weatherIcons]; + return weather ? weather.description : "unknown"; +}; + +const fetchWeather = ( + city: string = "Stockholm", + lat?: number, + lon?: number +): void => { + let apiURL: string = ""; + + if (lat !== undefined && lon !== undefined) { + apiURL = `${baseURL}weather?lat=${lat}&lon=${lon}&units=metric&APPID=${apiKEY}`; + } else { + apiURL = `${baseURL}weather?q=${city}&units=metric&APPID=${apiKEY}`; + } + + fetch(apiURL) + .then((response: Response) => { + if (!response.ok) throw new Error("The city was not found!"); + return response.json(); + }) + .then((data: any) => { + dayForecast(data); + updateBackground(Date.now() / 1000, data.sys.sunrise, data.sys.sunset); + }) + .catch(() => { + dailyForecast.innerHTML = `

    Sorry, we have no weather data matching your search, please select another city.

    `; + }); +}; + +const dayForecast = (data: any): void => { + const iconCode = data.weather[0].icon; + const weather = weatherIcons[iconCode as keyof typeof weatherIcons]; + const iconUrl = `./assets/icons/${weather.file}`; + const description = capitalFirst(data.weather[0].main); + const timezoneOffset = data.timezone; + + dailyForecast.innerHTML = ` +
    +

    ${data.main.temp.toFixed(0)}°C

    +

    ${data.name}

    +

    ${description}

    +
    +
    +

    Sunrise ${formatTime(data.sys.sunrise, timezoneOffset)}

    +

    Sunset ${formatTime(data.sys.sunset, timezoneOffset)}

    +
    +`; + + forecastIcon.innerHTML = `${description}`; +}; + + +const fetchForecast = ( + city: string = "Stockholm", + lat?: number, + lon?: number): void => { + let apiURLForecast: string; + if (lat !== undefined && lon !== undefined) { + apiURLForecast = `${baseURL}forecast?lat=${lat}&lon=${lon}&units=metric&APPID=${apiKEY}`; + } else { + apiURLForecast = `${baseURL}forecast?q=${city}&units=metric&APPID=${apiKEY}`; + } + + fetch(apiURLForecast) + .then((response) => { + if (!response.ok) throw new Error("The city was not found!"); + return response.json(); + }) + .then((data) => { + const timezoneOffset = data.city.timezone; + + const forecastList = data.list + .filter((forecast: any) => forecast.dt_txt.endsWith("12:00:00")) + .slice(0, 4); + + weeklyForecast.innerHTML = ""; + + forecastList.forEach((forecast: any) => { + const date = new Date(forecast.dt * 1000); + const forecastDay = date.toLocaleDateString("en-US", { weekday: "short" }); + const iconCode = forecast.weather[0].icon; + const weather = weatherIcons[iconCode as keyof typeof weatherIcons]; + const iconUrl = `./assets/icons/${weather.file}`; + + weeklyForecast.innerHTML += ` +
  • +

    ${forecastDay}

    +
    + ${forecast.weather[0].description} +

    ${Math.round(forecast.main.temp)}°C

    +
    +
  • `; + }); + + weeklyForecast.style.display = "none"; + weeklyForecast.classList.add("toggle-forecast-hide"); + }) + .catch((error) => { + weeklyForecast.innerHTML = `

    ${error.message}

    `; + }); +}; + +const getLocation = (): void => { + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition(showPosition, showError); + } else { + alert("Geolocation is not supported by this browser."); + fetchWeather(); + fetchForecast(); + } +}; + +const showPosition = (position: GeolocationPosition): void => { + const lat = position.coords.latitude; + const lon = position.coords.longitude; + fetchWeather(undefined, lat, lon); + fetchForecast(undefined, lat, lon); +}; + +const showError = (error: GeolocationPositionError): void => { + switch (error.code) { + case error.PERMISSION_DENIED: + alert("User denied the request for Geolocation."); + break; + case error.POSITION_UNAVAILABLE: + alert("Location information is unavailable."); + break; + case error.TIMEOUT: + alert("The request to get user location timed out."); + break; + } + fetchWeather(); + fetchForecast(); +}; + + +searchBtn.addEventListener("click", (): void => { + const city = (cityInput as HTMLInputElement | null)?.value.trim(); + if (city) { + fetchWeather(city); + fetchForecast(city); + } else { + alert("Try again with a valid city"); + } +}); + +getLocation(); + +const updateBackground = (currentTime: number, sunrise: number, sunset: number): void => { + if (currentTime >= sunrise && currentTime < sunset) { + body.classList.remove("night"); + } else { + body.classList.add("night"); + } +}; + +showForecastBtn.addEventListener("click", (): void => { + showForecastBtn.disabled = true; + const sunPosition = document.getElementById("sun-position") as HTMLElement; + + if (!weeklyForecast.classList.contains("toggle-forecast-show")) { + weeklyForecast.style.display = "block"; + setTimeout(() => { + forecastDiv.classList.add("compact"); + sunPosition.classList.add("compact-sun"); + weeklyForecast.classList.add("toggle-forecast-show"); + weekBtn.classList.add("btn-shift"); + }, 5); + + setTimeout(() => { + showForecastBtn.textContent = "▼"; + showForecastBtn.disabled = false; + }, 600); + } else { + weeklyForecast.classList.remove("toggle-forecast-show"); + forecastDiv.classList.remove("compact"); + sunPosition.classList.remove("compact-sun"); + weekBtn.classList.remove("btn-shift"); + + setTimeout(() => { + weeklyForecast.style.display = "none"; + }, 300); + + setTimeout(() => { + showForecastBtn.textContent = "▲"; + showForecastBtn.disabled = false; + }, 300); + } +}); diff --git a/style/style.css b/style/style.css new file mode 100644 index 0000000..53eaa0f --- /dev/null +++ b/style/style.css @@ -0,0 +1,337 @@ +:root { + --primary-color: #ffffff; + /* text*/ + --secondary-color: #e8e9ff; + /* daytime light*/ + --tertiary-color: #8589ff; + /* daytime Dark*/ + --quaternary-color: #757aff; + /* Button */ + --quinary-color: #6264a2; + /* night light*/ + --senary-color: #222350; + /* night dark*/ + --septenary-color: #000000; + /* */ + --primary-font: "Roboto", sans-serif; + --secondary-font: "Open Sans", sans-serif; + overflow: hidden; +} + +body { + font-family: var(--primary-font); + background: linear-gradient(45deg, + rgba(133, 137, 255, 1) 50%, + rgba(232, 233, 255, 1) 100%); + background-size: cover; + background-repeat: no-repeat; + background-attachment: fixed; + color: var(--primary-color); + margin: 0; +} + +.night { + font-family: var(--primary-font); + background: linear-gradient(45deg, + rgba(34, 35, 80, 1) 50%, + rgba(98, 100, 163, 1) 100%); + background-size: cover; + background-repeat: no-repeat; + background-attachment: fixed; + color: var(--primary-color); + margin: 0; +} + +h1 { + font-size: 5rem; + font-weight: 300; + margin: 0; + justify-self: start; + /* width: 80vmax; */ +} + +h2 { + font-size: 2.125rem; + font-weight: 300; + margin: 0; +} + +h3 { + font-size: .75rem; + font-weight: 100; + margin: 0; +} + +sup { + font-size: 2rem; +} + +.desktop { + display: flex; + flex-direction: column; + + align-content: center; + align-items: center; + width: 100%; +} + +nav { + display: flex; + flex-direction: column; + justify-content: center; + box-sizing: border-box; + width: 100dvw; + align-items: center; +} + +.search-container { + display: flex; + flex-direction: row; + justify-content: space-between; + padding: 1rem 0.5rem 0rem 0.5rem; + align-items: center; + gap: 0.5rem; + transition: transform 0.3s ease; + width: min-content; + max-width: 475px; +} + +#city-input { + flex: 1; + text-indent: 1rem; + border: none; + outline: none; + border-radius: 20px; + background: rgba(255, 255, 255, 0.4); + color: rgba(34, 35, 80, 0.6); + font-size: .75rem; + font-family: var(--primary-font); + width: 65dvw; + padding: .5rem; +} + +#city-input::placeholder { + color: rgba(98, 100, 162, 0.5); +} + +#search-btn { + padding: 0.5rem 0.5rem; + border: none; + border-radius: 20px; + background: rgba(255, 255, 255, 0.4); + color: var(--text-color); + cursor: pointer; + transition: background 0.2s ease; + font-family: var(--primary-font); + font-size: .75rem; + color: rgba(34, 35, 80, 0.4); +} + +#search-btn:hover { + background: rgba(255, 255, 255, 0.4); +} + +.top-forecast { + display: flex; + flex-direction: column; + row-gap: 0.5em; + margin-bottom: 2rem; + margin-top: 5dvh; +} + +.sun-posititon { + row-gap: .75rem; +} + +.forecast { + padding: 1em; + display: flex; + flex-direction: column; + align-content: center; + width: 80dvw; + flex: 1; + box-sizing: border-box; + max-width: 475px; +} + +.daily-forecast { + background-color: transparent; + flex: 1; + display: flex; + flex-direction: column; + justify-content: start; + align-items: start; + height: 50dvh; + width: 50dvw; +} + +#forecast-icon { + display: flex; + flex-direction: row; + justify-content: end; + box-sizing: border-box; +} + +#forecast-icon>img { + object-fit: contain; + background: none; + height: 7rem; + width: auto; +} + +#weekly-forecast { + display: none; + flex-direction: column; + align-items: center; + justify-content: space-around; + color: var(--septenary-color); + background-color: var(--primary-color); + height: 50dvh; + width: 100vw; + text-decoration: none; + box-sizing: border-box; + padding: 1rem; + border-top-left-radius: 25px; + border-top-right-radius: 25px; + max-width: 475px; +} + +.weather-temp { + display: inline-flex; + align-items: center; + height: fit-content; + column-gap: .5rem; +} + +.weather-temp img { + height: 25px; + width: auto; +} + +ul { + width: 100%; + height: 100%; + padding-top: 2rem; + padding: 0; + margin: 0; + row-gap: 2rem; +} + +li { + display: flex; + flex-direction: row; + align-content: center; + align-items: center; + justify-content: space-around; + align-items: center; + width: 100%; + flex-grow: 1; + height: 20%; + font-weight: 100; +} + +.toggle-forecast, +.toggle-forecast-hide { + transition: transform .7s ease; +} + +.toggle-forecast { + transform: translateY(10dv); + +} + +.toggle-forecast-hide { + transform: translateY(100dvh); + +} + +.toggle-forecast-show { + transform: translateY(0dvh); + display: block; +} + + +/* slow loading */ +.loader { + border: 8px solid rgba(255, 255, 255, 0.3); + border-top: 16px solid blue; + border-right: 16px solid green; + border-bottom: 16px solid red; + border-left: 16px solid pink; + border-radius: 50%; + width: 100px; + height: 100px; + animation: spin 3s linear infinite; + margin: auto; + display: none; + /* Hide loader by default */ +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +/* Show loader when loading class is added */ +.loading .loader { + display: block; +} + +.compact { + padding: 1em; + display: flex; + flex-direction: column; + width: 100%; + flex: 1; + box-sizing: border-box; + max-width: 475px; + height: 50dvh; + transform: scale(0.80)translateY(-2.5rem); + margin: 0 auto; +} + +.compact-sun { + display: flex; + flex-direction: row; + column-gap: 2rem; + margin-top: none; +} + +.sunrise { + display: inline; +} + +#week-btn { + position: fixed; + display: flex; + align-items: flex-start; + bottom: 20px; + right: calc(50% - 150px); + transition: transform 0.5s ease; +} + +.btn-shift { + transform: translateY(-47dvh); +} + +#toggle-btn { + height: 50px; + width: 50px; + border-radius: 50%; + background-color: rgb(98, 100, 162); + font-size: 1.2rem; + color: white; + border: none; + cursor: pointer; + z-index: 2; +} + +#toggle-btn:hover { + background-color: rgb(120, 122, 182); + transform: scale(1.05); +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7a8c196 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES6", + "outDir": "./dist", + "strict": true + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file