From f25fa025fc14b0209ef49cf4bd3fd4cb4b2a6ff5 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Tue, 24 Sep 2024 11:53:05 +0200 Subject: [PATCH 01/43] Started on weather app, fetching todays weather and the forecast --- index.html | 31 ++++++++++++++++ instructions.md | 16 ++++++-- script.js | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ style.css | 0 4 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 index.html create mode 100644 script.js create mode 100644 style.css diff --git a/index.html b/index.html new file mode 100644 index 000000000..8973dc214 --- /dev/null +++ b/index.html @@ -0,0 +1,31 @@ + + + + + + The Weatherington + + + +
+
+
+ Sungglasses +

Get your sunnies on. Stockholm is looking rather great today.

+
+
+
+
    +
  • +

    mon

    +

    23°

    +
  • +
+
+
+ + + diff --git a/instructions.md b/instructions.md index 42fc411ce..ea4570105 100644 --- a/instructions.md +++ b/instructions.md @@ -1,14 +1,17 @@ # Instructions + Start out with signing up for a [free Open Weather Map](https://home.openweathermap.org/users/sign_up "free Open Weather Map") account, as it can take up to a few hours for the API key to be activated. ## Step by step instructions + ### Step 1 - Get started with the weather API + [Sign up for a free Open Weather Map account](https://home.openweathermap.org/users/sign_up). Once signed in, go to "My API keys". You find that in the menu if you click your username. Copy the API Key. You can use the API Key in the APPID parameter when making calls to the openweathermap API. For example, to get the current weather in Stockholm, you can use the URL below. Remember to replace YOUR_API_KEY with the API key you copied from your dashboard. ``` -https://api.openweathermap.org/data/2.5/weather?q=Stockholm,Sweden&units=metric&APPID=YOUR_API_KEY +https://api.openweathermap.org/data/2.5/weather?q=Stockholm,Sweden&units=metric&APPID=f1eb0732aa36b48c85267620f68aa926 ``` The response should look something like this (this has been run through jsonlint.com to add newlines and indentation): @@ -63,12 +66,15 @@ You will need to use the `fetch()` function in JavaScript to load the weather da Read the [endpoint documentation](https://openweathermap.org/current) for the current weather. ### Step 2 - Present some data on your web app + Your task is to present some data on your web app. Start with: + - the city name - the temperature (rounded to 1 decimal place) - and what type of weather it is (the "description" in the JSON) ### Step 3 - Features + **Feature: Sunrise and sunset 🌅** Show the time for sunrise and sunset in a readable time format (Example: 13:00 or 1 PM). You will have to format the date from milliseconds to a readable format. [Here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date "Here") is a useful resource for how to do this. @@ -76,10 +82,10 @@ Show the time for sunrise and sunset in a readable time format (Example: 13:00 o Show a forecast for the next 4 days. You can choose how to display the forecast - perhaps you want to show the min and max temperature for each day, or perhaps you want to show the temperature from the middle of the day, or the humidity, what it feels like and so on. Just make sure to make it all fit nicely with your chosen design. ``` -https://api.openweathermap.org/data/2.5/forecast?q=Stockholm,Sweden&units=metric&APPID=YOUR_API_KEY +https://api.openweathermap.org/data/2.5/forecast?q=Stockholm,Sweden&units=metric&APPID=f1eb0732aa36b48c85267620f68aa926 ``` -The API gives us the next 4-5 days but for every third hour. So a good idea could be to only use the weather data from the same time every day. You can filter the forecast list array to only get the info from 12:00 each day for example. +The API gives us the next 4-5 days but for every third hour. So a good idea could be to only use the weather data from the same time every day. You can filter the forecast list array to only get the info from 12:00 each day for example. Read the [endpoint documentation](https://openweathermap.org/forecast5 "endpoint documentation") for the forecast. @@ -87,6 +93,7 @@ Read the [endpoint documentation](https://openweathermap.org/forecast5 "endpoint Style it to look like one of the provided designs. ## Requirements + - You should fetch data from the API using `fetch()` in JavaScript - The app should have: city name, current temperature, weather description, sunrise/sunset time, 4-day forecast - The presentation of the data should be in the specified format @@ -95,9 +102,11 @@ Style it to look like one of the provided designs. - Follow the guidelines on how to write clean code ## Stretch goals + So you’ve completed the requirements? Great job! Make sure you've committed and pushed a version of your project before starting on the stretch goals. Remember that the stretch goals are optional. ### Intermediate Stretch Goals + **Feature: Styling warm/cold 🌞❄️** Change the colours of the page based on the weather. If the weather is warm – use warm colours. If the weather is colder, use cold colours. If you really want to push your CSS muscles you can even make a background gradient. @@ -107,6 +116,7 @@ Another alternative is to include visual indicators for the type of weather, clo Give the user the option to choose between a couple of your favourite cities, or create a searchbar where the user can search for a specific city. ### Advanced Stretch Goals + **Feature: Use your location 🗺️** Use the [Geolocation API](https://www.w3schools.com/html/html5_geolocation.asp "Geolocation API") that is built into your browser to fetch the city that you are located in at the moment and show the weather for your location. diff --git a/script.js b/script.js new file mode 100644 index 000000000..0f081409b --- /dev/null +++ b/script.js @@ -0,0 +1,97 @@ +/* ********************************* + Constants +********************************* */ + +const API_URL_WEATHER_NOW = + "https://api.openweathermap.org/data/2.5/weather?q=Stockholm,Sweden&units=metric&APPID=f1eb0732aa36b48c85267620f68aa926"; +const API_URL_WEATHER_FORECAST = + "https://api.openweathermap.org/data/2.5/forecast?q=Stockholm,Sweden&units=metric&APPID=f1eb0732aa36b48c85267620f68aa926"; + +/* ********************************* + DOM selectors +********************************* */ + +const app = document.getElementById("app"); +let weatherTodayContainer = document.getElementById("weather-today"); +let weatherForecastContainer = document.getElementById("weather-forecast"); + +/* ********************************* + Global variables +********************************* */ + +let weatherToday = ""; +let weatherTypeToday = ""; + +/* ********************************* + Functions +********************************* */ + +const fetchWeather = async (weather) => { + try { + const response = await fetch(weather); + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + data = await response.json(); + return data; + } catch (error) { + console.error("An error occurred:", error); + } +}; + +// Helper function for changing background color based on type of weather +const typeOfWeather = () => { + switch (weatherTypeToday.toLowerCase()) { + case "clouds": + app.classList.add("is-cloudy"); + break; + case "sun": + app.classList.add("is-sunny"); + break; + default: + break; + } +}; + +// 1. Function to render current weather +const currentWeather = async () => { + const weatherRightNow = await fetchWeather(API_URL_WEATHER_NOW); + + console.log(weatherRightNow); + if (weatherRightNow) { + weatherTypeToday = weatherRightNow.weather[0].main; + const temp = weatherRightNow.main.temp; + const sunrise = new Date( + weatherRightNow.sys.sunrise * 1000 + ).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + hour12: false, + }); + const sunset = new Date( + weatherRightNow.sys.sunset * 1000 + ).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + hour12: false, + }); + + weatherTodayContainer.innerHTML = ` +
+

${weatherTypeToday} | ${temp}°

+

Sunrise ${sunrise}

+

Sunset ${sunset}

+
+ `; + } + + typeOfWeather(); +}; + +currentWeather(); + +// 2. Function to render forecast +const forecastedWeather = async () => { + const forecastData = await fetchWeather(API_URL_WEATHER_FORECAST); + console.log(forecastData); // Log the forecast data to check +}; diff --git a/style.css b/style.css new file mode 100644 index 000000000..e69de29bb From d2a65069f6a158265c958d12e6931c1f4ba5c327 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Sat, 28 Sep 2024 01:37:18 +0200 Subject: [PATCH 02/43] Added forecast + styling --- assets/design-1/Group16.png | Bin 1101 -> 0 bytes assets/design-1/Group34.png | Bin 1159 -> 0 bytes assets/design-1/Group36.png | Bin 1074 -> 0 bytes assets/design-1/Group37.png | Bin 1074 -> 0 bytes assets/design-1/Group38.png | Bin 1074 -> 0 bytes assets/images/.DS_Store | Bin 0 -> 6148 bytes assets/images/clouds-with-sun.svg | 17 + .../cloudy.svg} | 0 assets/images/drizzle.svg | 25 ++ assets/images/rainbow.svg | 4 + .../rainy.svg} | 0 assets/images/snowy.svg | 13 + .../sunny.svg} | 0 assets/images/sunset.svg | 13 + assets/images/thunderstorm.svg | 6 + assets/images/window.svg | 3 + index.html | 28 +- script.js | 292 +++++++++++++++--- style.css | 200 ++++++++++++ 19 files changed, 546 insertions(+), 55 deletions(-) delete mode 100644 assets/design-1/Group16.png delete mode 100644 assets/design-1/Group34.png delete mode 100644 assets/design-1/Group36.png delete mode 100644 assets/design-1/Group37.png delete mode 100644 assets/design-1/Group38.png create mode 100644 assets/images/.DS_Store create mode 100644 assets/images/clouds-with-sun.svg rename assets/{design-2/noun_Cloud_1188486.svg => images/cloudy.svg} (100%) create mode 100644 assets/images/drizzle.svg create mode 100644 assets/images/rainbow.svg rename assets/{design-2/noun_Umbrella_2030530.svg => images/rainy.svg} (100%) create mode 100644 assets/images/snowy.svg rename assets/{design-2/noun_Sunglasses_2055147.svg => images/sunny.svg} (100%) create mode 100644 assets/images/sunset.svg create mode 100644 assets/images/thunderstorm.svg create mode 100644 assets/images/window.svg diff --git a/assets/design-1/Group16.png b/assets/design-1/Group16.png deleted file mode 100644 index c2d0e3f5a573364a03e0fd8e208af5cfffb72ceb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1101 zcmV-T1hV^yP)pORlz0?V7J6`4{8w1>62tk6;xMj&Uhk^s0 z;9Q7e@EYtNWO3Pb6?K0=I)Y@9Xc{($gUB*;vP_thqL95v6da?}m6u_%49d08YfF1q zb4|bzdKAp=pS%2i-{<>ze)oLyLm!sSa%&eNwqk6I0U30SMu=5ADJ+6s@960X@9 zShIG0SU;zeh6{~fPH?P0o}#F^umu3H2uzaX27-v{mq#d+UXKT&W7IHoZvYav?SS}% zXCdXebU{z6We~3Z*n0bvo(hU8Dk{|cyQZdQeAyXO6eVK0z{BL!^tFlGli{hSql5YY z$HA0+5_-))1H-tWpzx?5{D-z?=kUF|Hzg8rtl#ffa)AJbVVFI@1+LA^I1IJ5wPW+e z!4v1_=VRNGlJ6fmTBac)BL$7=X*;xDY=n|y<$$7CNZsGoAxPM~bzzU=Sm4*pH-55; zFwr|Yowl3b41)D~gSp_XceB^6SB4$Wuw@W#^>o4OZxqA$*a&bO2Qe{fh`@=kwdYOs z&aqM3UZc_2gODUyl#!KF|MK1gD^6wK%^om!b%0tE2O^Oucq0CL|Eq^VB9ScHK%KGb zfyd2kLngY3s{x~3|suGM{HK;Xm5izi{AseTp{Sycc&LRuU#=9 zI-QOw|L9}6ToJKiu#k|?z&EC|@Bw*T&}-U03C&Gk-$p3?snPezN)3VtUSg|i=+|F5 zK_-*KuJo*sbw1x5)Svn65f8Q$A1i+*0>@Vc5EA$QaR+Qx3miC97*gV9Tu^8Hl;LOG z`pUk8hmuluJ`wQvs11I<)dL6fibDELjE};1&5hlBsxvmKH0>p&AFg>46?S#;{8x~Zy9c6UR7)zH4jRsx>U;In z$;^dBeNt8R#m@8>O42ei|2O>mOW#6tvu zlV@;($lH>bq|$7P2b>^);{w13J`h;!HNWPY^TNb~F&G*gfY`XrkhJ5!ta!a1py_EC zxo>d{TL%088u&A}rKN>kxH2ENBuR3sR31hItu=NtDavKCj24L@^XNmKF1Q zxdNYaGqVzjRLWtP*yVA%Fa$+eg#w>qnAvqGim@!~r==2k-^>hs!DuwLKF;|UPMC>g TTmc{V00000NkvXXu0mjf?e`Gl diff --git a/assets/design-1/Group34.png b/assets/design-1/Group34.png deleted file mode 100644 index f7e90e552c1375d85f1654594bb8d042479df958..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1159 zcmV;21bF+2P)X1^@s6%>pGE00004b3#c}2nYxW zdZg;K_kO@w7GI*Hj2WJ9Ckl4;0>PF?0g zoVu~N1VVfH(X>FOZ!8fgiSJe#Q4&Ioc#e2wO*C<^J>X?)!ev=iT$pJNF`3rhzg39soWhgj@_35(N`1_PJWE?)7@Tg|)S{ zKLztF8x&*Acs!mjj7H;?WreEM>g@oq(_k>D=LW_YZ$Jpi`8kGRIs!rS^73RH$BF9d z>L!=GQjGC70LUVQXfVdRghFVNM+)8H62$Sa>tpUHkq8}gySaQq$P_=PR;yJAp*4gM z%-;uvVvG}9E?1Pt;~A>2uXp=bq0tmJ$z{}Q@9&P4t%?)PF3#cfLQDG;+jq%fak;%) z03ZPX6oil;{GxzRK8;eTOeYqLlANqFJ|Qt_=GxW47^yU393iU3W}EI(MhiYG%8i$W zOQ?XKu%piktsT=BDyu(v_I|+w(29zq-$h0#($mtOTeRHLU0PcH<&Ny#`_?^`x@e$Zc)aOz*tXdMNr_^(dYuK<#X*FWg0+u_!LOGcaQfWT zH+IL}3XbE}-}6kNf`WpL$ti13?c1N1yksH(uq+Fwzy1_%j$0rlISCX>B{=Lh82W1% zBEz}6qhm8Agb*?>y;`l-_(J9@`RQ97cH_11h`Zm+4)M*;wcN=wT|a}O1+ULlVPY79kiqS-Vt zaHW5b!C>e_TCMI(*3P%G*Qag>C1y}>%bAlSzg+0y_uEqlWBlgk^yeF2&Dymhs51mX zb5jGq(j24Fc;43>bh^?T?;glcRL0I0SP0<*TdR)hJZAHqNR!Fr^g;PeI6h%j>wypO z;(Oo{UaSAoMQA(w?eWUW%Duko^?Pe-$QA%^+j|EK_zGLr!RS9ShlPn_X`1%VgYq{j zmFngA#MKR3GhRwbeP&b0;J=3l;Y4$zGa@2FPSdn&wmIA%xwyDkDHclz%R1iPl$K!^ z3Ptj0MGUxHEQrNnzUzO3Pm=G1;u}Jt7(5=APh*?5ItK>&Z8q!l@XU-olVKRgT+Ty} zD>O}u&1UlsilUz2I8MkPIVg&n@_N1gADU0q?6HC4I2%Gp1^~A?j=Lie2znWY`7=3FwcS=X(wLyJGm4nG#KaE@lPDUq528_l zU?iF;+ph0*Ev*~tM9r8E+k0=FR%${74G=Zz1T`uyi^hG>K>{&gNEjCCkd?M89ow$G z?KLHKnM-@zT}X`azP``z`906`d;a7g(D`{hp7Km4Gm0^;)9C_0y+E(mYmy`>f-$~P zuaEyGd2@5Kv8=3Yk0^@IT(`_Qj;l8q4CnoR|0TUn)#3`49U=53gm3_3{Pj{t4u|75 znx^XsAq~|Q+CCANe}xbpl_cpzC=?n~B5rMMt)wU_8w>_7=j@7Vju0B8C@Lrj!jZhg z?H$hVSp$7{O*KQ^dNXUb)f&L>Q3;$qCrwVK=VsDZ=7)QF0~-qkfDj5o2!F#EznyQp3EB-B38^i5A5yT_DDHP9c+Ep zYEq-+m6q4 z#?H-x{a+*w`*!%75JDjc;X?BeLe4;h(9;mYvW3BiFewPaU}s1B@P;PK>Xr6|C(S|p z_)8ib9!iD#b_p-?JiiG5usD;LQp3Ia=xS5cMHqdY77=zO$n&AmVomYvLO0r2E-EC_~|hzo=RsL2L=Ylb2y&o z-Gq>cD2gAeDtlX7+j53se#RKv3IPy8PXGY!saVFWr5OU}=Rr1=p8J*%a=gF4e{d&<=x?MG%^g62m}Jh7vofEUWka}IF_bqH^#U}OCZ;U s{QvvY@ULc`YgVVzX%++_p;z<20G0-ok{TUnlmGw#07*qoM6N<$f@n7UssI20 diff --git a/assets/design-1/Group37.png b/assets/design-1/Group37.png deleted file mode 100644 index ce9c147cfbcf523c7422d6d0fe743be6dfcd14d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1074 zcmV-21kL-2P)3FwcS=X(wLyJGm4nG#KaE@lPDUq528_l zU?iF;+ph0*Ev*~tM9r8E+k0=FR%${74G=Zz1T`uyi^hG>K>{&gNEjCCkd?M89ow$G z?KLHKnM-@zT}X`azP``z`906`d;a7g(D`{hp7Km4Gm0^;)9C_0y+E(mYmy`>f-$~P zuaEyGd2@5Kv8=3Yk0^@IT(`_Qj;l8q4CnoR|0TUn)#3`49U=53gm3_3{Pj{t4u|75 znx^XsAq~|Q+CCANe}xbpl_cpzC=?n~B5rMMt)wU_8w>_7=j@7Vju0B8C@Lrj!jZhg z?H$hVSp$7{O*KQ^dNXUb)f&L>Q3;$qCrwVK=VsDZ=7)QF0~-qkfDj5o2!F#EznyQp3EB-B38^i5A5yT_DDHP9c+Ep zYEq-+m6q4 z#?H-x{a+*w`*!%75JDjc;X?BeLe4;h(9;mYvW3BiFewPaU}s1B@P;PK>Xr6|C(S|p z_)8ib9!iD#b_p-?JiiG5usD;LQp3Ia=xS5cMHqdY77=zO$n&AmVomYvLO0r2E-EC_~|hzo=RsL2L=Ylb2y&o z-Gq>cD2gAeDtlX7+j53se#RKv3IPy8PXGY!saVFWr5OU}=Rr1=p8J*%a=gF4e{d&<=x?MG%^g62m}Jh7vofEUWka}IF_bqH^#U}OCZ;U s{QvvY@ULc`YgVVzX%++_p;z<20G0-ok{TUnlmGw#07*qoM6N<$f@n7UssI20 diff --git a/assets/design-1/Group38.png b/assets/design-1/Group38.png deleted file mode 100644 index ce9c147cfbcf523c7422d6d0fe743be6dfcd14d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1074 zcmV-21kL-2P)3FwcS=X(wLyJGm4nG#KaE@lPDUq528_l zU?iF;+ph0*Ev*~tM9r8E+k0=FR%${74G=Zz1T`uyi^hG>K>{&gNEjCCkd?M89ow$G z?KLHKnM-@zT}X`azP``z`906`d;a7g(D`{hp7Km4Gm0^;)9C_0y+E(mYmy`>f-$~P zuaEyGd2@5Kv8=3Yk0^@IT(`_Qj;l8q4CnoR|0TUn)#3`49U=53gm3_3{Pj{t4u|75 znx^XsAq~|Q+CCANe}xbpl_cpzC=?n~B5rMMt)wU_8w>_7=j@7Vju0B8C@Lrj!jZhg z?H$hVSp$7{O*KQ^dNXUb)f&L>Q3;$qCrwVK=VsDZ=7)QF0~-qkfDj5o2!F#EznyQp3EB-B38^i5A5yT_DDHP9c+Ep zYEq-+m6q4 z#?H-x{a+*w`*!%75JDjc;X?BeLe4;h(9;mYvW3BiFewPaU}s1B@P;PK>Xr6|C(S|p z_)8ib9!iD#b_p-?JiiG5usD;LQp3Ia=xS5cMHqdY77=zO$n&AmVomYvLO0r2E-EC_~|hzo=RsL2L=Ylb2y&o z-Gq>cD2gAeDtlX7+j53se#RKv3IPy8PXGY!saVFWr5OU}=Rr1=p8J*%a=gF4e{d&<=x?MG%^g62m}Jh7vofEUWka}IF_bqH^#U}OCZ;U s{QvvY@ULc`YgVVzX%++_p;z<20G0-ok{TUnlmGw#07*qoM6N<$f@n7UssI20 diff --git a/assets/images/.DS_Store b/assets/images/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + + + + + + + + + + + + + + + + diff --git a/assets/design-2/noun_Cloud_1188486.svg b/assets/images/cloudy.svg similarity index 100% rename from assets/design-2/noun_Cloud_1188486.svg rename to assets/images/cloudy.svg diff --git a/assets/images/drizzle.svg b/assets/images/drizzle.svg new file mode 100644 index 000000000..727bd8bc4 --- /dev/null +++ b/assets/images/drizzle.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/rainbow.svg b/assets/images/rainbow.svg new file mode 100644 index 000000000..c4bccae66 --- /dev/null +++ b/assets/images/rainbow.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/design-2/noun_Umbrella_2030530.svg b/assets/images/rainy.svg similarity index 100% rename from assets/design-2/noun_Umbrella_2030530.svg rename to assets/images/rainy.svg diff --git a/assets/images/snowy.svg b/assets/images/snowy.svg new file mode 100644 index 000000000..1719f812a --- /dev/null +++ b/assets/images/snowy.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/design-2/noun_Sunglasses_2055147.svg b/assets/images/sunny.svg similarity index 100% rename from assets/design-2/noun_Sunglasses_2055147.svg rename to assets/images/sunny.svg diff --git a/assets/images/sunset.svg b/assets/images/sunset.svg new file mode 100644 index 000000000..30b425b88 --- /dev/null +++ b/assets/images/sunset.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/images/thunderstorm.svg b/assets/images/thunderstorm.svg new file mode 100644 index 000000000..77a754317 --- /dev/null +++ b/assets/images/thunderstorm.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/window.svg b/assets/images/window.svg new file mode 100644 index 000000000..fdae950db --- /dev/null +++ b/assets/images/window.svg @@ -0,0 +1,3 @@ + + + diff --git a/index.html b/index.html index 8973dc214..89041970d 100644 --- a/index.html +++ b/index.html @@ -6,24 +6,22 @@ The Weatherington - -
-
-
- Sungglasses -

Get your sunnies on. Stockholm is looking rather great today.

+ +
+ +
+

Loading...

-
    -
  • -

    mon

    -

    23°

    -
  • -
+
    diff --git a/script.js b/script.js index 0f081409b..5348f91fb 100644 --- a/script.js +++ b/script.js @@ -1,66 +1,147 @@ +/* ********************************* + Global variables +********************************* */ + +let apiQueryCity = "London"; +let displayedCityName = "London"; +let weatherTypeToday = ""; + /* ********************************* Constants ********************************* */ -const API_URL_WEATHER_NOW = - "https://api.openweathermap.org/data/2.5/weather?q=Stockholm,Sweden&units=metric&APPID=f1eb0732aa36b48c85267620f68aa926"; -const API_URL_WEATHER_FORECAST = - "https://api.openweathermap.org/data/2.5/forecast?q=Stockholm,Sweden&units=metric&APPID=f1eb0732aa36b48c85267620f68aa926"; +const API_KEY = "f1eb0732aa36b48c85267620f68aa926"; +let API_URL_WEATHER_NOW = `https://api.openweathermap.org/data/2.5/weather?q=${apiQueryCity}&units=metric&APPID=${API_KEY}`; +let API_URL_WEATHER_FORECAST = `https://api.openweathermap.org/data/2.5/forecast?q=${apiQueryCity}&units=metric&APPID=${API_KEY}`; /* ********************************* DOM selectors ********************************* */ const app = document.getElementById("app"); -let weatherTodayContainer = document.getElementById("weather-today"); -let weatherForecastContainer = document.getElementById("weather-forecast"); +const weatherTodayContainer = document.getElementById("weather-today"); +const weatherForecastContainer = document.getElementById("weather-forecast"); /* ********************************* - Global variables + Weather types ********************************* */ -let weatherToday = ""; -let weatherTypeToday = ""; +const weatherTypes = { + clouds: { + className: "is-cloudy", + mainTitle: (city) => + `Light a fire and get cosy. ${city} is looking grey today.`, + imgSrc: "./assets/images/cloudy.svg", + imgAlt: "Clouds", + }, + clear: { + className: "is-clear", + mainTitle: (city) => + `Get your sunnies on. ${city} is looking rather great today.`, + imgSrc: "./assets/images/sunny.svg", + imgAlt: "Sunglasses", + }, + drizzle: { + className: "is-drizzly", + mainTitle: (city) => + `There's a light drizzle in ${city}. Keep your raincoat handy!`, + imgSrc: "./assets/images/drizzle.svg", + imgAlt: "Raincoat", + }, + rain: { + className: "is-rainy", + mainTitle: (city) => + `Get your umbrella! ${city} is looking rather rainy today.`, + imgSrc: "./assets/images/rainy.svg", + imgAlt: "Umbrella", + }, + snow: { + className: "is-snowy", + mainTitle: (city) => + `Time for snow boots! ${city} is a winter wonderland today.`, + imgSrc: "./assets/images/snowy.svg", + imgAlt: "Snowflake", + }, + thunderstorm: { + className: "is-thunderstorm", + mainTitle: (city) => + `Stay safe indoors! ${city} is rumbling with a thunderstorm today.`, + imgSrc: "./assets/images/thunderstorm.svg", + imgAlt: "Thunder and clouds", + }, + default: { + className: "", + mainTitle: (city) => + `The weather in ${city} can't be determined today. Just go outside and have a look.`, + imgSrc: "./assets/images/window.svg", + imgAlt: "Window", + }, +}; /* ********************************* Functions ********************************* */ -const fetchWeather = async (weather) => { +const fetchWeather = async (weatherUrl) => { try { - const response = await fetch(weather); + const response = await fetch(weatherUrl); + if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); + let errorMessage = `HTTP error! Status: ${response.status}`; + + // Attempt to parse the error response + try { + const errorData = await response.json(); + if (errorData && errorData.message) { + errorMessage = errorData.message; + } + } catch (parseError) { + // If parsing fails, keep the default message + } + + throw new Error(errorMessage); } - data = await response.json(); + + const data = await response.json(); return data; } catch (error) { - console.error("An error occurred:", error); + if (error.name === "TypeError" && error.message === "Failed to fetch") { + // Network error + throw new Error("Network error. Please check your internet connection."); + } else { + throw error; // Re-throw the error to be handled later + } } }; -// Helper function for changing background color based on type of weather -const typeOfWeather = () => { - switch (weatherTypeToday.toLowerCase()) { - case "clouds": - app.classList.add("is-cloudy"); - break; - case "sun": - app.classList.add("is-sunny"); - break; - default: - break; - } +// Function for having different content based on current weather +const typeOfWeather = (weatherType, cityName) => { + const type = weatherTypes[weatherType.toLowerCase()] || weatherTypes.default; + app.classList.add(type.className); + + return { + mainTitle: type.mainTitle(cityName), + imgSrc: type.imgSrc, + imgAlt: type.imgAlt, + }; }; // 1. Function to render current weather const currentWeather = async () => { - const weatherRightNow = await fetchWeather(API_URL_WEATHER_NOW); + try { + const weatherRightNow = await fetchWeather(API_URL_WEATHER_NOW); - console.log(weatherRightNow); - if (weatherRightNow) { + if (!weatherRightNow) { + weatherTodayContainer.innerHTML = ` +

    Rain check on the weather!

    +

    Looks like today’s forecast is a no-show. Check back in a bit!

    + `; + return; + } + + displayedCityName = weatherRightNow.name; weatherTypeToday = weatherRightNow.weather[0].main; - const temp = weatherRightNow.main.temp; + const temp = Math.round(weatherRightNow.main.temp); const sunrise = new Date( weatherRightNow.sys.sunrise * 1000 ).toLocaleTimeString([], { @@ -76,22 +157,153 @@ const currentWeather = async () => { hour12: false, }); + // Get mainTitle, imgSrc and imgAlt from typeOfWeather + const { mainTitle, imgSrc, imgAlt } = typeOfWeather( + weatherTypeToday, + displayedCityName + ); + weatherTodayContainer.innerHTML = ` -
    -

    ${weatherTypeToday} | ${temp}°

    -

    Sunrise ${sunrise}

    -

    Sunset ${sunset}

    -
    - `; - } +
    +

    ${weatherTypeToday} — ${temp}°

    +

    Sunrise ${sunrise}

    +

    Sunset ${sunset}

    +
    - typeOfWeather(); +
    + ${imgAlt} +

    ${mainTitle}

    +
    + `; + } catch (error) { + console.log(error); + weatherTodayContainer.innerHTML = ` +

    Oh no!

    +

    ${error.message}

    + `; + } }; currentWeather(); // 2. Function to render forecast const forecastedWeather = async () => { - const forecastData = await fetchWeather(API_URL_WEATHER_FORECAST); - console.log(forecastData); // Log the forecast data to check + try { + const forecastData = await fetchWeather(API_URL_WEATHER_FORECAST); + + if (!forecastData) { + weatherForecastContainer.innerHTML = ` +

    The forecast is no where to be seen.

    + `; + return; + } + + // Step 1: Get today's date in 'YYYY-MM-DD' format + const today = new Date().toISOString().split("T")[0]; // This gives 'YYYY-MM-DD' + + // Step 2: Filter out entries that have today's date + const forecastArray = forecastData.list.filter( + (item) => item.dt_txt.slice(0, 10) !== today + ); + + // Initialize an array to store the results for each day + let dailyTemperatures = []; + + // Loop over forecastArray in steps of 8 to process each day + for (let i = 0; i < forecastArray.length; i += 8) { + // Initialize variables to store the lowest temp_min and highest temp_max for the day + let lowestTempMin = Infinity; + let highestTempMax = -Infinity; + + // Log weekday + const date = new Date(forecastArray[i].dt_txt); + const weekdayNumber = date.getDay(); + const weekdays = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"]; + const weekday = weekdays[weekdayNumber]; + + // Loop through each of the 8 items for the current day + for (let j = i; j < i + 8 && j < forecastArray.length; j++) { + let tempMin = forecastArray[j].main.temp_min; + let tempMax = forecastArray[j].main.temp_max; + + // Update the lowest temp_min if a lower value is found + if (tempMin < lowestTempMin) { + lowestTempMin = tempMin; + } + + // Update the highest temp_max if a higher value is found + if (tempMax > highestTempMax) { + highestTempMax = tempMax; + } + } + + // Store the results for the current day + dailyTemperatures.push({ + weekday: weekday, + lowestTempMin: lowestTempMin, + highestTempMax: highestTempMax, + }); + } + + const forecastList = document.getElementById("weather-forecast__list"); + const forecastListItem = document.createDocumentFragment(); + + dailyTemperatures.forEach((day) => { + const li = document.createElement("li"); + li.innerHTML = ` + +

    ${day.weekday}

    +
    + +

    ${Math.round(day.highestTempMax)}°

    + / +

    ${Math.round(day.lowestTempMin)}°

    +
    + `; + forecastListItem.appendChild(li); + }); + + forecastList.innerHTML = ""; // Clear previous list items + forecastList.appendChild(forecastListItem); + } catch (error) { + weatherForecastContainer.innerHTML = ` +

    Couldn't catch the forecast

    + `; + } +}; + +forecastedWeather(); + +// Function to handle the search functionality +const handleSearch = () => { + event.preventDefault(); + + let city = document.getElementById("city-input").value.trim(); + + apiQueryCity = city; + // Update API URLs with the new city + updateApiUrls(city); + // Fetch new data + currentWeather(); + forecastedWeather(); +}; + +// Listen for click on the search button +document + .getElementById("search-button") + .addEventListener("click", handleSearch); + +// Listen for Enter key press in the input field +document.getElementById("city-input").addEventListener("keydown", (event) => { + if (event.key === "Enter") { + handleSearch(); + } +}); + +const updateApiUrls = (city) => { + API_URL_WEATHER_NOW = `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&APPID=${API_KEY}`; + API_URL_WEATHER_FORECAST = `https://api.openweathermap.org/data/2.5/forecast?q=${city}&units=metric&APPID=${API_KEY}`; }; diff --git a/style.css b/style.css index e69de29bb..3681cdede 100644 --- a/style.css +++ b/style.css @@ -0,0 +1,200 @@ +@import url("https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap"); + +:root { + --blue-light: #bde8fa; + --blue-dark: #164a68; + --yellow-light: #f7e9b9; + --yellow-dark: #2a5510; + --brown-light: #f1eaea; + --brown-dark: #645353; +} + +*, +:after, +:before { + box-sizing: border-box; +} + +body { + font-family: "Montserrat", sans-serif; + padding: 0; + margin: 0; + + display: flex; + justify-content: center; + align-items: center; + min-height: 100svh; +} + +body.is-rainy, +body.is-snowy, +body.is-drizzly { + color: var(--blue-dark); + background-color: var(--blue-light); +} + +body.is-clear, +body.is-thunderstorm { + color: var(--yellow-dark); + background-color: var(--yellow-light); +} + +body.is-cloudy, +body.is-atmosphere { + color: var(--brown-dark); + background-color: var(--brown-light); +} + +main { + padding: 1.5rem; + max-width: 500px; + display: flex; + flex-direction: column; + min-height: 100vh; + align-items: stretch; + justify-content: space-between; + margin-bottom: 54px; + + @media (min-width: 375px) { + padding: 2.5rem; + } + + @media (min-width: 668px) { + min-height: fit-content; + } +} + +h1 { + font-size: 2rem; + margin-top: 1rem; + margin-bottom: 2.5rem; + + @media (min-width: 375px) { + font-size: 2.25rem; + } +} + +.weather-today__meta { + display: flex; + flex-flow: column; + gap: 0.25rem; + margin-bottom: 2.5rem; + + p { + font-size: 1rem; + margin: 0; + + @media (min-width: 375px) { + font-size: 1.25rem; + } + } +} + +.weather-forecast__list { + display: flex; + flex-direction: column; + gap: 1rem; + list-style: none; + padding: 0; + margin: 0; + + li { + display: flex; + justify-content: space-between; + position: relative; + + .list-section { + display: flex; + gap: 0.5rem; + + * { + font-weight: 400; + font-size: 1rem; + margin: 0; + + @media (min-width: 375px) { + font-size: 1.25rem; + } + } + } + + &:after { + position: absolute; + content: ""; + width: 100%; + bottom: -10px; + border-bottom: 1px dashed; + } + + &:last-of-type:after { + border-bottom: none; + } + } +} + +.error-message { + &::first-letter { + text-transform: capitalize; + } +} + +.city-search { + position: fixed; + z-index: 5; + bottom: 0; + left: 0; + width: 100%; + + background-color: currentColor; + padding: 1rem; + + @media (min-width: 678px) { + padding: 2rem; + } + + .form-container { + display: flex; + gap: 1rem; + + @media (min-width: 678px) { + gap: 2rem; + } + } + + label { + flex-grow: 2; + span { + position: absolute; + opacity: 0; + } + } + + input { + font-size: 1rem; + border: none; + border-radius: 0.25rem; + padding: 0.5rem 0.75rem; + width: 100%; + + @media (min-width: 678px) { + font-size: 1.125rem; + padding: 1rem 1.5rem; + } + } + + button { + background-color: #fff; + color: currentColor; + font-size: 1rem; + font-weight: 700; + border: none; + border-radius: 2rem; + box-shadow: none; + padding: 0.5rem 1.25rem; + + @media (min-width: 678px) { + font-size: 1.125rem; + padding: 1rem 1.75rem; + } + } +} From a92bef3f01bd1cb30697e9d51642df1de7e44fa3 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Sun, 29 Sep 2024 17:35:04 +0200 Subject: [PATCH 03/43] Restructured code, added day/night functionality, inline input search, etc --- .DS_Store | Bin 0 -> 6148 bytes README.md | 14 +- assets/.DS_Store | Bin 0 -> 6148 bytes assets/animated/.DS_Store | Bin 0 -> 6148 bytes assets/animated/alert-avalanche-danger.svg | 1 + assets/animated/alert-falling-rocks.svg | 1 + assets/animated/barometer.svg | 1 + assets/animated/beanie.svg | 1 + assets/animated/celsius.svg | 1 + assets/animated/clear-day.svg | 1 + assets/animated/clear-night.svg | 1 + assets/animated/cloud-down.svg | 1 + assets/animated/cloud-up.svg | 1 + assets/animated/cloudy.svg | 1 + assets/animated/code-green.svg | 1 + assets/animated/code-orange.svg | 1 + assets/animated/code-red.svg | 1 + assets/animated/code-yellow.svg | 1 + assets/animated/compass.svg | 1 + assets/animated/drizzle.svg | 1 + assets/animated/dust-day.svg | 1 + assets/animated/dust-night.svg | 1 + assets/animated/dust-wind.svg | 1 + assets/animated/dust.svg | 1 + assets/animated/extreme-day-drizzle.svg | 1 + assets/animated/extreme-day-fog.svg | 1 + assets/animated/extreme-day-hail.svg | 1 + assets/animated/extreme-day-haze.svg | 1 + assets/animated/extreme-day-rain.svg | 1 + assets/animated/extreme-day-sleet.svg | 1 + assets/animated/extreme-day-smoke.svg | 1 + assets/animated/extreme-day-snow.svg | 1 + assets/animated/extreme-day.svg | 1 + assets/animated/extreme-drizzle.svg | 1 + assets/animated/extreme-fog.svg | 1 + assets/animated/extreme-hail.svg | 1 + assets/animated/extreme-haze.svg | 1 + assets/animated/extreme-night-drizzle.svg | 1 + assets/animated/extreme-night-fog.svg | 1 + assets/animated/extreme-night-hail.svg | 1 + assets/animated/extreme-night-haze.svg | 1 + assets/animated/extreme-night-rain.svg | 1 + assets/animated/extreme-night-sleet.svg | 1 + assets/animated/extreme-night-smoke.svg | 1 + assets/animated/extreme-night-snow.svg | 1 + assets/animated/extreme-night.svg | 1 + assets/animated/extreme-rain.svg | 1 + assets/animated/extreme-sleet.svg | 1 + assets/animated/extreme-smoke.svg | 1 + assets/animated/extreme-snow.svg | 1 + assets/animated/extreme.svg | 1 + assets/animated/fahrenheit.svg | 1 + assets/animated/falling-stars.svg | 1 + assets/animated/flag-gale-warning.svg | 1 + assets/animated/flag-hurricane-warning.svg | 1 + assets/animated/flag-small-craft-advisory.svg | 1 + assets/animated/flag-storm-warning.svg | 1 + assets/animated/fog-day.svg | 1 + assets/animated/fog-night.svg | 1 + assets/animated/fog.svg | 1 + assets/animated/glove.svg | 1 + assets/animated/hail.svg | 1 + assets/animated/haze-day.svg | 1 + assets/animated/haze-night.svg | 1 + assets/animated/haze.svg | 1 + assets/animated/horizon.svg | 1 + assets/animated/humidity.svg | 1 + assets/animated/hurricane.svg | 1 + assets/animated/lightning-bolt.svg | 1 + assets/animated/mist.svg | 1 + assets/animated/moon-first-quarter.svg | 1 + assets/animated/moon-full.svg | 1 + assets/animated/moon-last-quarter.svg | 1 + assets/animated/moon-new.svg | 1 + assets/animated/moon-waning-crescent.svg | 1 + assets/animated/moon-waning-gibbous.svg | 1 + assets/animated/moon-waxing-crescent.svg | 1 + assets/animated/moon-waxing-gibbous.svg | 1 + assets/animated/moonrise.svg | 1 + assets/animated/moonset.svg | 1 + assets/animated/not-available.svg | 1 + assets/animated/overcast-day-drizzle.svg | 1 + assets/animated/overcast-day-fog.svg | 1 + assets/animated/overcast-day-hail.svg | 1 + assets/animated/overcast-day-haze.svg | 1 + assets/animated/overcast-day-rain.svg | 1 + assets/animated/overcast-day-sleet.svg | 1 + assets/animated/overcast-day-smoke.svg | 1 + assets/animated/overcast-day-snow.svg | 1 + assets/animated/overcast-day.svg | 1 + assets/animated/overcast-drizzle.svg | 1 + assets/animated/overcast-fog.svg | 1 + assets/animated/overcast-hail.svg | 1 + assets/animated/overcast-haze.svg | 1 + assets/animated/overcast-night-drizzle.svg | 1 + assets/animated/overcast-night-fog.svg | 1 + assets/animated/overcast-night-hail.svg | 1 + assets/animated/overcast-night-haze.svg | 1 + assets/animated/overcast-night-rain.svg | 1 + assets/animated/overcast-night-sleet.svg | 1 + assets/animated/overcast-night-smoke.svg | 1 + assets/animated/overcast-night-snow.svg | 1 + assets/animated/overcast-night.svg | 1 + assets/animated/overcast-rain.svg | 1 + assets/animated/overcast-sleet.svg | 1 + assets/animated/overcast-smoke.svg | 1 + assets/animated/overcast-snow.svg | 1 + assets/animated/overcast.svg | 1 + assets/animated/partly-cloudy-day-drizzle.svg | 1 + assets/animated/partly-cloudy-day-fog.svg | 1 + assets/animated/partly-cloudy-day-hail.svg | 1 + assets/animated/partly-cloudy-day-haze.svg | 1 + assets/animated/partly-cloudy-day-rain.svg | 1 + assets/animated/partly-cloudy-day-sleet.svg | 1 + assets/animated/partly-cloudy-day-smoke.svg | 1 + assets/animated/partly-cloudy-day-snow.svg | 1 + assets/animated/partly-cloudy-day.svg | 1 + .../animated/partly-cloudy-night-drizzle.svg | 1 + assets/animated/partly-cloudy-night-fog.svg | 1 + assets/animated/partly-cloudy-night-hail.svg | 1 + assets/animated/partly-cloudy-night-haze.svg | 1 + assets/animated/partly-cloudy-night-rain.svg | 1 + assets/animated/partly-cloudy-night-sleet.svg | 1 + assets/animated/partly-cloudy-night-smoke.svg | 1 + assets/animated/partly-cloudy-night-snow.svg | 1 + assets/animated/partly-cloudy-night.svg | 1 + assets/animated/pollen-flower.svg | 1 + assets/animated/pollen-grass.svg | 1 + assets/animated/pollen-tree.svg | 1 + assets/animated/pollen.svg | 1 + assets/animated/pressure-high-alt.svg | 1 + assets/animated/pressure-high.svg | 1 + assets/animated/pressure-low-alt.svg | 1 + assets/animated/pressure-low.svg | 1 + assets/animated/rain.svg | 1 + assets/animated/rainbow-clear.svg | 1 + assets/animated/rainbow.svg | 1 + assets/animated/raindrop-measure.svg | 1 + assets/animated/raindrop.svg | 1 + assets/animated/raindrops.svg | 1 + assets/animated/sleet.svg | 1 + assets/animated/smoke-particles.svg | 1 + assets/animated/smoke.svg | 1 + assets/animated/snow.svg | 1 + assets/animated/snowflake.svg | 1 + assets/animated/snowman.svg | 1 + assets/animated/solar-eclipse.svg | 1 + assets/animated/star.svg | 1 + assets/animated/starry-night.svg | 1 + assets/animated/sun-hot.svg | 1 + assets/animated/sunrise.svg | 1 + assets/animated/sunset.svg | 1 + assets/animated/thermometer-celsius.svg | 1 + assets/animated/thermometer-colder.svg | 1 + assets/animated/thermometer-fahrenheit.svg | 1 + assets/animated/thermometer-glass-celsius.svg | 1 + .../animated/thermometer-glass-fahrenheit.svg | 1 + assets/animated/thermometer-glass.svg | 1 + assets/animated/thermometer-mercury-cold.svg | 1 + assets/animated/thermometer-mercury.svg | 1 + assets/animated/thermometer-moon.svg | 1 + assets/animated/thermometer-raindrop.svg | 1 + assets/animated/thermometer-snow.svg | 1 + assets/animated/thermometer-sun.svg | 1 + assets/animated/thermometer-warmer.svg | 1 + assets/animated/thermometer-water.svg | 1 + assets/animated/thermometer.svg | 1 + .../thunderstorms-day-extreme-rain.svg | 1 + .../thunderstorms-day-extreme-snow.svg | 1 + assets/animated/thunderstorms-day-extreme.svg | 1 + .../thunderstorms-day-overcast-rain.svg | 1 + .../thunderstorms-day-overcast-snow.svg | 1 + .../animated/thunderstorms-day-overcast.svg | 1 + assets/animated/thunderstorms-day-rain.svg | 1 + assets/animated/thunderstorms-day-snow.svg | 1 + assets/animated/thunderstorms-day.svg | 1 + .../animated/thunderstorms-extreme-rain.svg | 1 + .../animated/thunderstorms-extreme-snow.svg | 1 + assets/animated/thunderstorms-extreme.svg | 1 + .../thunderstorms-night-extreme-rain.svg | 1 + .../thunderstorms-night-extreme-snow.svg | 1 + .../animated/thunderstorms-night-extreme.svg | 1 + .../thunderstorms-night-overcast-rain.svg | 1 + .../thunderstorms-night-overcast-snow.svg | 1 + .../animated/thunderstorms-night-overcast.svg | 1 + assets/animated/thunderstorms-night-rain.svg | 1 + assets/animated/thunderstorms-night-snow.svg | 1 + assets/animated/thunderstorms-night.svg | 1 + .../animated/thunderstorms-overcast-rain.svg | 1 + .../animated/thunderstorms-overcast-snow.svg | 1 + assets/animated/thunderstorms-overcast.svg | 1 + assets/animated/thunderstorms-rain.svg | 1 + assets/animated/thunderstorms-snow.svg | 1 + assets/animated/thunderstorms.svg | 1 + assets/animated/tide-high.svg | 1 + assets/animated/tide-low.svg | 1 + assets/animated/time-afternoon.svg | 1 + assets/animated/time-evening.svg | 1 + assets/animated/time-late-afternoon.svg | 1 + assets/animated/time-late-evening.svg | 1 + assets/animated/time-late-morning.svg | 1 + assets/animated/time-late-night.svg | 1 + assets/animated/time-morning.svg | 1 + assets/animated/time-night.svg | 1 + assets/animated/tornado.svg | 1 + assets/animated/umbrella-wind-alt.svg | 1 + assets/animated/umbrella-wind.svg | 1 + assets/animated/umbrella.svg | 1 + assets/animated/uv-index-1.svg | 1 + assets/animated/uv-index-10.svg | 1 + assets/animated/uv-index-11.svg | 1 + assets/animated/uv-index-2.svg | 1 + assets/animated/uv-index-3.svg | 1 + assets/animated/uv-index-4.svg | 1 + assets/animated/uv-index-5.svg | 1 + assets/animated/uv-index-6.svg | 1 + assets/animated/uv-index-7.svg | 1 + assets/animated/uv-index-8.svg | 1 + assets/animated/uv-index-9.svg | 1 + assets/animated/uv-index.svg | 1 + assets/animated/wind-alert.svg | 1 + assets/animated/wind-beaufort-0.svg | 1 + assets/animated/wind-beaufort-1.svg | 1 + assets/animated/wind-beaufort-10.svg | 1 + assets/animated/wind-beaufort-11.svg | 1 + assets/animated/wind-beaufort-12.svg | 1 + assets/animated/wind-beaufort-2.svg | 1 + assets/animated/wind-beaufort-3.svg | 1 + assets/animated/wind-beaufort-4.svg | 1 + assets/animated/wind-beaufort-5.svg | 1 + assets/animated/wind-beaufort-6.svg | 1 + assets/animated/wind-beaufort-7.svg | 1 + assets/animated/wind-beaufort-8.svg | 1 + assets/animated/wind-beaufort-9.svg | 1 + assets/animated/wind-offshore.svg | 1 + assets/animated/wind-onshore.svg | 1 + assets/animated/wind-snow.svg | 1 + assets/animated/wind.svg | 1 + assets/animated/windsock-weak.svg | 1 + assets/animated/windsock.svg | 1 + assets/images/cloudy.svg | 2 +- favicon.svg | 1 + index.html | 11 +- pull_request_template.md | 3 - script.js | 634 ++++++++++++++---- style.css | 87 +-- 246 files changed, 768 insertions(+), 220 deletions(-) create mode 100644 .DS_Store create mode 100644 assets/.DS_Store create mode 100644 assets/animated/.DS_Store create mode 100644 assets/animated/alert-avalanche-danger.svg create mode 100644 assets/animated/alert-falling-rocks.svg create mode 100644 assets/animated/barometer.svg create mode 100644 assets/animated/beanie.svg create mode 100644 assets/animated/celsius.svg create mode 100644 assets/animated/clear-day.svg create mode 100644 assets/animated/clear-night.svg create mode 100644 assets/animated/cloud-down.svg create mode 100644 assets/animated/cloud-up.svg create mode 100644 assets/animated/cloudy.svg create mode 100644 assets/animated/code-green.svg create mode 100644 assets/animated/code-orange.svg create mode 100644 assets/animated/code-red.svg create mode 100644 assets/animated/code-yellow.svg create mode 100644 assets/animated/compass.svg create mode 100644 assets/animated/drizzle.svg create mode 100644 assets/animated/dust-day.svg create mode 100644 assets/animated/dust-night.svg create mode 100644 assets/animated/dust-wind.svg create mode 100644 assets/animated/dust.svg create mode 100644 assets/animated/extreme-day-drizzle.svg create mode 100644 assets/animated/extreme-day-fog.svg create mode 100644 assets/animated/extreme-day-hail.svg create mode 100644 assets/animated/extreme-day-haze.svg create mode 100644 assets/animated/extreme-day-rain.svg create mode 100644 assets/animated/extreme-day-sleet.svg create mode 100644 assets/animated/extreme-day-smoke.svg create mode 100644 assets/animated/extreme-day-snow.svg create mode 100644 assets/animated/extreme-day.svg create mode 100644 assets/animated/extreme-drizzle.svg create mode 100644 assets/animated/extreme-fog.svg create mode 100644 assets/animated/extreme-hail.svg create mode 100644 assets/animated/extreme-haze.svg create mode 100644 assets/animated/extreme-night-drizzle.svg create mode 100644 assets/animated/extreme-night-fog.svg create mode 100644 assets/animated/extreme-night-hail.svg create mode 100644 assets/animated/extreme-night-haze.svg create mode 100644 assets/animated/extreme-night-rain.svg create mode 100644 assets/animated/extreme-night-sleet.svg create mode 100644 assets/animated/extreme-night-smoke.svg create mode 100644 assets/animated/extreme-night-snow.svg create mode 100644 assets/animated/extreme-night.svg create mode 100644 assets/animated/extreme-rain.svg create mode 100644 assets/animated/extreme-sleet.svg create mode 100644 assets/animated/extreme-smoke.svg create mode 100644 assets/animated/extreme-snow.svg create mode 100644 assets/animated/extreme.svg create mode 100644 assets/animated/fahrenheit.svg create mode 100644 assets/animated/falling-stars.svg create mode 100644 assets/animated/flag-gale-warning.svg create mode 100644 assets/animated/flag-hurricane-warning.svg create mode 100644 assets/animated/flag-small-craft-advisory.svg create mode 100644 assets/animated/flag-storm-warning.svg create mode 100644 assets/animated/fog-day.svg create mode 100644 assets/animated/fog-night.svg create mode 100644 assets/animated/fog.svg create mode 100644 assets/animated/glove.svg create mode 100644 assets/animated/hail.svg create mode 100644 assets/animated/haze-day.svg create mode 100644 assets/animated/haze-night.svg create mode 100644 assets/animated/haze.svg create mode 100644 assets/animated/horizon.svg create mode 100644 assets/animated/humidity.svg create mode 100644 assets/animated/hurricane.svg create mode 100644 assets/animated/lightning-bolt.svg create mode 100644 assets/animated/mist.svg create mode 100644 assets/animated/moon-first-quarter.svg create mode 100644 assets/animated/moon-full.svg create mode 100644 assets/animated/moon-last-quarter.svg create mode 100644 assets/animated/moon-new.svg create mode 100644 assets/animated/moon-waning-crescent.svg create mode 100644 assets/animated/moon-waning-gibbous.svg create mode 100644 assets/animated/moon-waxing-crescent.svg create mode 100644 assets/animated/moon-waxing-gibbous.svg create mode 100644 assets/animated/moonrise.svg create mode 100644 assets/animated/moonset.svg create mode 100644 assets/animated/not-available.svg create mode 100644 assets/animated/overcast-day-drizzle.svg create mode 100644 assets/animated/overcast-day-fog.svg create mode 100644 assets/animated/overcast-day-hail.svg create mode 100644 assets/animated/overcast-day-haze.svg create mode 100644 assets/animated/overcast-day-rain.svg create mode 100644 assets/animated/overcast-day-sleet.svg create mode 100644 assets/animated/overcast-day-smoke.svg create mode 100644 assets/animated/overcast-day-snow.svg create mode 100644 assets/animated/overcast-day.svg create mode 100644 assets/animated/overcast-drizzle.svg create mode 100644 assets/animated/overcast-fog.svg create mode 100644 assets/animated/overcast-hail.svg create mode 100644 assets/animated/overcast-haze.svg create mode 100644 assets/animated/overcast-night-drizzle.svg create mode 100644 assets/animated/overcast-night-fog.svg create mode 100644 assets/animated/overcast-night-hail.svg create mode 100644 assets/animated/overcast-night-haze.svg create mode 100644 assets/animated/overcast-night-rain.svg create mode 100644 assets/animated/overcast-night-sleet.svg create mode 100644 assets/animated/overcast-night-smoke.svg create mode 100644 assets/animated/overcast-night-snow.svg create mode 100644 assets/animated/overcast-night.svg create mode 100644 assets/animated/overcast-rain.svg create mode 100644 assets/animated/overcast-sleet.svg create mode 100644 assets/animated/overcast-smoke.svg create mode 100644 assets/animated/overcast-snow.svg create mode 100644 assets/animated/overcast.svg create mode 100644 assets/animated/partly-cloudy-day-drizzle.svg create mode 100644 assets/animated/partly-cloudy-day-fog.svg create mode 100644 assets/animated/partly-cloudy-day-hail.svg create mode 100644 assets/animated/partly-cloudy-day-haze.svg create mode 100644 assets/animated/partly-cloudy-day-rain.svg create mode 100644 assets/animated/partly-cloudy-day-sleet.svg create mode 100644 assets/animated/partly-cloudy-day-smoke.svg create mode 100644 assets/animated/partly-cloudy-day-snow.svg create mode 100644 assets/animated/partly-cloudy-day.svg create mode 100644 assets/animated/partly-cloudy-night-drizzle.svg create mode 100644 assets/animated/partly-cloudy-night-fog.svg create mode 100644 assets/animated/partly-cloudy-night-hail.svg create mode 100644 assets/animated/partly-cloudy-night-haze.svg create mode 100644 assets/animated/partly-cloudy-night-rain.svg create mode 100644 assets/animated/partly-cloudy-night-sleet.svg create mode 100644 assets/animated/partly-cloudy-night-smoke.svg create mode 100644 assets/animated/partly-cloudy-night-snow.svg create mode 100644 assets/animated/partly-cloudy-night.svg create mode 100644 assets/animated/pollen-flower.svg create mode 100644 assets/animated/pollen-grass.svg create mode 100644 assets/animated/pollen-tree.svg create mode 100644 assets/animated/pollen.svg create mode 100644 assets/animated/pressure-high-alt.svg create mode 100644 assets/animated/pressure-high.svg create mode 100644 assets/animated/pressure-low-alt.svg create mode 100644 assets/animated/pressure-low.svg create mode 100644 assets/animated/rain.svg create mode 100644 assets/animated/rainbow-clear.svg create mode 100644 assets/animated/rainbow.svg create mode 100644 assets/animated/raindrop-measure.svg create mode 100644 assets/animated/raindrop.svg create mode 100644 assets/animated/raindrops.svg create mode 100644 assets/animated/sleet.svg create mode 100644 assets/animated/smoke-particles.svg create mode 100644 assets/animated/smoke.svg create mode 100644 assets/animated/snow.svg create mode 100644 assets/animated/snowflake.svg create mode 100644 assets/animated/snowman.svg create mode 100644 assets/animated/solar-eclipse.svg create mode 100644 assets/animated/star.svg create mode 100644 assets/animated/starry-night.svg create mode 100644 assets/animated/sun-hot.svg create mode 100644 assets/animated/sunrise.svg create mode 100644 assets/animated/sunset.svg create mode 100644 assets/animated/thermometer-celsius.svg create mode 100644 assets/animated/thermometer-colder.svg create mode 100644 assets/animated/thermometer-fahrenheit.svg create mode 100644 assets/animated/thermometer-glass-celsius.svg create mode 100644 assets/animated/thermometer-glass-fahrenheit.svg create mode 100644 assets/animated/thermometer-glass.svg create mode 100644 assets/animated/thermometer-mercury-cold.svg create mode 100644 assets/animated/thermometer-mercury.svg create mode 100644 assets/animated/thermometer-moon.svg create mode 100644 assets/animated/thermometer-raindrop.svg create mode 100644 assets/animated/thermometer-snow.svg create mode 100644 assets/animated/thermometer-sun.svg create mode 100644 assets/animated/thermometer-warmer.svg create mode 100644 assets/animated/thermometer-water.svg create mode 100644 assets/animated/thermometer.svg create mode 100644 assets/animated/thunderstorms-day-extreme-rain.svg create mode 100644 assets/animated/thunderstorms-day-extreme-snow.svg create mode 100644 assets/animated/thunderstorms-day-extreme.svg create mode 100644 assets/animated/thunderstorms-day-overcast-rain.svg create mode 100644 assets/animated/thunderstorms-day-overcast-snow.svg create mode 100644 assets/animated/thunderstorms-day-overcast.svg create mode 100644 assets/animated/thunderstorms-day-rain.svg create mode 100644 assets/animated/thunderstorms-day-snow.svg create mode 100644 assets/animated/thunderstorms-day.svg create mode 100644 assets/animated/thunderstorms-extreme-rain.svg create mode 100644 assets/animated/thunderstorms-extreme-snow.svg create mode 100644 assets/animated/thunderstorms-extreme.svg create mode 100644 assets/animated/thunderstorms-night-extreme-rain.svg create mode 100644 assets/animated/thunderstorms-night-extreme-snow.svg create mode 100644 assets/animated/thunderstorms-night-extreme.svg create mode 100644 assets/animated/thunderstorms-night-overcast-rain.svg create mode 100644 assets/animated/thunderstorms-night-overcast-snow.svg create mode 100644 assets/animated/thunderstorms-night-overcast.svg create mode 100644 assets/animated/thunderstorms-night-rain.svg create mode 100644 assets/animated/thunderstorms-night-snow.svg create mode 100644 assets/animated/thunderstorms-night.svg create mode 100644 assets/animated/thunderstorms-overcast-rain.svg create mode 100644 assets/animated/thunderstorms-overcast-snow.svg create mode 100644 assets/animated/thunderstorms-overcast.svg create mode 100644 assets/animated/thunderstorms-rain.svg create mode 100644 assets/animated/thunderstorms-snow.svg create mode 100644 assets/animated/thunderstorms.svg create mode 100644 assets/animated/tide-high.svg create mode 100644 assets/animated/tide-low.svg create mode 100644 assets/animated/time-afternoon.svg create mode 100644 assets/animated/time-evening.svg create mode 100644 assets/animated/time-late-afternoon.svg create mode 100644 assets/animated/time-late-evening.svg create mode 100644 assets/animated/time-late-morning.svg create mode 100644 assets/animated/time-late-night.svg create mode 100644 assets/animated/time-morning.svg create mode 100644 assets/animated/time-night.svg create mode 100644 assets/animated/tornado.svg create mode 100644 assets/animated/umbrella-wind-alt.svg create mode 100644 assets/animated/umbrella-wind.svg create mode 100644 assets/animated/umbrella.svg create mode 100644 assets/animated/uv-index-1.svg create mode 100644 assets/animated/uv-index-10.svg create mode 100644 assets/animated/uv-index-11.svg create mode 100644 assets/animated/uv-index-2.svg create mode 100644 assets/animated/uv-index-3.svg create mode 100644 assets/animated/uv-index-4.svg create mode 100644 assets/animated/uv-index-5.svg create mode 100644 assets/animated/uv-index-6.svg create mode 100644 assets/animated/uv-index-7.svg create mode 100644 assets/animated/uv-index-8.svg create mode 100644 assets/animated/uv-index-9.svg create mode 100644 assets/animated/uv-index.svg create mode 100644 assets/animated/wind-alert.svg create mode 100644 assets/animated/wind-beaufort-0.svg create mode 100644 assets/animated/wind-beaufort-1.svg create mode 100644 assets/animated/wind-beaufort-10.svg create mode 100644 assets/animated/wind-beaufort-11.svg create mode 100644 assets/animated/wind-beaufort-12.svg create mode 100644 assets/animated/wind-beaufort-2.svg create mode 100644 assets/animated/wind-beaufort-3.svg create mode 100644 assets/animated/wind-beaufort-4.svg create mode 100644 assets/animated/wind-beaufort-5.svg create mode 100644 assets/animated/wind-beaufort-6.svg create mode 100644 assets/animated/wind-beaufort-7.svg create mode 100644 assets/animated/wind-beaufort-8.svg create mode 100644 assets/animated/wind-beaufort-9.svg create mode 100644 assets/animated/wind-offshore.svg create mode 100644 assets/animated/wind-onshore.svg create mode 100644 assets/animated/wind-snow.svg create mode 100644 assets/animated/wind.svg create mode 100644 assets/animated/windsock-weak.svg create mode 100644 assets/animated/windsock.svg create mode 100644 favicon.svg delete mode 100644 pull_request_template.md diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0rKCA{9D@J@2=mkrgC+C`z^oiW7&LJ^Jw`{gMnZm7zhS} zfgfW4cQ&QCFpNGJ2nK?I0|PuC5*o2`91Qc(fi9N-Kt7|hKxZw%nB-VF4u-Hm)Ixz4 zs$OEKg=0RsU*$L$TDYheAL=WA7B8AtNB)%EMJvPTgMnaR%)q$~=e+-y_+=)G{BcN( zf`MS*pE01bx~UiVD8E}zK96^8Lc2ku&~Kmufj)W!V8CH1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 \ No newline at end of file diff --git a/assets/animated/alert-falling-rocks.svg b/assets/animated/alert-falling-rocks.svg new file mode 100644 index 000000000..140e919c2 --- /dev/null +++ b/assets/animated/alert-falling-rocks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/barometer.svg b/assets/animated/barometer.svg new file mode 100644 index 000000000..1c92d0a8c --- /dev/null +++ b/assets/animated/barometer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/beanie.svg b/assets/animated/beanie.svg new file mode 100644 index 000000000..fa3dfb1a4 --- /dev/null +++ b/assets/animated/beanie.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/celsius.svg b/assets/animated/celsius.svg new file mode 100644 index 000000000..0914adcc2 --- /dev/null +++ b/assets/animated/celsius.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/clear-day.svg b/assets/animated/clear-day.svg new file mode 100644 index 000000000..dc8d9c0d0 --- /dev/null +++ b/assets/animated/clear-day.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/clear-night.svg b/assets/animated/clear-night.svg new file mode 100644 index 000000000..d5d2701ea --- /dev/null +++ b/assets/animated/clear-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/cloud-down.svg b/assets/animated/cloud-down.svg new file mode 100644 index 000000000..0864a7fa0 --- /dev/null +++ b/assets/animated/cloud-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/cloud-up.svg b/assets/animated/cloud-up.svg new file mode 100644 index 000000000..4c38227b9 --- /dev/null +++ b/assets/animated/cloud-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/cloudy.svg b/assets/animated/cloudy.svg new file mode 100644 index 000000000..dfd2a3be4 --- /dev/null +++ b/assets/animated/cloudy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/code-green.svg b/assets/animated/code-green.svg new file mode 100644 index 000000000..9880a0e6e --- /dev/null +++ b/assets/animated/code-green.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/code-orange.svg b/assets/animated/code-orange.svg new file mode 100644 index 000000000..63fecdb28 --- /dev/null +++ b/assets/animated/code-orange.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/code-red.svg b/assets/animated/code-red.svg new file mode 100644 index 000000000..677de5249 --- /dev/null +++ b/assets/animated/code-red.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/code-yellow.svg b/assets/animated/code-yellow.svg new file mode 100644 index 000000000..77a90672e --- /dev/null +++ b/assets/animated/code-yellow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/compass.svg b/assets/animated/compass.svg new file mode 100644 index 000000000..fa8ec1f96 --- /dev/null +++ b/assets/animated/compass.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/drizzle.svg b/assets/animated/drizzle.svg new file mode 100644 index 000000000..e7138a531 --- /dev/null +++ b/assets/animated/drizzle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/dust-day.svg b/assets/animated/dust-day.svg new file mode 100644 index 000000000..ab910a78c --- /dev/null +++ b/assets/animated/dust-day.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/dust-night.svg b/assets/animated/dust-night.svg new file mode 100644 index 000000000..72b8e0b8f --- /dev/null +++ b/assets/animated/dust-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/dust-wind.svg b/assets/animated/dust-wind.svg new file mode 100644 index 000000000..957ab2cd9 --- /dev/null +++ b/assets/animated/dust-wind.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/dust.svg b/assets/animated/dust.svg new file mode 100644 index 000000000..d9f914f79 --- /dev/null +++ b/assets/animated/dust.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-day-drizzle.svg b/assets/animated/extreme-day-drizzle.svg new file mode 100644 index 000000000..aa5394f38 --- /dev/null +++ b/assets/animated/extreme-day-drizzle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-day-fog.svg b/assets/animated/extreme-day-fog.svg new file mode 100644 index 000000000..3d5ba2d2e --- /dev/null +++ b/assets/animated/extreme-day-fog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-day-hail.svg b/assets/animated/extreme-day-hail.svg new file mode 100644 index 000000000..423eda165 --- /dev/null +++ b/assets/animated/extreme-day-hail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-day-haze.svg b/assets/animated/extreme-day-haze.svg new file mode 100644 index 000000000..5c4e7a0f1 --- /dev/null +++ b/assets/animated/extreme-day-haze.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-day-rain.svg b/assets/animated/extreme-day-rain.svg new file mode 100644 index 000000000..35741dd3d --- /dev/null +++ b/assets/animated/extreme-day-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-day-sleet.svg b/assets/animated/extreme-day-sleet.svg new file mode 100644 index 000000000..76f98f696 --- /dev/null +++ b/assets/animated/extreme-day-sleet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-day-smoke.svg b/assets/animated/extreme-day-smoke.svg new file mode 100644 index 000000000..bd67f6822 --- /dev/null +++ b/assets/animated/extreme-day-smoke.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-day-snow.svg b/assets/animated/extreme-day-snow.svg new file mode 100644 index 000000000..17bb13a49 --- /dev/null +++ b/assets/animated/extreme-day-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-day.svg b/assets/animated/extreme-day.svg new file mode 100644 index 000000000..d2aef46a7 --- /dev/null +++ b/assets/animated/extreme-day.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-drizzle.svg b/assets/animated/extreme-drizzle.svg new file mode 100644 index 000000000..897c8155e --- /dev/null +++ b/assets/animated/extreme-drizzle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-fog.svg b/assets/animated/extreme-fog.svg new file mode 100644 index 000000000..321356f07 --- /dev/null +++ b/assets/animated/extreme-fog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-hail.svg b/assets/animated/extreme-hail.svg new file mode 100644 index 000000000..8cff3e7f1 --- /dev/null +++ b/assets/animated/extreme-hail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-haze.svg b/assets/animated/extreme-haze.svg new file mode 100644 index 000000000..9e9bfeb8e --- /dev/null +++ b/assets/animated/extreme-haze.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-night-drizzle.svg b/assets/animated/extreme-night-drizzle.svg new file mode 100644 index 000000000..af4226175 --- /dev/null +++ b/assets/animated/extreme-night-drizzle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-night-fog.svg b/assets/animated/extreme-night-fog.svg new file mode 100644 index 000000000..5ed2f6ff8 --- /dev/null +++ b/assets/animated/extreme-night-fog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-night-hail.svg b/assets/animated/extreme-night-hail.svg new file mode 100644 index 000000000..dbf6c1b55 --- /dev/null +++ b/assets/animated/extreme-night-hail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-night-haze.svg b/assets/animated/extreme-night-haze.svg new file mode 100644 index 000000000..2b039549c --- /dev/null +++ b/assets/animated/extreme-night-haze.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-night-rain.svg b/assets/animated/extreme-night-rain.svg new file mode 100644 index 000000000..806950613 --- /dev/null +++ b/assets/animated/extreme-night-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-night-sleet.svg b/assets/animated/extreme-night-sleet.svg new file mode 100644 index 000000000..70d5bbc9a --- /dev/null +++ b/assets/animated/extreme-night-sleet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-night-smoke.svg b/assets/animated/extreme-night-smoke.svg new file mode 100644 index 000000000..757104aa2 --- /dev/null +++ b/assets/animated/extreme-night-smoke.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-night-snow.svg b/assets/animated/extreme-night-snow.svg new file mode 100644 index 000000000..fdded1c51 --- /dev/null +++ b/assets/animated/extreme-night-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-night.svg b/assets/animated/extreme-night.svg new file mode 100644 index 000000000..86265f350 --- /dev/null +++ b/assets/animated/extreme-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-rain.svg b/assets/animated/extreme-rain.svg new file mode 100644 index 000000000..16be34883 --- /dev/null +++ b/assets/animated/extreme-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-sleet.svg b/assets/animated/extreme-sleet.svg new file mode 100644 index 000000000..f93b1c5c0 --- /dev/null +++ b/assets/animated/extreme-sleet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-smoke.svg b/assets/animated/extreme-smoke.svg new file mode 100644 index 000000000..124a87f8c --- /dev/null +++ b/assets/animated/extreme-smoke.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme-snow.svg b/assets/animated/extreme-snow.svg new file mode 100644 index 000000000..546e341a1 --- /dev/null +++ b/assets/animated/extreme-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/extreme.svg b/assets/animated/extreme.svg new file mode 100644 index 000000000..50cd85c22 --- /dev/null +++ b/assets/animated/extreme.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/fahrenheit.svg b/assets/animated/fahrenheit.svg new file mode 100644 index 000000000..eee481abf --- /dev/null +++ b/assets/animated/fahrenheit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/falling-stars.svg b/assets/animated/falling-stars.svg new file mode 100644 index 000000000..cb8244300 --- /dev/null +++ b/assets/animated/falling-stars.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/flag-gale-warning.svg b/assets/animated/flag-gale-warning.svg new file mode 100644 index 000000000..7191aba37 --- /dev/null +++ b/assets/animated/flag-gale-warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/flag-hurricane-warning.svg b/assets/animated/flag-hurricane-warning.svg new file mode 100644 index 000000000..e01417794 --- /dev/null +++ b/assets/animated/flag-hurricane-warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/flag-small-craft-advisory.svg b/assets/animated/flag-small-craft-advisory.svg new file mode 100644 index 000000000..8853b286b --- /dev/null +++ b/assets/animated/flag-small-craft-advisory.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/flag-storm-warning.svg b/assets/animated/flag-storm-warning.svg new file mode 100644 index 000000000..7298c1601 --- /dev/null +++ b/assets/animated/flag-storm-warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/fog-day.svg b/assets/animated/fog-day.svg new file mode 100644 index 000000000..8cf79c44b --- /dev/null +++ b/assets/animated/fog-day.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/fog-night.svg b/assets/animated/fog-night.svg new file mode 100644 index 000000000..94649b6df --- /dev/null +++ b/assets/animated/fog-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/fog.svg b/assets/animated/fog.svg new file mode 100644 index 000000000..a73b29056 --- /dev/null +++ b/assets/animated/fog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/glove.svg b/assets/animated/glove.svg new file mode 100644 index 000000000..97a610288 --- /dev/null +++ b/assets/animated/glove.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/hail.svg b/assets/animated/hail.svg new file mode 100644 index 000000000..c8afc2a87 --- /dev/null +++ b/assets/animated/hail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/haze-day.svg b/assets/animated/haze-day.svg new file mode 100644 index 000000000..7118308ac --- /dev/null +++ b/assets/animated/haze-day.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/haze-night.svg b/assets/animated/haze-night.svg new file mode 100644 index 000000000..002dc22dd --- /dev/null +++ b/assets/animated/haze-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/haze.svg b/assets/animated/haze.svg new file mode 100644 index 000000000..94b754f45 --- /dev/null +++ b/assets/animated/haze.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/horizon.svg b/assets/animated/horizon.svg new file mode 100644 index 000000000..a040ff2b6 --- /dev/null +++ b/assets/animated/horizon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/humidity.svg b/assets/animated/humidity.svg new file mode 100644 index 000000000..81ecd29b1 --- /dev/null +++ b/assets/animated/humidity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/hurricane.svg b/assets/animated/hurricane.svg new file mode 100644 index 000000000..719ea1f39 --- /dev/null +++ b/assets/animated/hurricane.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/lightning-bolt.svg b/assets/animated/lightning-bolt.svg new file mode 100644 index 000000000..4ca650b87 --- /dev/null +++ b/assets/animated/lightning-bolt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/mist.svg b/assets/animated/mist.svg new file mode 100644 index 000000000..c08748905 --- /dev/null +++ b/assets/animated/mist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/moon-first-quarter.svg b/assets/animated/moon-first-quarter.svg new file mode 100644 index 000000000..fda73d4aa --- /dev/null +++ b/assets/animated/moon-first-quarter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/moon-full.svg b/assets/animated/moon-full.svg new file mode 100644 index 000000000..f99fe93fc --- /dev/null +++ b/assets/animated/moon-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/moon-last-quarter.svg b/assets/animated/moon-last-quarter.svg new file mode 100644 index 000000000..6a626199c --- /dev/null +++ b/assets/animated/moon-last-quarter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/moon-new.svg b/assets/animated/moon-new.svg new file mode 100644 index 000000000..3105a4c7b --- /dev/null +++ b/assets/animated/moon-new.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/moon-waning-crescent.svg b/assets/animated/moon-waning-crescent.svg new file mode 100644 index 000000000..7843eb8e7 --- /dev/null +++ b/assets/animated/moon-waning-crescent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/moon-waning-gibbous.svg b/assets/animated/moon-waning-gibbous.svg new file mode 100644 index 000000000..1d04b626a --- /dev/null +++ b/assets/animated/moon-waning-gibbous.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/moon-waxing-crescent.svg b/assets/animated/moon-waxing-crescent.svg new file mode 100644 index 000000000..41e1b7e67 --- /dev/null +++ b/assets/animated/moon-waxing-crescent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/moon-waxing-gibbous.svg b/assets/animated/moon-waxing-gibbous.svg new file mode 100644 index 000000000..78f4a8b7c --- /dev/null +++ b/assets/animated/moon-waxing-gibbous.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/moonrise.svg b/assets/animated/moonrise.svg new file mode 100644 index 000000000..c9221c1c4 --- /dev/null +++ b/assets/animated/moonrise.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/moonset.svg b/assets/animated/moonset.svg new file mode 100644 index 000000000..b22a57a4b --- /dev/null +++ b/assets/animated/moonset.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/not-available.svg b/assets/animated/not-available.svg new file mode 100644 index 000000000..615c07c06 --- /dev/null +++ b/assets/animated/not-available.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-day-drizzle.svg b/assets/animated/overcast-day-drizzle.svg new file mode 100644 index 000000000..9a83dbf65 --- /dev/null +++ b/assets/animated/overcast-day-drizzle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-day-fog.svg b/assets/animated/overcast-day-fog.svg new file mode 100644 index 000000000..75e192b33 --- /dev/null +++ b/assets/animated/overcast-day-fog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-day-hail.svg b/assets/animated/overcast-day-hail.svg new file mode 100644 index 000000000..afb6cce3d --- /dev/null +++ b/assets/animated/overcast-day-hail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-day-haze.svg b/assets/animated/overcast-day-haze.svg new file mode 100644 index 000000000..d5e318f07 --- /dev/null +++ b/assets/animated/overcast-day-haze.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-day-rain.svg b/assets/animated/overcast-day-rain.svg new file mode 100644 index 000000000..6f23bc625 --- /dev/null +++ b/assets/animated/overcast-day-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-day-sleet.svg b/assets/animated/overcast-day-sleet.svg new file mode 100644 index 000000000..8a45bfb2f --- /dev/null +++ b/assets/animated/overcast-day-sleet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-day-smoke.svg b/assets/animated/overcast-day-smoke.svg new file mode 100644 index 000000000..f57c8d030 --- /dev/null +++ b/assets/animated/overcast-day-smoke.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-day-snow.svg b/assets/animated/overcast-day-snow.svg new file mode 100644 index 000000000..051b410c4 --- /dev/null +++ b/assets/animated/overcast-day-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-day.svg b/assets/animated/overcast-day.svg new file mode 100644 index 000000000..f3f1c74d8 --- /dev/null +++ b/assets/animated/overcast-day.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-drizzle.svg b/assets/animated/overcast-drizzle.svg new file mode 100644 index 000000000..3c133dcd2 --- /dev/null +++ b/assets/animated/overcast-drizzle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-fog.svg b/assets/animated/overcast-fog.svg new file mode 100644 index 000000000..074fe9729 --- /dev/null +++ b/assets/animated/overcast-fog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-hail.svg b/assets/animated/overcast-hail.svg new file mode 100644 index 000000000..da697645c --- /dev/null +++ b/assets/animated/overcast-hail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-haze.svg b/assets/animated/overcast-haze.svg new file mode 100644 index 000000000..5bad0d427 --- /dev/null +++ b/assets/animated/overcast-haze.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-night-drizzle.svg b/assets/animated/overcast-night-drizzle.svg new file mode 100644 index 000000000..318b229e7 --- /dev/null +++ b/assets/animated/overcast-night-drizzle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-night-fog.svg b/assets/animated/overcast-night-fog.svg new file mode 100644 index 000000000..5cdf8a9d6 --- /dev/null +++ b/assets/animated/overcast-night-fog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-night-hail.svg b/assets/animated/overcast-night-hail.svg new file mode 100644 index 000000000..5352dbae0 --- /dev/null +++ b/assets/animated/overcast-night-hail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-night-haze.svg b/assets/animated/overcast-night-haze.svg new file mode 100644 index 000000000..13f3da913 --- /dev/null +++ b/assets/animated/overcast-night-haze.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-night-rain.svg b/assets/animated/overcast-night-rain.svg new file mode 100644 index 000000000..13cb96724 --- /dev/null +++ b/assets/animated/overcast-night-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-night-sleet.svg b/assets/animated/overcast-night-sleet.svg new file mode 100644 index 000000000..9960ff0f7 --- /dev/null +++ b/assets/animated/overcast-night-sleet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-night-smoke.svg b/assets/animated/overcast-night-smoke.svg new file mode 100644 index 000000000..1fb7bd4bb --- /dev/null +++ b/assets/animated/overcast-night-smoke.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-night-snow.svg b/assets/animated/overcast-night-snow.svg new file mode 100644 index 000000000..9fc98a4e1 --- /dev/null +++ b/assets/animated/overcast-night-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-night.svg b/assets/animated/overcast-night.svg new file mode 100644 index 000000000..0b57cdefa --- /dev/null +++ b/assets/animated/overcast-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-rain.svg b/assets/animated/overcast-rain.svg new file mode 100644 index 000000000..52dfdb1ca --- /dev/null +++ b/assets/animated/overcast-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-sleet.svg b/assets/animated/overcast-sleet.svg new file mode 100644 index 000000000..11bccd87a --- /dev/null +++ b/assets/animated/overcast-sleet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-smoke.svg b/assets/animated/overcast-smoke.svg new file mode 100644 index 000000000..36bafb4a4 --- /dev/null +++ b/assets/animated/overcast-smoke.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast-snow.svg b/assets/animated/overcast-snow.svg new file mode 100644 index 000000000..880961cf5 --- /dev/null +++ b/assets/animated/overcast-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/overcast.svg b/assets/animated/overcast.svg new file mode 100644 index 000000000..64170d2af --- /dev/null +++ b/assets/animated/overcast.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-day-drizzle.svg b/assets/animated/partly-cloudy-day-drizzle.svg new file mode 100644 index 000000000..b86ea7ba0 --- /dev/null +++ b/assets/animated/partly-cloudy-day-drizzle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-day-fog.svg b/assets/animated/partly-cloudy-day-fog.svg new file mode 100644 index 000000000..1f484cc2c --- /dev/null +++ b/assets/animated/partly-cloudy-day-fog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-day-hail.svg b/assets/animated/partly-cloudy-day-hail.svg new file mode 100644 index 000000000..45850ed5b --- /dev/null +++ b/assets/animated/partly-cloudy-day-hail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-day-haze.svg b/assets/animated/partly-cloudy-day-haze.svg new file mode 100644 index 000000000..96bfaadb8 --- /dev/null +++ b/assets/animated/partly-cloudy-day-haze.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-day-rain.svg b/assets/animated/partly-cloudy-day-rain.svg new file mode 100644 index 000000000..6b01d0ff0 --- /dev/null +++ b/assets/animated/partly-cloudy-day-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-day-sleet.svg b/assets/animated/partly-cloudy-day-sleet.svg new file mode 100644 index 000000000..7d84b51b0 --- /dev/null +++ b/assets/animated/partly-cloudy-day-sleet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-day-smoke.svg b/assets/animated/partly-cloudy-day-smoke.svg new file mode 100644 index 000000000..a7e4e0c65 --- /dev/null +++ b/assets/animated/partly-cloudy-day-smoke.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-day-snow.svg b/assets/animated/partly-cloudy-day-snow.svg new file mode 100644 index 000000000..95535631d --- /dev/null +++ b/assets/animated/partly-cloudy-day-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-day.svg b/assets/animated/partly-cloudy-day.svg new file mode 100644 index 000000000..33c11000a --- /dev/null +++ b/assets/animated/partly-cloudy-day.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-night-drizzle.svg b/assets/animated/partly-cloudy-night-drizzle.svg new file mode 100644 index 000000000..796738df1 --- /dev/null +++ b/assets/animated/partly-cloudy-night-drizzle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-night-fog.svg b/assets/animated/partly-cloudy-night-fog.svg new file mode 100644 index 000000000..00466d65f --- /dev/null +++ b/assets/animated/partly-cloudy-night-fog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-night-hail.svg b/assets/animated/partly-cloudy-night-hail.svg new file mode 100644 index 000000000..2afa4f986 --- /dev/null +++ b/assets/animated/partly-cloudy-night-hail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-night-haze.svg b/assets/animated/partly-cloudy-night-haze.svg new file mode 100644 index 000000000..521c122ce --- /dev/null +++ b/assets/animated/partly-cloudy-night-haze.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-night-rain.svg b/assets/animated/partly-cloudy-night-rain.svg new file mode 100644 index 000000000..b35cc0165 --- /dev/null +++ b/assets/animated/partly-cloudy-night-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-night-sleet.svg b/assets/animated/partly-cloudy-night-sleet.svg new file mode 100644 index 000000000..5f540096d --- /dev/null +++ b/assets/animated/partly-cloudy-night-sleet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-night-smoke.svg b/assets/animated/partly-cloudy-night-smoke.svg new file mode 100644 index 000000000..98493b710 --- /dev/null +++ b/assets/animated/partly-cloudy-night-smoke.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-night-snow.svg b/assets/animated/partly-cloudy-night-snow.svg new file mode 100644 index 000000000..d000665ce --- /dev/null +++ b/assets/animated/partly-cloudy-night-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-night.svg b/assets/animated/partly-cloudy-night.svg new file mode 100644 index 000000000..e7baec890 --- /dev/null +++ b/assets/animated/partly-cloudy-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/pollen-flower.svg b/assets/animated/pollen-flower.svg new file mode 100644 index 000000000..dc5d42484 --- /dev/null +++ b/assets/animated/pollen-flower.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/pollen-grass.svg b/assets/animated/pollen-grass.svg new file mode 100644 index 000000000..adf58486f --- /dev/null +++ b/assets/animated/pollen-grass.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/pollen-tree.svg b/assets/animated/pollen-tree.svg new file mode 100644 index 000000000..c873d868f --- /dev/null +++ b/assets/animated/pollen-tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/pollen.svg b/assets/animated/pollen.svg new file mode 100644 index 000000000..f55733fa0 --- /dev/null +++ b/assets/animated/pollen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/pressure-high-alt.svg b/assets/animated/pressure-high-alt.svg new file mode 100644 index 000000000..00439f352 --- /dev/null +++ b/assets/animated/pressure-high-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/pressure-high.svg b/assets/animated/pressure-high.svg new file mode 100644 index 000000000..8d806db69 --- /dev/null +++ b/assets/animated/pressure-high.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/pressure-low-alt.svg b/assets/animated/pressure-low-alt.svg new file mode 100644 index 000000000..1d2ebd781 --- /dev/null +++ b/assets/animated/pressure-low-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/pressure-low.svg b/assets/animated/pressure-low.svg new file mode 100644 index 000000000..958ec2fdc --- /dev/null +++ b/assets/animated/pressure-low.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/rain.svg b/assets/animated/rain.svg new file mode 100644 index 000000000..e7f0e23bf --- /dev/null +++ b/assets/animated/rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/rainbow-clear.svg b/assets/animated/rainbow-clear.svg new file mode 100644 index 000000000..469f342f5 --- /dev/null +++ b/assets/animated/rainbow-clear.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/rainbow.svg b/assets/animated/rainbow.svg new file mode 100644 index 000000000..ddf8ff32a --- /dev/null +++ b/assets/animated/rainbow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/raindrop-measure.svg b/assets/animated/raindrop-measure.svg new file mode 100644 index 000000000..c03b458cd --- /dev/null +++ b/assets/animated/raindrop-measure.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/raindrop.svg b/assets/animated/raindrop.svg new file mode 100644 index 000000000..a262133e2 --- /dev/null +++ b/assets/animated/raindrop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/raindrops.svg b/assets/animated/raindrops.svg new file mode 100644 index 000000000..bdb911274 --- /dev/null +++ b/assets/animated/raindrops.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/sleet.svg b/assets/animated/sleet.svg new file mode 100644 index 000000000..2bd0ba1d5 --- /dev/null +++ b/assets/animated/sleet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/smoke-particles.svg b/assets/animated/smoke-particles.svg new file mode 100644 index 000000000..d9311dc1c --- /dev/null +++ b/assets/animated/smoke-particles.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/smoke.svg b/assets/animated/smoke.svg new file mode 100644 index 000000000..4a6285ee7 --- /dev/null +++ b/assets/animated/smoke.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/snow.svg b/assets/animated/snow.svg new file mode 100644 index 000000000..ad936d2fc --- /dev/null +++ b/assets/animated/snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/snowflake.svg b/assets/animated/snowflake.svg new file mode 100644 index 000000000..820682fe5 --- /dev/null +++ b/assets/animated/snowflake.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/snowman.svg b/assets/animated/snowman.svg new file mode 100644 index 000000000..f041f719a --- /dev/null +++ b/assets/animated/snowman.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/solar-eclipse.svg b/assets/animated/solar-eclipse.svg new file mode 100644 index 000000000..568e70618 --- /dev/null +++ b/assets/animated/solar-eclipse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/star.svg b/assets/animated/star.svg new file mode 100644 index 000000000..0c44ca230 --- /dev/null +++ b/assets/animated/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/starry-night.svg b/assets/animated/starry-night.svg new file mode 100644 index 000000000..666955373 --- /dev/null +++ b/assets/animated/starry-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/sun-hot.svg b/assets/animated/sun-hot.svg new file mode 100644 index 000000000..402e16487 --- /dev/null +++ b/assets/animated/sun-hot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/sunrise.svg b/assets/animated/sunrise.svg new file mode 100644 index 000000000..dabb941fd --- /dev/null +++ b/assets/animated/sunrise.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/sunset.svg b/assets/animated/sunset.svg new file mode 100644 index 000000000..595ead3e4 --- /dev/null +++ b/assets/animated/sunset.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thermometer-celsius.svg b/assets/animated/thermometer-celsius.svg new file mode 100644 index 000000000..73f7e33e3 --- /dev/null +++ b/assets/animated/thermometer-celsius.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thermometer-colder.svg b/assets/animated/thermometer-colder.svg new file mode 100644 index 000000000..484d7ccc3 --- /dev/null +++ b/assets/animated/thermometer-colder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thermometer-fahrenheit.svg b/assets/animated/thermometer-fahrenheit.svg new file mode 100644 index 000000000..c98a38e52 --- /dev/null +++ b/assets/animated/thermometer-fahrenheit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thermometer-glass-celsius.svg b/assets/animated/thermometer-glass-celsius.svg new file mode 100644 index 000000000..3d9fc4a37 --- /dev/null +++ b/assets/animated/thermometer-glass-celsius.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thermometer-glass-fahrenheit.svg b/assets/animated/thermometer-glass-fahrenheit.svg new file mode 100644 index 000000000..1820314fb --- /dev/null +++ b/assets/animated/thermometer-glass-fahrenheit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thermometer-glass.svg b/assets/animated/thermometer-glass.svg new file mode 100644 index 000000000..d8e87b3ed --- /dev/null +++ b/assets/animated/thermometer-glass.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thermometer-mercury-cold.svg b/assets/animated/thermometer-mercury-cold.svg new file mode 100644 index 000000000..bc77bb899 --- /dev/null +++ b/assets/animated/thermometer-mercury-cold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thermometer-mercury.svg b/assets/animated/thermometer-mercury.svg new file mode 100644 index 000000000..073fa7868 --- /dev/null +++ b/assets/animated/thermometer-mercury.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thermometer-moon.svg b/assets/animated/thermometer-moon.svg new file mode 100644 index 000000000..8bc657f15 --- /dev/null +++ b/assets/animated/thermometer-moon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thermometer-raindrop.svg b/assets/animated/thermometer-raindrop.svg new file mode 100644 index 000000000..14cde92fd --- /dev/null +++ b/assets/animated/thermometer-raindrop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thermometer-snow.svg b/assets/animated/thermometer-snow.svg new file mode 100644 index 000000000..d3e365879 --- /dev/null +++ b/assets/animated/thermometer-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thermometer-sun.svg b/assets/animated/thermometer-sun.svg new file mode 100644 index 000000000..7d3597b42 --- /dev/null +++ b/assets/animated/thermometer-sun.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thermometer-warmer.svg b/assets/animated/thermometer-warmer.svg new file mode 100644 index 000000000..3c3e22a1f --- /dev/null +++ b/assets/animated/thermometer-warmer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thermometer-water.svg b/assets/animated/thermometer-water.svg new file mode 100644 index 000000000..d4b93e21f --- /dev/null +++ b/assets/animated/thermometer-water.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thermometer.svg b/assets/animated/thermometer.svg new file mode 100644 index 000000000..1d4df7543 --- /dev/null +++ b/assets/animated/thermometer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-day-extreme-rain.svg b/assets/animated/thunderstorms-day-extreme-rain.svg new file mode 100644 index 000000000..86ca3fd0e --- /dev/null +++ b/assets/animated/thunderstorms-day-extreme-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-day-extreme-snow.svg b/assets/animated/thunderstorms-day-extreme-snow.svg new file mode 100644 index 000000000..5e063a6d8 --- /dev/null +++ b/assets/animated/thunderstorms-day-extreme-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-day-extreme.svg b/assets/animated/thunderstorms-day-extreme.svg new file mode 100644 index 000000000..4a4c82f35 --- /dev/null +++ b/assets/animated/thunderstorms-day-extreme.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-day-overcast-rain.svg b/assets/animated/thunderstorms-day-overcast-rain.svg new file mode 100644 index 000000000..cede3baf9 --- /dev/null +++ b/assets/animated/thunderstorms-day-overcast-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-day-overcast-snow.svg b/assets/animated/thunderstorms-day-overcast-snow.svg new file mode 100644 index 000000000..1e4d09f7c --- /dev/null +++ b/assets/animated/thunderstorms-day-overcast-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-day-overcast.svg b/assets/animated/thunderstorms-day-overcast.svg new file mode 100644 index 000000000..1e3686463 --- /dev/null +++ b/assets/animated/thunderstorms-day-overcast.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-day-rain.svg b/assets/animated/thunderstorms-day-rain.svg new file mode 100644 index 000000000..595595d9b --- /dev/null +++ b/assets/animated/thunderstorms-day-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-day-snow.svg b/assets/animated/thunderstorms-day-snow.svg new file mode 100644 index 000000000..d19209034 --- /dev/null +++ b/assets/animated/thunderstorms-day-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-day.svg b/assets/animated/thunderstorms-day.svg new file mode 100644 index 000000000..fccc8060e --- /dev/null +++ b/assets/animated/thunderstorms-day.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-extreme-rain.svg b/assets/animated/thunderstorms-extreme-rain.svg new file mode 100644 index 000000000..cf51b2703 --- /dev/null +++ b/assets/animated/thunderstorms-extreme-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-extreme-snow.svg b/assets/animated/thunderstorms-extreme-snow.svg new file mode 100644 index 000000000..54913a50c --- /dev/null +++ b/assets/animated/thunderstorms-extreme-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-extreme.svg b/assets/animated/thunderstorms-extreme.svg new file mode 100644 index 000000000..36af054dc --- /dev/null +++ b/assets/animated/thunderstorms-extreme.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-night-extreme-rain.svg b/assets/animated/thunderstorms-night-extreme-rain.svg new file mode 100644 index 000000000..14da76852 --- /dev/null +++ b/assets/animated/thunderstorms-night-extreme-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-night-extreme-snow.svg b/assets/animated/thunderstorms-night-extreme-snow.svg new file mode 100644 index 000000000..ba7357edf --- /dev/null +++ b/assets/animated/thunderstorms-night-extreme-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-night-extreme.svg b/assets/animated/thunderstorms-night-extreme.svg new file mode 100644 index 000000000..d8a32bd17 --- /dev/null +++ b/assets/animated/thunderstorms-night-extreme.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-night-overcast-rain.svg b/assets/animated/thunderstorms-night-overcast-rain.svg new file mode 100644 index 000000000..a6547369f --- /dev/null +++ b/assets/animated/thunderstorms-night-overcast-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-night-overcast-snow.svg b/assets/animated/thunderstorms-night-overcast-snow.svg new file mode 100644 index 000000000..70851afd9 --- /dev/null +++ b/assets/animated/thunderstorms-night-overcast-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-night-overcast.svg b/assets/animated/thunderstorms-night-overcast.svg new file mode 100644 index 000000000..20a61e776 --- /dev/null +++ b/assets/animated/thunderstorms-night-overcast.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-night-rain.svg b/assets/animated/thunderstorms-night-rain.svg new file mode 100644 index 000000000..21ad69060 --- /dev/null +++ b/assets/animated/thunderstorms-night-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-night-snow.svg b/assets/animated/thunderstorms-night-snow.svg new file mode 100644 index 000000000..650b2b69b --- /dev/null +++ b/assets/animated/thunderstorms-night-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-night.svg b/assets/animated/thunderstorms-night.svg new file mode 100644 index 000000000..ec510873d --- /dev/null +++ b/assets/animated/thunderstorms-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-overcast-rain.svg b/assets/animated/thunderstorms-overcast-rain.svg new file mode 100644 index 000000000..f5af7b795 --- /dev/null +++ b/assets/animated/thunderstorms-overcast-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-overcast-snow.svg b/assets/animated/thunderstorms-overcast-snow.svg new file mode 100644 index 000000000..756e560a9 --- /dev/null +++ b/assets/animated/thunderstorms-overcast-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-overcast.svg b/assets/animated/thunderstorms-overcast.svg new file mode 100644 index 000000000..ebb158922 --- /dev/null +++ b/assets/animated/thunderstorms-overcast.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-rain.svg b/assets/animated/thunderstorms-rain.svg new file mode 100644 index 000000000..6ac6ae62c --- /dev/null +++ b/assets/animated/thunderstorms-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms-snow.svg b/assets/animated/thunderstorms-snow.svg new file mode 100644 index 000000000..c920b6a1d --- /dev/null +++ b/assets/animated/thunderstorms-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/thunderstorms.svg b/assets/animated/thunderstorms.svg new file mode 100644 index 000000000..53cc12a42 --- /dev/null +++ b/assets/animated/thunderstorms.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/tide-high.svg b/assets/animated/tide-high.svg new file mode 100644 index 000000000..99aa01ae3 --- /dev/null +++ b/assets/animated/tide-high.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/tide-low.svg b/assets/animated/tide-low.svg new file mode 100644 index 000000000..6f0d2ddcb --- /dev/null +++ b/assets/animated/tide-low.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/time-afternoon.svg b/assets/animated/time-afternoon.svg new file mode 100644 index 000000000..0c842e04a --- /dev/null +++ b/assets/animated/time-afternoon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/time-evening.svg b/assets/animated/time-evening.svg new file mode 100644 index 000000000..8b17c529b --- /dev/null +++ b/assets/animated/time-evening.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/time-late-afternoon.svg b/assets/animated/time-late-afternoon.svg new file mode 100644 index 000000000..e36969367 --- /dev/null +++ b/assets/animated/time-late-afternoon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/time-late-evening.svg b/assets/animated/time-late-evening.svg new file mode 100644 index 000000000..683dff2d2 --- /dev/null +++ b/assets/animated/time-late-evening.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/time-late-morning.svg b/assets/animated/time-late-morning.svg new file mode 100644 index 000000000..d62719d7c --- /dev/null +++ b/assets/animated/time-late-morning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/time-late-night.svg b/assets/animated/time-late-night.svg new file mode 100644 index 000000000..e81ea50bd --- /dev/null +++ b/assets/animated/time-late-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/time-morning.svg b/assets/animated/time-morning.svg new file mode 100644 index 000000000..c36dec89a --- /dev/null +++ b/assets/animated/time-morning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/time-night.svg b/assets/animated/time-night.svg new file mode 100644 index 000000000..f968f0938 --- /dev/null +++ b/assets/animated/time-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/tornado.svg b/assets/animated/tornado.svg new file mode 100644 index 000000000..b049d2e8e --- /dev/null +++ b/assets/animated/tornado.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/umbrella-wind-alt.svg b/assets/animated/umbrella-wind-alt.svg new file mode 100644 index 000000000..fbaf23193 --- /dev/null +++ b/assets/animated/umbrella-wind-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/umbrella-wind.svg b/assets/animated/umbrella-wind.svg new file mode 100644 index 000000000..370b2e3dd --- /dev/null +++ b/assets/animated/umbrella-wind.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/umbrella.svg b/assets/animated/umbrella.svg new file mode 100644 index 000000000..6ac49c369 --- /dev/null +++ b/assets/animated/umbrella.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/uv-index-1.svg b/assets/animated/uv-index-1.svg new file mode 100644 index 000000000..ed8da4ad8 --- /dev/null +++ b/assets/animated/uv-index-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/uv-index-10.svg b/assets/animated/uv-index-10.svg new file mode 100644 index 000000000..745ec2156 --- /dev/null +++ b/assets/animated/uv-index-10.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/uv-index-11.svg b/assets/animated/uv-index-11.svg new file mode 100644 index 000000000..9fdb61ef1 --- /dev/null +++ b/assets/animated/uv-index-11.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/uv-index-2.svg b/assets/animated/uv-index-2.svg new file mode 100644 index 000000000..6cf1a2e0d --- /dev/null +++ b/assets/animated/uv-index-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/uv-index-3.svg b/assets/animated/uv-index-3.svg new file mode 100644 index 000000000..b889f715b --- /dev/null +++ b/assets/animated/uv-index-3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/uv-index-4.svg b/assets/animated/uv-index-4.svg new file mode 100644 index 000000000..611a42ad8 --- /dev/null +++ b/assets/animated/uv-index-4.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/uv-index-5.svg b/assets/animated/uv-index-5.svg new file mode 100644 index 000000000..2011bc14b --- /dev/null +++ b/assets/animated/uv-index-5.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/uv-index-6.svg b/assets/animated/uv-index-6.svg new file mode 100644 index 000000000..5d68226fd --- /dev/null +++ b/assets/animated/uv-index-6.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/uv-index-7.svg b/assets/animated/uv-index-7.svg new file mode 100644 index 000000000..fec87d8e2 --- /dev/null +++ b/assets/animated/uv-index-7.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/uv-index-8.svg b/assets/animated/uv-index-8.svg new file mode 100644 index 000000000..b405be7b7 --- /dev/null +++ b/assets/animated/uv-index-8.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/uv-index-9.svg b/assets/animated/uv-index-9.svg new file mode 100644 index 000000000..e13460fe1 --- /dev/null +++ b/assets/animated/uv-index-9.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/uv-index.svg b/assets/animated/uv-index.svg new file mode 100644 index 000000000..f5b55b32e --- /dev/null +++ b/assets/animated/uv-index.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-alert.svg b/assets/animated/wind-alert.svg new file mode 100644 index 000000000..850b0ada8 --- /dev/null +++ b/assets/animated/wind-alert.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-beaufort-0.svg b/assets/animated/wind-beaufort-0.svg new file mode 100644 index 000000000..2f4c52c5c --- /dev/null +++ b/assets/animated/wind-beaufort-0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-beaufort-1.svg b/assets/animated/wind-beaufort-1.svg new file mode 100644 index 000000000..97dc106cb --- /dev/null +++ b/assets/animated/wind-beaufort-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-beaufort-10.svg b/assets/animated/wind-beaufort-10.svg new file mode 100644 index 000000000..ae673ac13 --- /dev/null +++ b/assets/animated/wind-beaufort-10.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-beaufort-11.svg b/assets/animated/wind-beaufort-11.svg new file mode 100644 index 000000000..ecc7d4e8c --- /dev/null +++ b/assets/animated/wind-beaufort-11.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-beaufort-12.svg b/assets/animated/wind-beaufort-12.svg new file mode 100644 index 000000000..0f6b142af --- /dev/null +++ b/assets/animated/wind-beaufort-12.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-beaufort-2.svg b/assets/animated/wind-beaufort-2.svg new file mode 100644 index 000000000..2af7136b8 --- /dev/null +++ b/assets/animated/wind-beaufort-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-beaufort-3.svg b/assets/animated/wind-beaufort-3.svg new file mode 100644 index 000000000..1d23f9575 --- /dev/null +++ b/assets/animated/wind-beaufort-3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-beaufort-4.svg b/assets/animated/wind-beaufort-4.svg new file mode 100644 index 000000000..cf75506d6 --- /dev/null +++ b/assets/animated/wind-beaufort-4.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-beaufort-5.svg b/assets/animated/wind-beaufort-5.svg new file mode 100644 index 000000000..8cf02e32e --- /dev/null +++ b/assets/animated/wind-beaufort-5.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-beaufort-6.svg b/assets/animated/wind-beaufort-6.svg new file mode 100644 index 000000000..db016a23a --- /dev/null +++ b/assets/animated/wind-beaufort-6.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-beaufort-7.svg b/assets/animated/wind-beaufort-7.svg new file mode 100644 index 000000000..8e7214c10 --- /dev/null +++ b/assets/animated/wind-beaufort-7.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-beaufort-8.svg b/assets/animated/wind-beaufort-8.svg new file mode 100644 index 000000000..49ceb4c7a --- /dev/null +++ b/assets/animated/wind-beaufort-8.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-beaufort-9.svg b/assets/animated/wind-beaufort-9.svg new file mode 100644 index 000000000..2442597ea --- /dev/null +++ b/assets/animated/wind-beaufort-9.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-offshore.svg b/assets/animated/wind-offshore.svg new file mode 100644 index 000000000..446d5e25f --- /dev/null +++ b/assets/animated/wind-offshore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-onshore.svg b/assets/animated/wind-onshore.svg new file mode 100644 index 000000000..cc3b92531 --- /dev/null +++ b/assets/animated/wind-onshore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind-snow.svg b/assets/animated/wind-snow.svg new file mode 100644 index 000000000..221525f4c --- /dev/null +++ b/assets/animated/wind-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/wind.svg b/assets/animated/wind.svg new file mode 100644 index 000000000..1f8e31ef1 --- /dev/null +++ b/assets/animated/wind.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/windsock-weak.svg b/assets/animated/windsock-weak.svg new file mode 100644 index 000000000..4e98428e4 --- /dev/null +++ b/assets/animated/windsock-weak.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/windsock.svg b/assets/animated/windsock.svg new file mode 100644 index 000000000..d7b3f7f68 --- /dev/null +++ b/assets/animated/windsock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/cloudy.svg b/assets/images/cloudy.svg index c2375e901..deead5fb9 100644 --- a/assets/images/cloudy.svg +++ b/assets/images/cloudy.svg @@ -1,7 +1,7 @@ - + diff --git a/favicon.svg b/favicon.svg new file mode 100644 index 000000000..10d04a603 --- /dev/null +++ b/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.html b/index.html index 89041970d..4f7f8077d 100644 --- a/index.html +++ b/index.html @@ -4,19 +4,12 @@ The Weatherington +
    - +

    Loading...

    diff --git a/pull_request_template.md b/pull_request_template.md deleted file mode 100644 index 70fa177f7..000000000 --- a/pull_request_template.md +++ /dev/null @@ -1,3 +0,0 @@ -## Netlify link -Add your Netlify link here. -PS. Don't forget to add it in your readme as well. diff --git a/script.js b/script.js index 5348f91fb..3aa99699b 100644 --- a/script.js +++ b/script.js @@ -1,88 +1,310 @@ /* ********************************* - Global variables + Constants ********************************* */ -let apiQueryCity = "London"; -let displayedCityName = "London"; +// API key for OpenWeatherMap API +const API_KEY = "f1eb0732aa36b48c85267620f68aa926"; + +// Base URLs for current weather and forecast APIs +const BASE_URL_WEATHER = "https://api.openweathermap.org/data/2.5/weather"; +const BASE_URL_FORECAST = "https://api.openweathermap.org/data/2.5/forecast"; + +// Array of weekdays for easy reference +const weekdays = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"]; + +/* ********************************* + Global Variables +********************************* */ + +// Default city for initial weather display +let apiQueryCity = "Stockholm"; + +// Displayed city name (may differ in case or spelling) +let displayedCityName = "Stockholm"; + +// Current weather type (e.g., "Clouds", "Clear") let weatherTypeToday = ""; +// User input city name during a search +let userCityInput = ""; + /* ********************************* - Constants + Testing Configuration ********************************* */ -const API_KEY = "f1eb0732aa36b48c85267620f68aa926"; -let API_URL_WEATHER_NOW = `https://api.openweathermap.org/data/2.5/weather?q=${apiQueryCity}&units=metric&APPID=${API_KEY}`; -let API_URL_WEATHER_FORECAST = `https://api.openweathermap.org/data/2.5/forecast?q=${apiQueryCity}&units=metric&APPID=${API_KEY}`; +// Set to true to use mock data instead of real API data +const useMockData = false; // Change to false to use real data /* ********************************* - DOM selectors + DOM Selectors ********************************* */ +// Main app container const app = document.getElementById("app"); + +// Container for today's weather const weatherTodayContainer = document.getElementById("weather-today"); + +// Container for weather forecast const weatherForecastContainer = document.getElementById("weather-forecast"); +// List element for the forecast data +const forecastList = document.getElementById("weather-forecast__list"); + /* ********************************* - Weather types + Weather Types ********************************* */ +// Object containing data for different weather conditions const weatherTypes = { clouds: { className: "is-cloudy", - mainTitle: (city) => - `Light a fire and get cosy. ${city} is looking grey today.`, - imgSrc: "./assets/images/cloudy.svg", - imgAlt: "Clouds", + dayMessage: "Light a fire and get cosy. {city} is looking grey today.", + dayImgSrc: "./assets/animated/cloudy.svg", + dayImgAlt: "Clouds", + nightMessage: "Light a fire and get cosy. {city} is looking grey today.", + nightImgSrc: "./assets/animated/cloudy.svg", + nightImgAlt: "Clouds", }, clear: { className: "is-clear", - mainTitle: (city) => - `Get your sunnies on. ${city} is looking rather great today.`, - imgSrc: "./assets/images/sunny.svg", - imgAlt: "Sunglasses", + dayMessage: "Get your sunnies on. {city} is looking rather great today.", + dayImgSrc: "./assets/animated/clear-day.svg", + dayImgAlt: "Sun spinning", + nightMessage: "It's a clear and beautiful night in {city}.", + nightImgSrc: "./assets/animated/clear-night.svg", + nightImgAlt: "Clear night", }, drizzle: { className: "is-drizzly", - mainTitle: (city) => - `There's a light drizzle in ${city}. Keep your raincoat handy!`, - imgSrc: "./assets/images/drizzle.svg", - imgAlt: "Raincoat", + dayMessage: "There's a light drizzle in {city}. Keep your raincoat handy!", + dayImgSrc: "./assets/animated/drizzle.svg", + dayImgAlt: "Drizzle", + nightMessage: + "There's a light drizzle in {city}. Keep your raincoat handy!", + nightImgSrc: "./assets/animated/drizzle.svg", + nightImgAlt: "Drizzle", }, rain: { className: "is-rainy", - mainTitle: (city) => - `Get your umbrella! ${city} is looking rather rainy today.`, - imgSrc: "./assets/images/rainy.svg", - imgAlt: "Umbrella", + dayMessage: "Get your umbrella! {city} is looking rather rainy today.", + dayImgSrc: "./assets/animated/raindrops.svg", + dayImgAlt: "Raindrops", + nightMessage: "Get your umbrella! {city} is looking rather rainy today.", + nightImgSrc: "./assets/animated/raindrops.svg", + nightImgAlt: "Raindrops", }, snow: { className: "is-snowy", - mainTitle: (city) => - `Time for snow boots! ${city} is a winter wonderland today.`, - imgSrc: "./assets/images/snowy.svg", - imgAlt: "Snowflake", + dayMessage: "Time for snow boots! {city} is a winter wonderland today.", + dayImgSrc: "./assets/animated/snow.svg", + dayImgAlt: "Cloud with snow", + nightMessage: "Time for snow boots! {city} is a winter wonderland today.", + nightImgSrc: "./assets/animated/snow.svg", + nightImgAlt: "Cloud with snow", }, thunderstorm: { className: "is-thunderstorm", - mainTitle: (city) => - `Stay safe indoors! ${city} is rumbling with a thunderstorm today.`, - imgSrc: "./assets/images/thunderstorm.svg", - imgAlt: "Thunder and clouds", + dayMessage: + "Stay safe indoors! {city} is rumbling with a thunderstorm today.", + dayImgSrc: "./assets/animated/thunderstorms-rain.svg", + dayImgAlt: "Thunder and clouds", + nightMessage: + "Stay safe indoors! {city} is rumbling with a thunderstorm today.", + nightImgSrc: "./assets/animated/thunderstorms-rain.svg", + nightImgAlt: "Thunder and clouds", + }, + mist: { + className: "is-misty", + dayMessage: "It's foggy in {city} today.", + dayImgSrc: "./assets/animated/mist.svg", + dayImgAlt: "Mist", + nightMessage: "It's foggy in {city} today.", + nightImgSrc: "./assets/animated/mist.svg", + nightImgAlt: "Mist", }, default: { - className: "", - mainTitle: (city) => - `The weather in ${city} can't be determined today. Just go outside and have a look.`, - imgSrc: "./assets/images/window.svg", - imgAlt: "Window", + className: "is-default", + dayMessage: + "The weather in {city} can't be determined today. Just go outside and have a look.", + dayImgSrc: "./assets/animated/compass.svg", + dayImgAlt: "Compass", + nightMessage: + "The weather in {city} can't be determined today. Just go outside and have a look.", + nightImgSrc: "./assets/animated/compass.svg", + nightImgAlt: "Compass", }, }; +const weatherClassNames = [ + "is-cloudy", + "is-clear", + "is-drizzly", + "is-rainy", + "is-snowy", + "is-thunderstorm", + "is-misty", + "is-default", +]; + /* ********************************* - Functions + Mock Data for Testing ********************************* */ -const fetchWeather = async (weatherUrl) => { +// Mock data for different weather types +const mockWeatherData = { + clear: { + weather: [{ main: "Clear" }], + main: { temp: 25 }, + sys: { + sunrise: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago + sunset: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now + }, + name: "Mock City", + }, + clouds: { + weather: [{ main: "Clouds" }], + main: { temp: 18 }, + sys: { + sunrise: Math.floor(Date.now() / 1000) - 3600, + sunset: Math.floor(Date.now() / 1000) + 3600, + }, + name: "Mock City", + }, + rain: { + weather: [{ main: "Rain" }], + main: { temp: 15 }, + sys: { + sunrise: Math.floor(Date.now() / 1000) - 3600, + sunset: Math.floor(Date.now() / 1000) + 3600, + }, + name: "Mock City", + }, + snow: { + weather: [{ main: "Snow" }], + main: { temp: -5 }, + sys: { + sunrise: Math.floor(Date.now() / 1000) - 3600, + sunset: Math.floor(Date.now() / 1000) + 3600, + }, + name: "Mock City", + }, + thunderstorm: { + weather: [{ main: "Thunderstorm" }], + main: { temp: 20 }, + sys: { + sunrise: Math.floor(Date.now() / 1000) - 3600, + sunset: Math.floor(Date.now() / 1000) + 3600, + }, + name: "Mock City", + }, + drizzle: { + weather: [{ main: "Drizzle" }], + main: { temp: 17 }, + sys: { + sunrise: Math.floor(Date.now() / 1000) - 3600, + sunset: Math.floor(Date.now() / 1000) + 3600, + }, + name: "Mock City", + }, + mist: { + weather: [{ main: "Mist" }], + main: { temp: 12 }, + sys: { + sunrise: Math.floor(Date.now() / 1000) - 3600, + sunset: Math.floor(Date.now() / 1000) + 3600, + }, + name: "Mock City", + }, + default: { + weather: [{ main: "Default" }], + main: { temp: 12 }, + sys: { + sunrise: Math.floor(Date.now() / 1000) - 3600, + sunset: Math.floor(Date.now() / 1000) + 3600, + }, + name: "Mock City", + }, +}; + +/* ********************************* + Helper Functions +********************************* */ + +/** + * Generates the API URL for current weather of a given city + * @param {string} city - The city name + * @returns {string} - The API URL for fetching current weather + */ +const getWeatherUrl = (city) => { + return `${BASE_URL_WEATHER}?q=${city}&units=metric&APPID=${API_KEY}`; +}; + +/** + * Generates the API URL for weather forecast of a given city + * @param {string} city - The city name + * @returns {string} - The API URL for fetching weather forecast + */ +const getForecastUrl = (city) => { + return `${BASE_URL_FORECAST}?q=${city}&units=metric&APPID=${API_KEY}`; +}; + +/** + * Converts a date string to a weekday abbreviation + * @param {string} dateString - The date string to convert + * @returns {string} - The abbreviated weekday (e.g., "mon") + */ +const getWeekdayAbbreviation = (dateString) => { + const date = new Date(dateString); + const weekdayNumber = date.getDay(); + return weekdays[weekdayNumber]; +}; + +/** + * Formats the temperature value by appending ° symbol if it's a number + * @param {number|string} temp - The temperature value + * @returns {string} - Formatted temperature string + */ +const formatTemperature = (temp) => { + return typeof temp === "number" ? `${temp}°` : temp; +}; + +/** + * Updates the width of the input field based on its content's pixel width. + * @param {HTMLInputElement} inputElement - The input element to resize. + */ +function updateInputWidth(inputElement) { + const temporarySpan = document.createElement("span"); + temporarySpan.style.visibility = "hidden"; + temporarySpan.style.position = "absolute"; + temporarySpan.style.whiteSpace = "pre"; // Preserves spaces and prevents wrapping + temporarySpan.style.font = getComputedStyle(inputElement).font; + temporarySpan.textContent = + inputElement.value || inputElement.placeholder || ""; + document.body.appendChild(temporarySpan); + const width = temporarySpan.getBoundingClientRect().width + 2; // Add some padding + document.body.removeChild(temporarySpan); + inputElement.style.width = width + "px"; +} + +/** + * Fetches weather data from the given API URL or returns mock data for testing + * @param {string} weatherUrl - The API URL for fetching weather data + * @param {string} [mockType] - The weather type to mock (optional) + * @returns {Promise} - The weather data as a JavaScript object + */ +const fetchWeather = async (weatherUrl, mockType = null) => { + // If mockType is provided, return the corresponding mock data + if (useMockData && mockType && mockWeatherData[mockType]) { + // Simulate async operation with a Promise + return new Promise((resolve) => { + setTimeout(() => { + resolve(mockWeatherData[mockType]); + }, 100); // Simulate a short delay + }); + } + + // Existing fetch logic try { const response = await fetch(weatherUrl); @@ -114,22 +336,111 @@ const fetchWeather = async (weatherUrl) => { } }; -// Function for having different content based on current weather -const typeOfWeather = (weatherType, cityName) => { +/** + * Creates the city input field in the DOM. + * @param {HTMLElement} parentElement - The parent element to append the input to. + * @param {string} inputValue - The value to set in the input field. + */ +const createCityInput = (parentElement, inputValue = displayedCityName) => { + // Create the input element + const cityInput = document.createElement("input"); + cityInput.type = "text"; + cityInput.id = "city-input"; + cityInput.value = inputValue; + cityInput.autocomplete = "off"; + + // Append the input to the parentElement before measuring width + parentElement.appendChild(cityInput); + + // Adjust the width based on the new value + updateInputWidth(cityInput); + + // Event listener for 'input' to adjust the width dynamically + cityInput.addEventListener("input", () => { + updateInputWidth(cityInput); + }); + + // Event listener for 'keydown' to handle Enter key press + cityInput.addEventListener("keydown", (event) => { + if (event.key === "Enter") { + handleSearch(event); // Update the city when Enter is pressed + } + }); + + // Event listener for 'click' to select the entire text in the input field + cityInput.addEventListener("click", () => { + cityInput.select(); + }); + + // Event listener for 'blur' to handle when the input loses focus + cityInput.addEventListener("blur", (event) => { + handleSearch(event); // Update the city when input loses focus + }); +}; + +/** + * Determines the type of weather and returns corresponding data + * @param {string} weatherType - The current weather type (e.g., "Clouds") + * @param {boolean} isDaytime - Whether it's currently daytime + * @returns {object} - An object containing the main title, image source, and image alt text + */ +const typeOfWeather = (weatherType, isDaytime) => { const type = weatherTypes[weatherType.toLowerCase()] || weatherTypes.default; - app.classList.add(type.className); + + // Remove all previous weather classes + app.classList.remove(...weatherClassNames); + + // Add the new weather class if it exists + if (type.className) { + app.classList.add(type.className); + } + + // Select message and image based on isDaytime + const mainTitle = isDaytime ? type.dayMessage : type.nightMessage; + const imgSrc = isDaytime ? type.dayImgSrc : type.nightImgSrc; + const imgAlt = isDaytime ? type.dayImgAlt : type.nightImgAlt; return { - mainTitle: type.mainTitle(cityName), - imgSrc: type.imgSrc, - imgAlt: type.imgAlt, + mainTitle, + imgSrc, + imgAlt, }; }; -// 1. Function to render current weather -const currentWeather = async () => { +/** + * Handles the search functionality when the user inputs a city name. + * @param {Event} event - The event object. + */ +const handleSearch = (event) => { + event.preventDefault(); + + const inputElement = event.target; + let city = inputElement.value.trim(); + + // Save user's input to a global variable + userCityInput = city; + + if (city && city !== apiQueryCity) { + apiQueryCity = city; + + // Fetch new data + currentWeather(); + forecastedWeather(); + } +}; + +/* ********************************* + Main Functions +********************************* */ + +/** + * Fetches and displays the current weather for the selected city + */ +const currentWeather = async (mockType = null) => { try { - const weatherRightNow = await fetchWeather(API_URL_WEATHER_NOW); + // Fetch current weather data for the city + const weatherUrl = getWeatherUrl(apiQueryCity); + const weatherRightNow = await fetchWeather(weatherUrl, mockType); if (!weatherRightNow) { weatherTodayContainer.innerHTML = ` @@ -139,74 +450,134 @@ const currentWeather = async () => { return; } + // Update global variables with the new data displayedCityName = weatherRightNow.name; weatherTypeToday = weatherRightNow.weather[0].main; const temp = Math.round(weatherRightNow.main.temp); - const sunrise = new Date( - weatherRightNow.sys.sunrise * 1000 - ).toLocaleTimeString([], { + + // Create Date objects for sunrise and sunset times + const sunriseTime = new Date(weatherRightNow.sys.sunrise * 1000); + const sunsetTime = new Date(weatherRightNow.sys.sunset * 1000); + + // Format sunrise and sunset times to local time strings + const sunrise = sunriseTime.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false, }); - const sunset = new Date( - weatherRightNow.sys.sunset * 1000 - ).toLocaleTimeString([], { + const sunset = sunsetTime.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false, }); - // Get mainTitle, imgSrc and imgAlt from typeOfWeather + // Determine if it's currently daytime + const currentTime = new Date(); + const isDaytime = currentTime >= sunriseTime && currentTime < sunsetTime; + + // Get mainTitle, imgSrc, and imgAlt from typeOfWeather const { mainTitle, imgSrc, imgAlt } = typeOfWeather( weatherTypeToday, - displayedCityName + isDaytime ); + // Set the innerHTML of weatherTodayContainer weatherTodayContainer.innerHTML = ` -
    -

    ${weatherTypeToday} — ${temp}°

    -

    Sunrise ${sunrise}

    -

    Sunset ${sunset}

    -
    - -
    - ${imgAlt} -

    ${mainTitle}

    -
    - `; +
    +

    ${weatherTypeToday} / ${temp}°

    +

    Sunrise ${sunrise}

    +

    Sunset ${sunset}

    +
    + +
    + ${imgAlt} +

    +
    + `; + + const h1 = weatherTodayContainer.querySelector( + "#weather-today__greeting h1" + ); + + // Split the mainTitle at the '{city}' placeholder and insert the input dynamically + let parts = mainTitle.split("{city}"); + h1.innerHTML = ""; + h1.appendChild(document.createTextNode(parts[0])); + createCityInput(h1); // Append the city input field + h1.appendChild(document.createTextNode(parts[1])); } catch (error) { - console.log(error); + // Handle the error gracefully by displaying a message and keeping the input in the

    weatherTodayContainer.innerHTML = ` -

    Oh no!

    -

    ${error.message}

    - `; +
    +

    Oh no! City not found. Try another:

    +
    + `; + + const h1 = weatherTodayContainer.querySelector( + "#weather-today__greeting h1" + ); + createCityInput(h1, userCityInput); // Use helper to append input } }; -currentWeather(); +/** + * Fetches and displays the weather forecast for the selected city + */ +const forecastedWeather = async (mockType = null) => { + // Inline helper function to generate the forecast list + const generateForecastList = (dailyTemperatures) => { + const forecastListItem = document.createDocumentFragment(); + + dailyTemperatures.forEach((day) => { + const li = document.createElement("li"); + li.innerHTML = ` + +

    ${day.weekday}

    +
    + +

    ${formatTemperature(day.highestTempMax)}

    + / +

    ${formatTemperature(day.lowestTempMin)}

    +
    + `; + forecastListItem.appendChild(li); + }); + + forecastList.innerHTML = ""; // Clear previous list items + forecastList.appendChild(forecastListItem); + }; -// 2. Function to render forecast -const forecastedWeather = async () => { try { - const forecastData = await fetchWeather(API_URL_WEATHER_FORECAST); + // Fetch weather forecast data for the city + const forecastUrl = getForecastUrl(apiQueryCity); + const forecastData = await fetchWeather(forecastUrl, mockType); if (!forecastData) { - weatherForecastContainer.innerHTML = ` -

    The forecast is no where to be seen.

    - `; + // If no data, generate days with dashes directly + const today = new Date(); + let dailyTemperatures = []; + + for (let i = 1; i <= 5; i++) { + let nextDay = new Date(today); + nextDay.setDate(today.getDate() + i); + const weekday = getWeekdayAbbreviation(nextDay.toISOString()); + dailyTemperatures.push({ + weekday: weekday, + lowestTempMin: "–", + highestTempMax: "–", + }); + } + + generateForecastList(dailyTemperatures); return; } - // Step 1: Get today's date in 'YYYY-MM-DD' format - const today = new Date().toISOString().split("T")[0]; // This gives 'YYYY-MM-DD' + // Get today's date in 'YYYY-MM-DD' format + const todayDateString = new Date().toISOString().split("T")[0]; - // Step 2: Filter out entries that have today's date + // Filter out entries that have today's date const forecastArray = forecastData.list.filter( - (item) => item.dt_txt.slice(0, 10) !== today + (item) => item.dt_txt.slice(0, 10) !== todayDateString ); // Initialize an array to store the results for each day @@ -218,11 +589,8 @@ const forecastedWeather = async () => { let lowestTempMin = Infinity; let highestTempMax = -Infinity; - // Log weekday - const date = new Date(forecastArray[i].dt_txt); - const weekdayNumber = date.getDay(); - const weekdays = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"]; - const weekday = weekdays[weekdayNumber]; + // Get the weekday + const weekday = getWeekdayAbbreviation(forecastArray[i].dt_txt); // Loop through each of the 8 items for the current day for (let j = i; j < i + 8 && j < forecastArray.length; j++) { @@ -243,67 +611,45 @@ const forecastedWeather = async () => { // Store the results for the current day dailyTemperatures.push({ weekday: weekday, - lowestTempMin: lowestTempMin, - highestTempMax: highestTempMax, + lowestTempMin: Math.round(lowestTempMin), + highestTempMax: Math.round(highestTempMax), }); } - const forecastList = document.getElementById("weather-forecast__list"); - const forecastListItem = document.createDocumentFragment(); + // Generate the forecast list with actual data + generateForecastList(dailyTemperatures); + } catch (error) { + // Generate days with dashes + const today = new Date(); + let dailyTemperatures = []; - dailyTemperatures.forEach((day) => { - const li = document.createElement("li"); - li.innerHTML = ` - -

    ${day.weekday}

    -
    - -

    ${Math.round(day.highestTempMax)}°

    - / -

    ${Math.round(day.lowestTempMin)}°

    -
    - `; - forecastListItem.appendChild(li); - }); + for (let i = 1; i <= 5; i++) { + let nextDay = new Date(today); + nextDay.setDate(today.getDate() + i); + const weekday = getWeekdayAbbreviation(nextDay.toISOString()); + dailyTemperatures.push({ + weekday: weekday, + lowestTempMin: "–", + highestTempMax: "–", + }); + } - forecastList.innerHTML = ""; // Clear previous list items - forecastList.appendChild(forecastListItem); - } catch (error) { - weatherForecastContainer.innerHTML = ` -

    Couldn't catch the forecast

    - `; + // Generate the forecast list with placeholders + generateForecastList(dailyTemperatures); } }; -forecastedWeather(); - -// Function to handle the search functionality -const handleSearch = () => { - event.preventDefault(); - - let city = document.getElementById("city-input").value.trim(); +/* ********************************* + Initial Function Calls +********************************* */ - apiQueryCity = city; - // Update API URLs with the new city - updateApiUrls(city); - // Fetch new data +if (useMockData) { + // Test with different weather types + const testWeatherType = "thunderstorm"; // Change to test different types + currentWeather(testWeatherType); + forecastedWeather(testWeatherType); +} else { + // Use real API data currentWeather(); forecastedWeather(); -}; - -// Listen for click on the search button -document - .getElementById("search-button") - .addEventListener("click", handleSearch); - -// Listen for Enter key press in the input field -document.getElementById("city-input").addEventListener("keydown", (event) => { - if (event.key === "Enter") { - handleSearch(); - } -}); - -const updateApiUrls = (city) => { - API_URL_WEATHER_NOW = `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&APPID=${API_KEY}`; - API_URL_WEATHER_FORECAST = `https://api.openweathermap.org/data/2.5/forecast?q=${city}&units=metric&APPID=${API_KEY}`; -}; +} diff --git a/style.css b/style.css index 3681cdede..0940c1e4c 100644 --- a/style.css +++ b/style.css @@ -40,7 +40,8 @@ body.is-thunderstorm { } body.is-cloudy, -body.is-atmosphere { +body.is-atmosphere, +body.is-misty { color: var(--brown-dark); background-color: var(--brown-light); } @@ -53,7 +54,6 @@ main { min-height: 100vh; align-items: stretch; justify-content: space-between; - margin-bottom: 54px; @media (min-width: 375px) { padding: 2.5rem; @@ -78,7 +78,7 @@ h1 { display: flex; flex-flow: column; gap: 0.25rem; - margin-bottom: 2.5rem; + margin-bottom: 1rem; p { font-size: 1rem; @@ -88,6 +88,18 @@ h1 { font-size: 1.25rem; } } + + span { + margin: 0 0.25rem; + } +} + +.weather-today__greeting { + img { + max-width: 12rem; + position: relative; + left: -1.5rem; + } } .weather-forecast__list { @@ -138,63 +150,24 @@ h1 { } } -.city-search { - position: fixed; - z-index: 5; - bottom: 0; - left: 0; - width: 100%; - - background-color: currentColor; - padding: 1rem; - - @media (min-width: 678px) { - padding: 2rem; - } - - .form-container { - display: flex; - gap: 1rem; - - @media (min-width: 678px) { - gap: 2rem; - } - } - - label { - flex-grow: 2; - span { - position: absolute; - opacity: 0; - } - } +#city-input { + background: transparent; + border: none; + border-bottom: 1px dashed; + font-family: "Montserrat"; + font-size: 2.25rem; + font-weight: 700; + color: currentColor; + padding: 0; - input { - font-size: 1rem; - border: none; - border-radius: 0.25rem; - padding: 0.5rem 0.75rem; - width: 100%; - - @media (min-width: 678px) { - font-size: 1.125rem; - padding: 1rem 1.5rem; - } + &::selection { + background-color: #fff; } - button { + &:focus { + outline: none; background-color: #fff; - color: currentColor; - font-size: 1rem; - font-weight: 700; - border: none; - border-radius: 2rem; - box-shadow: none; - padding: 0.5rem 1.25rem; - - @media (min-width: 678px) { - font-size: 1.125rem; - padding: 1rem 1.75rem; - } + border-radius: 0.5rem; + border-bottom-color: transparent; } } From 05c1516e6851c7df305faaba39b45485abe981ee Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Sun, 29 Sep 2024 17:35:38 +0200 Subject: [PATCH 04/43] Removed images --- assets/images/.DS_Store | Bin 6148 -> 0 bytes assets/images/clouds-with-sun.svg | 17 ----------------- assets/images/cloudy.svg | 7 ------- assets/images/drizzle.svg | 25 ------------------------- assets/images/rainbow.svg | 4 ---- assets/images/rainy.svg | 16 ---------------- assets/images/snowy.svg | 13 ------------- assets/images/sunny.svg | 23 ----------------------- assets/images/sunset.svg | 13 ------------- assets/images/thunderstorm.svg | 6 ------ assets/images/window.svg | 3 --- 11 files changed, 127 deletions(-) delete mode 100644 assets/images/.DS_Store delete mode 100644 assets/images/clouds-with-sun.svg delete mode 100644 assets/images/cloudy.svg delete mode 100644 assets/images/drizzle.svg delete mode 100644 assets/images/rainbow.svg delete mode 100644 assets/images/rainy.svg delete mode 100644 assets/images/snowy.svg delete mode 100644 assets/images/sunny.svg delete mode 100644 assets/images/sunset.svg delete mode 100644 assets/images/thunderstorm.svg delete mode 100644 assets/images/window.svg diff --git a/assets/images/.DS_Store b/assets/images/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 - - - - - - - - - - - - - - - - diff --git a/assets/images/cloudy.svg b/assets/images/cloudy.svg deleted file mode 100644 index deead5fb9..000000000 --- a/assets/images/cloudy.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/assets/images/drizzle.svg b/assets/images/drizzle.svg deleted file mode 100644 index 727bd8bc4..000000000 --- a/assets/images/drizzle.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/images/rainbow.svg b/assets/images/rainbow.svg deleted file mode 100644 index c4bccae66..000000000 --- a/assets/images/rainbow.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/images/rainy.svg b/assets/images/rainy.svg deleted file mode 100644 index 8a414b15f..000000000 --- a/assets/images/rainy.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/assets/images/snowy.svg b/assets/images/snowy.svg deleted file mode 100644 index 1719f812a..000000000 --- a/assets/images/snowy.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/assets/images/sunny.svg b/assets/images/sunny.svg deleted file mode 100644 index a1fcd7e8b..000000000 --- a/assets/images/sunny.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/images/sunset.svg b/assets/images/sunset.svg deleted file mode 100644 index 30b425b88..000000000 --- a/assets/images/sunset.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/assets/images/thunderstorm.svg b/assets/images/thunderstorm.svg deleted file mode 100644 index 77a754317..000000000 --- a/assets/images/thunderstorm.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/assets/images/window.svg b/assets/images/window.svg deleted file mode 100644 index fdae950db..000000000 --- a/assets/images/window.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - From 1460e8ac035875c5a3449889072329c6f2a263b1 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Sun, 29 Sep 2024 17:38:03 +0200 Subject: [PATCH 05/43] Favicon update --- favicon.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/favicon.svg b/favicon.svg index 10d04a603..bd486fa63 100644 --- a/favicon.svg +++ b/favicon.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 059335bdcacc52aea66d320976aa71f6088394d1 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Sun, 29 Sep 2024 17:39:55 +0200 Subject: [PATCH 06/43] Added width and height to image to avoid layout shifts --- script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script.js b/script.js index 3aa99699b..38205d4a1 100644 --- a/script.js +++ b/script.js @@ -490,7 +490,7 @@ const currentWeather = async (mockType = null) => {
    - ${imgAlt} + ${imgAlt}

    `; From b5b3b0f61a6d7848d1f917ef48c5d85785c9398d Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Sun, 29 Sep 2024 18:15:43 +0200 Subject: [PATCH 07/43] Wait for fonts to load to avoid error in input width --- script.js | 15 ++++++++++++--- style.css | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/script.js b/script.js index 38205d4a1..104d03853 100644 --- a/script.js +++ b/script.js @@ -352,9 +352,6 @@ const createCityInput = (parentElement, inputValue = displayedCityName) => { // Append the input to the parentElement before measuring width parentElement.appendChild(cityInput); - // Adjust the width based on the new value - updateInputWidth(cityInput); - // Event listener for 'input' to adjust the width dynamically cityInput.addEventListener("input", () => { updateInputWidth(cityInput); @@ -376,6 +373,18 @@ const createCityInput = (parentElement, inputValue = displayedCityName) => { cityInput.addEventListener("blur", (event) => { handleSearch(event); // Update the city when input loses focus }); + + // Wait for fonts to be loaded before adjusting the width + if (document.fonts && document.fonts.ready) { + document.fonts.ready.then(() => { + updateInputWidth(cityInput); + }); + } else { + // Fallback for browsers that do not support document.fonts + window.addEventListener("load", () => { + updateInputWidth(cityInput); + }); + } }; /** diff --git a/style.css b/style.css index 0940c1e4c..9605314d4 100644 --- a/style.css +++ b/style.css @@ -68,6 +68,7 @@ h1 { font-size: 2rem; margin-top: 1rem; margin-bottom: 2.5rem; + text-wrap: balance; @media (min-width: 375px) { font-size: 2.25rem; From b99c4e01e280d72567f48325c76312ff8a2c9eb2 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Sun, 29 Sep 2024 18:17:24 +0200 Subject: [PATCH 08/43] Small update --- style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/style.css b/style.css index 9605314d4..a9cb0ff66 100644 --- a/style.css +++ b/style.css @@ -154,6 +154,7 @@ h1 { #city-input { background: transparent; border: none; + border-radius: 0; border-bottom: 1px dashed; font-family: "Montserrat"; font-size: 2.25rem; From 8073aeac8ffa0f9fc30815bac5174c0edf246105 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Mon, 30 Sep 2024 09:46:13 +0200 Subject: [PATCH 09/43] Fixed messages and images for nighttime --- assets/animated/clear-night.svg | 2 +- assets/animated/compass-night.svg | 1 + assets/animated/compass.svg | 2 +- assets/animated/extreme-night-drizzle.svg | 2 +- assets/animated/extreme-night-rain.svg | 2 +- assets/animated/extreme-night-snow.svg | 2 +- assets/animated/mist-night.svg | 1 + assets/animated/partly-cloudy-night.svg | 2 +- .../thunderstorms-night-extreme-rain.svg | 2 +- script.js | 76 ++++++++++--------- style.css | 29 ++++++- 11 files changed, 78 insertions(+), 43 deletions(-) create mode 100644 assets/animated/compass-night.svg create mode 100644 assets/animated/mist-night.svg diff --git a/assets/animated/clear-night.svg b/assets/animated/clear-night.svg index d5d2701ea..0410a9bc9 100644 --- a/assets/animated/clear-night.svg +++ b/assets/animated/clear-night.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/compass-night.svg b/assets/animated/compass-night.svg new file mode 100644 index 000000000..795d0f200 --- /dev/null +++ b/assets/animated/compass-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/compass.svg b/assets/animated/compass.svg index fa8ec1f96..ec4c3c8d6 100644 --- a/assets/animated/compass.svg +++ b/assets/animated/compass.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/extreme-night-drizzle.svg b/assets/animated/extreme-night-drizzle.svg index af4226175..b9ce38a22 100644 --- a/assets/animated/extreme-night-drizzle.svg +++ b/assets/animated/extreme-night-drizzle.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/extreme-night-rain.svg b/assets/animated/extreme-night-rain.svg index 806950613..09fe870b7 100644 --- a/assets/animated/extreme-night-rain.svg +++ b/assets/animated/extreme-night-rain.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/extreme-night-snow.svg b/assets/animated/extreme-night-snow.svg index fdded1c51..5cf294de1 100644 --- a/assets/animated/extreme-night-snow.svg +++ b/assets/animated/extreme-night-snow.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/mist-night.svg b/assets/animated/mist-night.svg new file mode 100644 index 000000000..9bde7c25f --- /dev/null +++ b/assets/animated/mist-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-night.svg b/assets/animated/partly-cloudy-night.svg index e7baec890..9f7cc6d4e 100644 --- a/assets/animated/partly-cloudy-night.svg +++ b/assets/animated/partly-cloudy-night.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/thunderstorms-night-extreme-rain.svg b/assets/animated/thunderstorms-night-extreme-rain.svg index 14da76852..473d054db 100644 --- a/assets/animated/thunderstorms-night-extreme-rain.svg +++ b/assets/animated/thunderstorms-night-extreme-rain.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/script.js b/script.js index 104d03853..68fa6ed42 100644 --- a/script.js +++ b/script.js @@ -34,6 +34,7 @@ let userCityInput = ""; // Set to true to use mock data instead of real API data const useMockData = false; // Change to false to use real data +const simulateNighttime = false; // Set to true to force nighttime for testing /* ********************************* DOM Selectors @@ -62,8 +63,9 @@ const weatherTypes = { dayMessage: "Light a fire and get cosy. {city} is looking grey today.", dayImgSrc: "./assets/animated/cloudy.svg", dayImgAlt: "Clouds", - nightMessage: "Light a fire and get cosy. {city} is looking grey today.", - nightImgSrc: "./assets/animated/cloudy.svg", + nightMessage: + "The clouds linger over {city}, creating a calm, moody night.", + nightImgSrc: "./assets/animated/partly-cloudy-night.svg", nightImgAlt: "Clouds", }, clear: { @@ -80,9 +82,8 @@ const weatherTypes = { dayMessage: "There's a light drizzle in {city}. Keep your raincoat handy!", dayImgSrc: "./assets/animated/drizzle.svg", dayImgAlt: "Drizzle", - nightMessage: - "There's a light drizzle in {city}. Keep your raincoat handy!", - nightImgSrc: "./assets/animated/drizzle.svg", + nightMessage: "A soft drizzle falls over {city} this night.", + nightImgSrc: "./assets/animated/extreme-night-drizzle.svg", nightImgAlt: "Drizzle", }, rain: { @@ -90,8 +91,8 @@ const weatherTypes = { dayMessage: "Get your umbrella! {city} is looking rather rainy today.", dayImgSrc: "./assets/animated/raindrops.svg", dayImgAlt: "Raindrops", - nightMessage: "Get your umbrella! {city} is looking rather rainy today.", - nightImgSrc: "./assets/animated/raindrops.svg", + nightMessage: "The rain pours down in {city} – a perfect night to stay in.", + nightImgSrc: "./assets/animated/extreme-night-rain.svg", nightImgAlt: "Raindrops", }, snow: { @@ -99,8 +100,8 @@ const weatherTypes = { dayMessage: "Time for snow boots! {city} is a winter wonderland today.", dayImgSrc: "./assets/animated/snow.svg", dayImgAlt: "Cloud with snow", - nightMessage: "Time for snow boots! {city} is a winter wonderland today.", - nightImgSrc: "./assets/animated/snow.svg", + nightMessage: "Snow falls on {city} tonight. A peaceful winter night.", + nightImgSrc: "./assets/animated/extreme-night-snow.svg", nightImgAlt: "Cloud with snow", }, thunderstorm: { @@ -109,9 +110,8 @@ const weatherTypes = { "Stay safe indoors! {city} is rumbling with a thunderstorm today.", dayImgSrc: "./assets/animated/thunderstorms-rain.svg", dayImgAlt: "Thunder and clouds", - nightMessage: - "Stay safe indoors! {city} is rumbling with a thunderstorm today.", - nightImgSrc: "./assets/animated/thunderstorms-rain.svg", + nightMessage: "Thunder rolls through the night sky in {city}.", + nightImgSrc: "./assets/animated/thunderstorms-night-extreme-rain.svg", nightImgAlt: "Thunder and clouds", }, mist: { @@ -119,19 +119,19 @@ const weatherTypes = { dayMessage: "It's foggy in {city} today.", dayImgSrc: "./assets/animated/mist.svg", dayImgAlt: "Mist", - nightMessage: "It's foggy in {city} today.", - nightImgSrc: "./assets/animated/mist.svg", + nightMessage: + "A thick mist settles over {city}, making the night mysterious and quiet.", + nightImgSrc: "./assets/animated/mist-night.svg", nightImgAlt: "Mist", }, default: { className: "is-default", - dayMessage: - "The weather in {city} can't be determined today. Just go outside and have a look.", + dayMessage: "The weather in {city} can't be determined today.", dayImgSrc: "./assets/animated/compass.svg", dayImgAlt: "Compass", nightMessage: - "The weather in {city} can't be determined today. Just go outside and have a look.", - nightImgSrc: "./assets/animated/compass.svg", + "It's hard to tell what the weather is like tonight in {city}.", + nightImgSrc: "./assets/animated/compass-night.svg", nightImgAlt: "Compass", }, }; @@ -157,8 +157,8 @@ const mockWeatherData = { weather: [{ main: "Clear" }], main: { temp: 25 }, sys: { - sunrise: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago - sunset: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now + sunrise: Math.floor(Date.now() / 1000) - 7200, // 2 hours ago + sunset: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago, it's night }, name: "Mock City", }, @@ -166,8 +166,8 @@ const mockWeatherData = { weather: [{ main: "Clouds" }], main: { temp: 18 }, sys: { - sunrise: Math.floor(Date.now() / 1000) - 3600, - sunset: Math.floor(Date.now() / 1000) + 3600, + sunrise: Math.floor(Date.now() / 1000) - 7200, // 2 hours ago + sunset: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago, it's night }, name: "Mock City", }, @@ -175,8 +175,8 @@ const mockWeatherData = { weather: [{ main: "Rain" }], main: { temp: 15 }, sys: { - sunrise: Math.floor(Date.now() / 1000) - 3600, - sunset: Math.floor(Date.now() / 1000) + 3600, + sunrise: Math.floor(Date.now() / 1000) - 7200, // 2 hours ago + sunset: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago, it's night }, name: "Mock City", }, @@ -184,8 +184,8 @@ const mockWeatherData = { weather: [{ main: "Snow" }], main: { temp: -5 }, sys: { - sunrise: Math.floor(Date.now() / 1000) - 3600, - sunset: Math.floor(Date.now() / 1000) + 3600, + sunrise: Math.floor(Date.now() / 1000) - 7200, // 2 hours ago + sunset: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago, it's night }, name: "Mock City", }, @@ -193,8 +193,8 @@ const mockWeatherData = { weather: [{ main: "Thunderstorm" }], main: { temp: 20 }, sys: { - sunrise: Math.floor(Date.now() / 1000) - 3600, - sunset: Math.floor(Date.now() / 1000) + 3600, + sunrise: Math.floor(Date.now() / 1000) - 7200, // 2 hours ago + sunset: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago, it's night }, name: "Mock City", }, @@ -202,8 +202,8 @@ const mockWeatherData = { weather: [{ main: "Drizzle" }], main: { temp: 17 }, sys: { - sunrise: Math.floor(Date.now() / 1000) - 3600, - sunset: Math.floor(Date.now() / 1000) + 3600, + sunrise: Math.floor(Date.now() / 1000) - 7200, // 2 hours ago + sunset: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago, it's night }, name: "Mock City", }, @@ -211,8 +211,8 @@ const mockWeatherData = { weather: [{ main: "Mist" }], main: { temp: 12 }, sys: { - sunrise: Math.floor(Date.now() / 1000) - 3600, - sunset: Math.floor(Date.now() / 1000) + 3600, + sunrise: Math.floor(Date.now() / 1000) - 7200, // 2 hours ago + sunset: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago, it's night }, name: "Mock City", }, @@ -220,8 +220,8 @@ const mockWeatherData = { weather: [{ main: "Default" }], main: { temp: 12 }, sys: { - sunrise: Math.floor(Date.now() / 1000) - 3600, - sunset: Math.floor(Date.now() / 1000) + 3600, + sunrise: Math.floor(Date.now() / 1000) - 7200, // 2 hours ago + sunset: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago, it's night }, name: "Mock City", }, @@ -404,6 +404,9 @@ const typeOfWeather = (weatherType, isDaytime) => { app.classList.add(type.className); } + // Add is-nighttime as class on the app container when sun is down + !isDaytime && app.classList.add("is-nighttime"); + // Select message and image based on isDaytime const mainTitle = isDaytime ? type.dayMessage : type.nightMessage; const imgSrc = isDaytime ? type.dayImgSrc : type.nightImgSrc; @@ -482,7 +485,10 @@ const currentWeather = async (mockType = null) => { // Determine if it's currently daytime const currentTime = new Date(); - const isDaytime = currentTime >= sunriseTime && currentTime < sunsetTime; + const isDaytime = + useMockData && simulateNighttime === false + ? true // Force daytime during mock testing if simulateNighttime is false + : currentTime >= sunriseTime && currentTime < sunsetTime; // Get mainTitle, imgSrc, and imgAlt from typeOfWeather const { mainTitle, imgSrc, imgAlt } = typeOfWeather( diff --git a/style.css b/style.css index a9cb0ff66..ff51be0cc 100644 --- a/style.css +++ b/style.css @@ -6,7 +6,9 @@ --yellow-light: #f7e9b9; --yellow-dark: #2a5510; --brown-light: #f1eaea; - --brown-dark: #645353; + --brown-dark: #534a4a; + --gray-light: #eeeeee; + --gray-dark: #464646; } *, @@ -31,12 +33,22 @@ body.is-snowy, body.is-drizzly { color: var(--blue-dark); background-color: var(--blue-light); + + &.is-nighttime { + color: var(--blue-light); + background-color: var(--blue-dark); + } } body.is-clear, body.is-thunderstorm { color: var(--yellow-dark); background-color: var(--yellow-light); + + &.is-nighttime { + color: var(--yellow-light); + background-color: var(--blue-dark); + } } body.is-cloudy, @@ -44,6 +56,21 @@ body.is-atmosphere, body.is-misty { color: var(--brown-dark); background-color: var(--brown-light); + + &.is-nighttime { + color: var(--brown-light); + background-color: var(--brown-dark); + } +} + +body.is-default { + color: var(--gray-dark); + background-color: var(--gray-light); + + &.is-nighttime { + color: var(--gray-light); + background-color: var(--gray-dark); + } } main { From 58d140fe0f0616eb7cfa572fb841baffb8561fe3 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Mon, 30 Sep 2024 09:57:22 +0200 Subject: [PATCH 10/43] Remove is-nighttime if day --- script.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/script.js b/script.js index 68fa6ed42..d617242f8 100644 --- a/script.js +++ b/script.js @@ -405,7 +405,9 @@ const typeOfWeather = (weatherType, isDaytime) => { } // Add is-nighttime as class on the app container when sun is down - !isDaytime && app.classList.add("is-nighttime"); + isDaytime + ? app.classList.remove("is-nighttime") + : app.classList.add("is-nighttime"); // Select message and image based on isDaytime const mainTitle = isDaytime ? type.dayMessage : type.nightMessage; From 59817fb58509d939c26b3187f243fe934a2077ba Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Mon, 30 Sep 2024 10:41:30 +0200 Subject: [PATCH 11/43] ReadMe --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9b4b47886..35aa17271 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ The forecast was trickier to figure out. I first tried to just select 12:00 for I also decided to add a search in order to be able to switch city. I made it inline with the app title, which was a bit tricky to make work. +If I had more time I would have looked into timezones. Right now the sunrise and sunset times show the local times for the user, and not the city that's been entered. + ## View it live [https://weatherington.netlify.app/](https://weatherington.netlify.app/) From 0b72c49e5aca731890571783de7213523d9bc659 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Mon, 30 Sep 2024 10:44:59 +0200 Subject: [PATCH 12/43] Small update --- script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script.js b/script.js index d617242f8..144d10143 100644 --- a/script.js +++ b/script.js @@ -523,7 +523,7 @@ const currentWeather = async (mockType = null) => { createCityInput(h1); // Append the city input field h1.appendChild(document.createTextNode(parts[1])); } catch (error) { - // Handle the error gracefully by displaying a message and keeping the input in the

    + // Handle the error by displaying a message and keeping the input in the

    weatherTodayContainer.innerHTML = `

    Oh no! City not found. Try another:

    From 0001a478f55c8c8bbb483bb09aa349752480210d Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Mon, 30 Sep 2024 11:50:48 +0200 Subject: [PATCH 13/43] New message --- script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script.js b/script.js index 144d10143..fa0161164 100644 --- a/script.js +++ b/script.js @@ -120,7 +120,7 @@ const weatherTypes = { dayImgSrc: "./assets/animated/mist.svg", dayImgAlt: "Mist", nightMessage: - "A thick mist settles over {city}, making the night mysterious and quiet.", + "A thick mist settles over {city} on this mysterious and quiet night.", nightImgSrc: "./assets/animated/mist-night.svg", nightImgAlt: "Mist", }, From e992ef35d546c8a3c51e06b8ddb9908c453bca48 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Mon, 30 Sep 2024 11:53:52 +0200 Subject: [PATCH 14/43] Nighttime fix --- style.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/style.css b/style.css index ff51be0cc..4c0057396 100644 --- a/style.css +++ b/style.css @@ -193,6 +193,15 @@ h1 { background-color: #fff; } + .is-nighttime & { + &:focus { + background-color: #000; + } + &::selection { + background-color: #000; + } + } + &:focus { outline: none; background-color: #fff; From 1d681d35ca0200b41cebe1ba7be9277b8f9c7734 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Mon, 30 Sep 2024 22:12:22 +0200 Subject: [PATCH 15/43] Disable Password Managers --- script.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/script.js b/script.js index fa0161164..7739a7771 100644 --- a/script.js +++ b/script.js @@ -349,6 +349,12 @@ const createCityInput = (parentElement, inputValue = displayedCityName) => { cityInput.value = inputValue; cityInput.autocomplete = "off"; + // Try to disable common Password Managers from injecting themselves in the input field + cityInput.setAttribute("data-1p-ignore", ""); + cityInput.setAttribute("data-bwignore", ""); + cityInput.setAttribute("data-lpignore", "true"); + cityInput.setAttribute("data-form-type", "other"); + // Append the input to the parentElement before measuring width parentElement.appendChild(cityInput); @@ -662,7 +668,7 @@ const forecastedWeather = async (mockType = null) => { if (useMockData) { // Test with different weather types - const testWeatherType = "thunderstorm"; // Change to test different types + const testWeatherType = "rain"; // Change to test different types currentWeather(testWeatherType); forecastedWeather(testWeatherType); } else { From 5b2c7a0e961879092fe485e42fa6e44b198aa68a Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Mon, 30 Sep 2024 22:52:26 +0200 Subject: [PATCH 16/43] Clean-up --- index.html | 1 - script.js | 1 + style.css | 26 ++++++++++++++++++-------- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/index.html b/index.html index 4f7f8077d..3cad5e2e0 100644 --- a/index.html +++ b/index.html @@ -9,7 +9,6 @@
    -

    Loading...

    diff --git a/script.js b/script.js index 7739a7771..db241ae52 100644 --- a/script.js +++ b/script.js @@ -278,6 +278,7 @@ function updateInputWidth(inputElement) { temporarySpan.style.visibility = "hidden"; temporarySpan.style.position = "absolute"; temporarySpan.style.whiteSpace = "pre"; // Preserves spaces and prevents wrapping + temporarySpan.style.padding = "0 0.25rem"; temporarySpan.style.font = getComputedStyle(inputElement).font; temporarySpan.textContent = inputElement.value || inputElement.placeholder || ""; diff --git a/style.css b/style.css index 4c0057396..ac19ee14b 100644 --- a/style.css +++ b/style.css @@ -83,7 +83,7 @@ main { justify-content: space-between; @media (min-width: 375px) { - padding: 2.5rem; + padding: 2.25rem; } @media (min-width: 668px) { @@ -92,21 +92,30 @@ main { } h1 { - font-size: 2rem; - margin-top: 1rem; - margin-bottom: 2.5rem; + font-size: 1.875rem; + margin-top: 0; + margin-bottom: 0; text-wrap: balance; @media (min-width: 375px) { font-size: 2.25rem; } + + @media (min-width: 668px) { + margin-top: 1rem; + margin-bottom: 2rem; + } } .weather-today__meta { display: flex; flex-flow: column; gap: 0.25rem; - margin-bottom: 1rem; + margin-bottom: 0; + + @media (min-width: 668px) { + margin-bottom: 1rem; + } p { font-size: 1rem; @@ -182,12 +191,13 @@ h1 { background: transparent; border: none; border-radius: 0; - border-bottom: 1px dashed; + border-bottom: 2px dashed; font-family: "Montserrat"; font-size: 2.25rem; font-weight: 700; color: currentColor; - padding: 0; + padding: 0 0.25rem; + cursor: pointer; &::selection { background-color: #fff; @@ -205,7 +215,7 @@ h1 { &:focus { outline: none; background-color: #fff; + border-color: transparent; border-radius: 0.5rem; - border-bottom-color: transparent; } } From d20bb1dda058b165b36ce7a5f696d0d397a4f95d Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Mon, 30 Sep 2024 22:54:38 +0200 Subject: [PATCH 17/43] Small update --- style.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/style.css b/style.css index ac19ee14b..7e6705e72 100644 --- a/style.css +++ b/style.css @@ -94,7 +94,7 @@ main { h1 { font-size: 1.875rem; margin-top: 0; - margin-bottom: 0; + margin-bottom: 2rem; text-wrap: balance; @media (min-width: 375px) { @@ -103,7 +103,6 @@ h1 { @media (min-width: 668px) { margin-top: 1rem; - margin-bottom: 2rem; } } From c04b4f175eb6686c814549184a5fe702e2ed5a71 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Tue, 1 Oct 2024 15:57:42 +0200 Subject: [PATCH 18/43] Initial changes for better accessibility --- assets/animated/clear-day.svg | 2 +- assets/animated/clear-night.svg | 2 +- assets/animated/cloudy.svg | 2 +- assets/animated/overcast-day-rain.svg | 2 +- assets/animated/partly-cloudy-night.svg | 2 +- assets/animated/rain.svg | 2 +- index.html | 11 +- script.js | 179 ++++++++++++++++++++---- style.css | 93 +++++++++--- 9 files changed, 241 insertions(+), 54 deletions(-) diff --git a/assets/animated/clear-day.svg b/assets/animated/clear-day.svg index dc8d9c0d0..bc2015b3b 100644 --- a/assets/animated/clear-day.svg +++ b/assets/animated/clear-day.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/clear-night.svg b/assets/animated/clear-night.svg index 0410a9bc9..af54a37e2 100644 --- a/assets/animated/clear-night.svg +++ b/assets/animated/clear-night.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/cloudy.svg b/assets/animated/cloudy.svg index dfd2a3be4..cd0074006 100644 --- a/assets/animated/cloudy.svg +++ b/assets/animated/cloudy.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/overcast-day-rain.svg b/assets/animated/overcast-day-rain.svg index 6f23bc625..676c60126 100644 --- a/assets/animated/overcast-day-rain.svg +++ b/assets/animated/overcast-day-rain.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/partly-cloudy-night.svg b/assets/animated/partly-cloudy-night.svg index 9f7cc6d4e..645bcd279 100644 --- a/assets/animated/partly-cloudy-night.svg +++ b/assets/animated/partly-cloudy-night.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/rain.svg b/assets/animated/rain.svg index e7f0e23bf..894035c30 100644 --- a/assets/animated/rain.svg +++ b/assets/animated/rain.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/index.html b/index.html index 3cad5e2e0..1ef508563 100644 --- a/index.html +++ b/index.html @@ -9,10 +9,17 @@
    -
    -

    Loading...

    +
    + +

    Loading weather...

    +
      diff --git a/script.js b/script.js index db241ae52..8cd689c07 100644 --- a/script.js +++ b/script.js @@ -10,7 +10,16 @@ const BASE_URL_WEATHER = "https://api.openweathermap.org/data/2.5/weather"; const BASE_URL_FORECAST = "https://api.openweathermap.org/data/2.5/forecast"; // Array of weekdays for easy reference -const weekdays = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"]; +const weekdaysShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; +const weekdaysLong = [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", +]; /* ********************************* Global Variables @@ -25,9 +34,15 @@ let displayedCityName = "Stockholm"; // Current weather type (e.g., "Clouds", "Clear") let weatherTypeToday = ""; +// Current weather description +let weatherDescriptionToday = ""; + // User input city name during a search let userCityInput = ""; +// Flag to track if the user has interacted +let userHasInteracted = false; + /* ********************************* Testing Configuration ********************************* */ @@ -46,8 +61,8 @@ const app = document.getElementById("app"); // Container for today's weather const weatherTodayContainer = document.getElementById("weather-today"); -// Container for weather forecast -const weatherForecastContainer = document.getElementById("weather-forecast"); +// Container for weather forecast title +const weatherForecastTitle = document.getElementById("weather-forecast__title"); // List element for the forecast data const forecastList = document.getElementById("weather-forecast__list"); @@ -60,7 +75,7 @@ const forecastList = document.getElementById("weather-forecast__list"); const weatherTypes = { clouds: { className: "is-cloudy", - dayMessage: "Light a fire and get cosy. {city} is looking grey today.", + dayMessage: "Light a fire and get cozy. {city} is looking grey today.", dayImgSrc: "./assets/animated/cloudy.svg", dayImgAlt: "Clouds", nightMessage: @@ -89,7 +104,7 @@ const weatherTypes = { rain: { className: "is-rainy", dayMessage: "Get your umbrella! {city} is looking rather rainy today.", - dayImgSrc: "./assets/animated/raindrops.svg", + dayImgSrc: "./assets/animated/rain.svg", dayImgAlt: "Raindrops", nightMessage: "The rain pours down in {city} – a perfect night to stay in.", nightImgSrc: "./assets/animated/extreme-night-rain.svg", @@ -250,14 +265,22 @@ const getForecastUrl = (city) => { }; /** - * Converts a date string to a weekday abbreviation + * Updates the document title to include the currently displayed city name. + */ +const updateDocumentTitle = () => { + document.title = `Weatherington – Currently showing the weather in ${displayedCityName}`; +}; + +/** + * Converts a date string to a weekday name (short or long) * @param {string} dateString - The date string to convert - * @returns {string} - The abbreviated weekday (e.g., "mon") + * @param {boolean} [isLong=false] - Whether to return the long name (e.g., "Monday") + * @returns {string} - The weekday name (e.g., "mon" or "Monday") */ -const getWeekdayAbbreviation = (dateString) => { +const getWeekdayName = (dateString, isLong = false) => { const date = new Date(dateString); const weekdayNumber = date.getDay(); - return weekdays[weekdayNumber]; + return isLong ? weekdaysLong[weekdayNumber] : weekdaysShort[weekdayNumber]; }; /** @@ -288,6 +311,27 @@ function updateInputWidth(inputElement) { inputElement.style.width = width + "px"; } +/** + * Updates the ARIA live region to announce changes to screen reader users. + * @param {string} message - The message to announce. + */ +const updateAriaNotification = (message) => { + if (!userHasInteracted) { + // Do not announce if the user hasn't interacted yet + return; + } + + const ariaNotification = document.getElementById("aria-notification"); + if (ariaNotification) { + // Clear the content to ensure the screen reader detects the change + ariaNotification.textContent = ""; + // Use setTimeout to ensure the content updates properly + setTimeout(() => { + ariaNotification.textContent = message; + }, 100); + } +}; + /** * Fetches weather data from the given API URL or returns mock data for testing * @param {string} weatherUrl - The API URL for fetching weather data @@ -349,19 +393,21 @@ const createCityInput = (parentElement, inputValue = displayedCityName) => { cityInput.id = "city-input"; cityInput.value = inputValue; cityInput.autocomplete = "off"; + cityInput.setAttribute("aria-label", "Change city to update weather"); - // Try to disable common Password Managers from injecting themselves in the input field + // Disable common Password Managers from injecting themselves in the input field cityInput.setAttribute("data-1p-ignore", ""); cityInput.setAttribute("data-bwignore", ""); cityInput.setAttribute("data-lpignore", "true"); cityInput.setAttribute("data-form-type", "other"); - // Append the input to the parentElement before measuring width + // Append the input and hidden city name to the parentElement parentElement.appendChild(cityInput); // Event listener for 'input' to adjust the width dynamically cityInput.addEventListener("input", () => { updateInputWidth(cityInput); + hiddenCityName.textContent = cityInput.value; }); // Event listener for 'keydown' to handle Enter key press @@ -444,6 +490,9 @@ const handleSearch = (event) => { if (city && city !== apiQueryCity) { apiQueryCity = city; + // Set the flag indicating the user has interacted + userHasInteracted = true; + // Fetch new data currentWeather(); forecastedWeather(); @@ -474,26 +523,32 @@ const currentWeather = async (mockType = null) => { // Update global variables with the new data displayedCityName = weatherRightNow.name; weatherTypeToday = weatherRightNow.weather[0].main; + weatherDescriptionToday = weatherRightNow.weather[0].description; const temp = Math.round(weatherRightNow.main.temp); + // Update the document title + updateDocumentTitle(); + // Create Date objects for sunrise and sunset times const sunriseTime = new Date(weatherRightNow.sys.sunrise * 1000); const sunsetTime = new Date(weatherRightNow.sys.sunset * 1000); // Format sunrise and sunset times to local time strings - const sunrise = sunriseTime.toLocaleTimeString([], { + const sunrise = sunriseTime.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", hour12: false, }); - const sunset = sunsetTime.toLocaleTimeString([], { + const sunset = sunsetTime.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", hour12: false, }); - // Determine if it's currently daytime + // Get current time const currentTime = new Date(); + + // Determine if it's currently daytime const isDaytime = useMockData && simulateNighttime === false ? true // Force daytime during mock testing if simulateNighttime is false @@ -505,17 +560,61 @@ const currentWeather = async (mockType = null) => { isDaytime ); + // Format for human-readable date + const humanReadableDate = currentTime.toLocaleDateString("en-GB", { + weekday: "long", + year: "numeric", + month: "short", + day: "numeric", + }); + + // Format for machine-readable date (ISO 8601) + const isoDate = currentTime.toISOString().split("T")[0]; // 'YYYY-MM-DD' + + let sunriseIcon = ` + + + + + + + `; + + let sunsetIcon = ` + + + + + + + + + + + + + + `; + // Set the innerHTML of weatherTodayContainer weatherTodayContainer.innerHTML = ` -
      -

      ${weatherTypeToday} / ${temp}°

      -

      Sunrise ${sunrise}

      -

      Sunset ${sunset}

      -
      -
      - ${imgAlt} + ${imgAlt} +

      + +

      +

      It is ${temp}° and ${weatherDescriptionToday}.

      +
      +
      +

      Time of sunrise today is ${sunrise}

      + +

      Time of sunset today is ${sunset}

      +
      `; @@ -529,6 +628,10 @@ const currentWeather = async (mockType = null) => { h1.appendChild(document.createTextNode(parts[0])); createCityInput(h1); // Append the city input field h1.appendChild(document.createTextNode(parts[1])); + + // Announce the updated weather information to screen reader users + const announcement = `Weather updated for ${displayedCityName}. It is ${temp} degrees and ${weatherDescriptionToday}.`; + updateAriaNotification(announcement); } catch (error) { // Handle the error by displaying a message and keeping the input in the

      weatherTodayContainer.innerHTML = ` @@ -541,6 +644,13 @@ const currentWeather = async (mockType = null) => { "#weather-today__greeting h1" ); createCityInput(h1, userCityInput); // Use helper to append input + + // Update the document title to indicate an error + document.title = `Weatherington – City not found`; + + updateAriaNotification( + `Error: Unable to find a city with the name ${userCityInput}. Please try another.` + ); } }; @@ -556,7 +666,8 @@ const forecastedWeather = async (mockType = null) => { const li = document.createElement("li"); li.innerHTML = ` -

      ${day.weekday}

      + +

      ${day.weekdayLong}

      ${formatTemperature(day.highestTempMax)}

      @@ -576,6 +687,12 @@ const forecastedWeather = async (mockType = null) => { const forecastUrl = getForecastUrl(apiQueryCity); const forecastData = await fetchWeather(forecastUrl, mockType); + // Add the forecast title + const forecastTitle = document.createElement("h2"); + forecastTitle.textContent = "4-day forecast"; + weatherForecastTitle.innerHTML = ""; // Clear previous content + weatherForecastTitle.prepend(forecastTitle); + if (!forecastData) { // If no data, generate days with dashes directly const today = new Date(); @@ -584,9 +701,11 @@ const forecastedWeather = async (mockType = null) => { for (let i = 1; i <= 5; i++) { let nextDay = new Date(today); nextDay.setDate(today.getDate() + i); - const weekday = getWeekdayAbbreviation(nextDay.toISOString()); + const weekdayShort = getWeekdayName(nextDay.toISOString()); + const weekdayLong = getWeekdayName(nextDay.toISOString(), true); dailyTemperatures.push({ - weekday: weekday, + weekdayShort: weekdayShort, + weekdayLong: weekdayLong, lowestTempMin: "–", highestTempMax: "–", }); @@ -614,7 +733,8 @@ const forecastedWeather = async (mockType = null) => { let highestTempMax = -Infinity; // Get the weekday - const weekday = getWeekdayAbbreviation(forecastArray[i].dt_txt); + const weekdayShort = getWeekdayName(forecastArray[i].dt_txt); + const weekdayLong = getWeekdayName(forecastArray[i].dt_txt, true); // Loop through each of the 8 items for the current day for (let j = i; j < i + 8 && j < forecastArray.length; j++) { @@ -634,7 +754,8 @@ const forecastedWeather = async (mockType = null) => { // Store the results for the current day dailyTemperatures.push({ - weekday: weekday, + weekdayShort: weekdayShort, + weekdayLong: weekdayLong, lowestTempMin: Math.round(lowestTempMin), highestTempMax: Math.round(highestTempMax), }); @@ -650,9 +771,11 @@ const forecastedWeather = async (mockType = null) => { for (let i = 1; i <= 5; i++) { let nextDay = new Date(today); nextDay.setDate(today.getDate() + i); - const weekday = getWeekdayAbbreviation(nextDay.toISOString()); + const weekdayShort = getWeekdayName(nextDay.toISOString()); + const weekdayLong = getWeekdayName(nextDay.toISOString(), true); dailyTemperatures.push({ - weekday: weekday, + weekdayShort: weekdayShort, + weekdayLong: weekdayLong, lowestTempMin: "–", highestTempMax: "–", }); diff --git a/style.css b/style.css index 7e6705e72..ebdfcc4ce 100644 --- a/style.css +++ b/style.css @@ -1,12 +1,12 @@ @import url("https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap"); :root { - --blue-light: #bde8fa; - --blue-dark: #164a68; - --yellow-light: #f7e9b9; - --yellow-dark: #2a5510; + --blue-light: #bfecff; + --blue-dark: #123e57; + --yellow-light: #fef3cf; + --yellow-dark: #174b4d; --brown-light: #f1eaea; - --brown-dark: #534a4a; + --brown-dark: #383a4a; --gray-light: #eeeeee; --gray-dark: #464646; } @@ -73,8 +73,20 @@ body.is-default { } } +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + white-space: nowrap; /* Added to prevent text from wrapping */ +} + main { - padding: 1.5rem; + padding: 0.5rem 1.5rem 1.5rem; max-width: 500px; display: flex; flex-direction: column; @@ -83,7 +95,7 @@ main { justify-content: space-between; @media (min-width: 375px) { - padding: 2.25rem; + padding: 0.5rem 2rem 2rem; } @media (min-width: 668px) { @@ -92,49 +104,82 @@ main { } h1 { - font-size: 1.875rem; + font-size: 1.75rem; margin-top: 0; margin-bottom: 2rem; text-wrap: balance; @media (min-width: 375px) { + font-size: 2rem; + } + + @media (min-width: 668px) { font-size: 2.25rem; + margin-top: 1.25rem; + margin-bottom: 1.5rem; } +} + +h2 { + font-size: 1.25rem; +} + +p { + font-size: 1.125rem; + margin-top: 0.5rem; +} + +.text-large { + font-size: 1.5rem; + margin-bottom: 1.5em; @media (min-width: 668px) { - margin-top: 1rem; + font-size: 1.5rem; } } .weather-today__meta { display: flex; - flex-flow: column; - gap: 0.25rem; - margin-bottom: 0; + flex-flow: row; + gap: 0.5rem; + margin-bottom: 2.5rem; @media (min-width: 668px) { - margin-bottom: 1rem; + margin-bottom: 2.5rem; } - p { - font-size: 1rem; + .badge { + display: flex; + align-items: center; + gap: 0.5rem; + + font-size: 0.875rem; + font-weight: 600; + + border: 1px solid currentColor; + border-radius: 0.5rem; + padding: 0.375rem 0.75rem 0.375rem 0.6rem; margin: 0; @media (min-width: 375px) { - font-size: 1.25rem; + font-size: 1rem; } } span { margin: 0 0.25rem; } + + .icon path { + fill: currentColor; + } } .weather-today__greeting { img { max-width: 12rem; position: relative; - left: -1.5rem; + left: -0.875rem; } } @@ -192,12 +237,24 @@ h1 { border-radius: 0; border-bottom: 2px dashed; font-family: "Montserrat"; - font-size: 2.25rem; + font-size: 1.75rem; font-weight: 700; color: currentColor; padding: 0 0.25rem; cursor: pointer; + @media (min-width: 375px) { + font-size: 2rem; + } + + @media (min-width: 678px) { + font-size: 2.25rem; + } + + &:hover { + border-bottom-style: solid; + } + &::selection { background-color: #fff; } From ba913370d73436fece5528398f85f5cb37a3d3d2 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Tue, 1 Oct 2024 22:55:38 +0200 Subject: [PATCH 19/43] Clearer messages for screen reader. Empty alt attributes for decorative images. --- assets/animated/drizzle.svg | 2 +- assets/animated/extreme-night-drizzle.svg | 2 +- assets/animated/extreme-night-rain.svg | 2 +- assets/animated/extreme-night-snow.svg | 2 +- assets/animated/mist.svg | 2 +- assets/animated/rain.svg | 2 +- assets/animated/snow.svg | 2 +- .../thunderstorms-day-overcast-rain.svg | 2 +- .../thunderstorms-night-extreme-rain.svg | 2 +- assets/animated/thunderstorms-rain.svg | 2 +- index.html | 11 +++++ script.js | 46 ++++++------------- style.css | 10 ++-- 13 files changed, 42 insertions(+), 45 deletions(-) diff --git a/assets/animated/drizzle.svg b/assets/animated/drizzle.svg index e7138a531..b7c59fa29 100644 --- a/assets/animated/drizzle.svg +++ b/assets/animated/drizzle.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/extreme-night-drizzle.svg b/assets/animated/extreme-night-drizzle.svg index b9ce38a22..db79e3017 100644 --- a/assets/animated/extreme-night-drizzle.svg +++ b/assets/animated/extreme-night-drizzle.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/extreme-night-rain.svg b/assets/animated/extreme-night-rain.svg index 09fe870b7..2b43e0359 100644 --- a/assets/animated/extreme-night-rain.svg +++ b/assets/animated/extreme-night-rain.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/extreme-night-snow.svg b/assets/animated/extreme-night-snow.svg index 5cf294de1..9606b4076 100644 --- a/assets/animated/extreme-night-snow.svg +++ b/assets/animated/extreme-night-snow.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/mist.svg b/assets/animated/mist.svg index c08748905..ee12632c0 100644 --- a/assets/animated/mist.svg +++ b/assets/animated/mist.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/rain.svg b/assets/animated/rain.svg index 894035c30..1cbb22ba7 100644 --- a/assets/animated/rain.svg +++ b/assets/animated/rain.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/snow.svg b/assets/animated/snow.svg index ad936d2fc..e6d62ebce 100644 --- a/assets/animated/snow.svg +++ b/assets/animated/snow.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/thunderstorms-day-overcast-rain.svg b/assets/animated/thunderstorms-day-overcast-rain.svg index cede3baf9..e1ef885a3 100644 --- a/assets/animated/thunderstorms-day-overcast-rain.svg +++ b/assets/animated/thunderstorms-day-overcast-rain.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/thunderstorms-night-extreme-rain.svg b/assets/animated/thunderstorms-night-extreme-rain.svg index 473d054db..573e2d456 100644 --- a/assets/animated/thunderstorms-night-extreme-rain.svg +++ b/assets/animated/thunderstorms-night-extreme-rain.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/animated/thunderstorms-rain.svg b/assets/animated/thunderstorms-rain.svg index 6ac6ae62c..878e116d4 100644 --- a/assets/animated/thunderstorms-rain.svg +++ b/assets/animated/thunderstorms-rain.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/index.html b/index.html index 1ef508563..f18711776 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,18 @@ + The Weatherington + diff --git a/script.js b/script.js index 8cd689c07..ac07df959 100644 --- a/script.js +++ b/script.js @@ -77,77 +77,61 @@ const weatherTypes = { className: "is-cloudy", dayMessage: "Light a fire and get cozy. {city} is looking grey today.", dayImgSrc: "./assets/animated/cloudy.svg", - dayImgAlt: "Clouds", nightMessage: "The clouds linger over {city}, creating a calm, moody night.", nightImgSrc: "./assets/animated/partly-cloudy-night.svg", - nightImgAlt: "Clouds", }, clear: { className: "is-clear", dayMessage: "Get your sunnies on. {city} is looking rather great today.", dayImgSrc: "./assets/animated/clear-day.svg", - dayImgAlt: "Sun spinning", nightMessage: "It's a clear and beautiful night in {city}.", nightImgSrc: "./assets/animated/clear-night.svg", - nightImgAlt: "Clear night", }, drizzle: { className: "is-drizzly", dayMessage: "There's a light drizzle in {city}. Keep your raincoat handy!", dayImgSrc: "./assets/animated/drizzle.svg", - dayImgAlt: "Drizzle", nightMessage: "A soft drizzle falls over {city} this night.", nightImgSrc: "./assets/animated/extreme-night-drizzle.svg", - nightImgAlt: "Drizzle", }, rain: { className: "is-rainy", dayMessage: "Get your umbrella! {city} is looking rather rainy today.", dayImgSrc: "./assets/animated/rain.svg", - dayImgAlt: "Raindrops", nightMessage: "The rain pours down in {city} – a perfect night to stay in.", nightImgSrc: "./assets/animated/extreme-night-rain.svg", - nightImgAlt: "Raindrops", }, snow: { className: "is-snowy", dayMessage: "Time for snow boots! {city} is a winter wonderland today.", dayImgSrc: "./assets/animated/snow.svg", - dayImgAlt: "Cloud with snow", nightMessage: "Snow falls on {city} tonight. A peaceful winter night.", nightImgSrc: "./assets/animated/extreme-night-snow.svg", - nightImgAlt: "Cloud with snow", }, thunderstorm: { className: "is-thunderstorm", dayMessage: "Stay safe indoors! {city} is rumbling with a thunderstorm today.", - dayImgSrc: "./assets/animated/thunderstorms-rain.svg", - dayImgAlt: "Thunder and clouds", + dayImgSrc: "./assets/animated/thunderstorms-day-overcast-rain.svg", nightMessage: "Thunder rolls through the night sky in {city}.", nightImgSrc: "./assets/animated/thunderstorms-night-extreme-rain.svg", - nightImgAlt: "Thunder and clouds", }, mist: { className: "is-misty", dayMessage: "It's foggy in {city} today.", dayImgSrc: "./assets/animated/mist.svg", - dayImgAlt: "Mist", nightMessage: "A thick mist settles over {city} on this mysterious and quiet night.", nightImgSrc: "./assets/animated/mist-night.svg", - nightImgAlt: "Mist", }, default: { className: "is-default", dayMessage: "The weather in {city} can't be determined today.", dayImgSrc: "./assets/animated/compass.svg", - dayImgAlt: "Compass", nightMessage: "It's hard to tell what the weather is like tonight in {city}.", nightImgSrc: "./assets/animated/compass-night.svg", - nightImgAlt: "Compass", }, }; @@ -407,7 +391,6 @@ const createCityInput = (parentElement, inputValue = displayedCityName) => { // Event listener for 'input' to adjust the width dynamically cityInput.addEventListener("input", () => { updateInputWidth(cityInput); - hiddenCityName.textContent = cityInput.value; }); // Event listener for 'keydown' to handle Enter key press @@ -465,12 +448,10 @@ const typeOfWeather = (weatherType, isDaytime) => { // Select message and image based on isDaytime const mainTitle = isDaytime ? type.dayMessage : type.nightMessage; const imgSrc = isDaytime ? type.dayImgSrc : type.nightImgSrc; - const imgAlt = isDaytime ? type.dayImgAlt : type.nightImgAlt; return { mainTitle, imgSrc, - imgAlt, }; }; @@ -554,11 +535,8 @@ const currentWeather = async (mockType = null) => { ? true // Force daytime during mock testing if simulateNighttime is false : currentTime >= sunriseTime && currentTime < sunsetTime; - // Get mainTitle, imgSrc, and imgAlt from typeOfWeather - const { mainTitle, imgSrc, imgAlt } = typeOfWeather( - weatherTypeToday, - isDaytime - ); + // Get mainTitle and imgSrc from typeOfWeather + const { mainTitle, imgSrc } = typeOfWeather(weatherTypeToday, isDaytime); // Format for human-readable date const humanReadableDate = currentTime.toLocaleDateString("en-GB", { @@ -599,7 +577,7 @@ const currentWeather = async (mockType = null) => { // Set the innerHTML of weatherTodayContainer weatherTodayContainer.innerHTML = `
      - ${imgAlt} +

      @@ -670,9 +648,15 @@ const forecastedWeather = async (mockType = null) => {

      ${day.weekdayLong}

      -

      ${formatTemperature(day.highestTempMax)}

      - / -

      ${formatTemperature(day.lowestTempMin)}

      + +

      Highest temp of the day is ${formatTemperature( + day.highestTempMax + )}

      + + +

      Lowest temp of the day is ${formatTemperature( + day.lowestTempMin + )}

      `; forecastListItem.appendChild(li); @@ -689,7 +673,7 @@ const forecastedWeather = async (mockType = null) => { // Add the forecast title const forecastTitle = document.createElement("h2"); - forecastTitle.textContent = "4-day forecast"; + forecastTitle.textContent = "5-day forecast"; weatherForecastTitle.innerHTML = ""; // Clear previous content weatherForecastTitle.prepend(forecastTitle); @@ -792,7 +776,7 @@ const forecastedWeather = async (mockType = null) => { if (useMockData) { // Test with different weather types - const testWeatherType = "rain"; // Change to test different types + const testWeatherType = "snow"; // Change to test different types currentWeather(testWeatherType); forecastedWeather(testWeatherType); } else { diff --git a/style.css b/style.css index ebdfcc4ce..57008b977 100644 --- a/style.css +++ b/style.css @@ -1,5 +1,3 @@ -@import url("https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap"); - :root { --blue-light: #bfecff; --blue-dark: #123e57; @@ -18,7 +16,6 @@ } body { - font-family: "Montserrat", sans-serif; padding: 0; margin: 0; @@ -176,6 +173,7 @@ p { } .weather-today__greeting { + position: relative; img { max-width: 12rem; position: relative; @@ -236,7 +234,6 @@ p { border: none; border-radius: 0; border-bottom: 2px dashed; - font-family: "Montserrat"; font-size: 1.75rem; font-weight: 700; color: currentColor; @@ -275,3 +272,8 @@ p { border-radius: 0.5rem; } } + +.city-placeholder { + color: transparent; + position: relative; +} From df7a71c2dc1cac237d8ec8f298256f5095709b2a Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Tue, 1 Oct 2024 23:31:32 +0200 Subject: [PATCH 20/43] Display the current city's time in sunrise and sunset --- assets/animated/extreme-night-rain.svg | 2 +- script.js | 55 ++++++++++++++++---------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/assets/animated/extreme-night-rain.svg b/assets/animated/extreme-night-rain.svg index 2b43e0359..48081c389 100644 --- a/assets/animated/extreme-night-rain.svg +++ b/assets/animated/extreme-night-rain.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/script.js b/script.js index ac07df959..748c209bc 100644 --- a/script.js +++ b/script.js @@ -77,8 +77,7 @@ const weatherTypes = { className: "is-cloudy", dayMessage: "Light a fire and get cozy. {city} is looking grey today.", dayImgSrc: "./assets/animated/cloudy.svg", - nightMessage: - "The clouds linger over {city}, creating a calm, moody night.", + nightMessage: "The clouds linger over {city} creating a calm, moody night.", nightImgSrc: "./assets/animated/partly-cloudy-night.svg", }, clear: { @@ -99,7 +98,7 @@ const weatherTypes = { className: "is-rainy", dayMessage: "Get your umbrella! {city} is looking rather rainy today.", dayImgSrc: "./assets/animated/rain.svg", - nightMessage: "The rain pours down in {city} – a perfect night to stay in.", + nightMessage: "The rain pours down in {city}. A perfect night to stay in.", nightImgSrc: "./assets/animated/extreme-night-rain.svg", }, snow: { @@ -121,8 +120,7 @@ const weatherTypes = { className: "is-misty", dayMessage: "It's foggy in {city} today.", dayImgSrc: "./assets/animated/mist.svg", - nightMessage: - "A thick mist settles over {city} on this mysterious and quiet night.", + nightMessage: "A thick mist settles over {city} on this mysterious night.", nightImgSrc: "./assets/animated/mist-night.svg", }, default: { @@ -495,9 +493,9 @@ const currentWeather = async (mockType = null) => { if (!weatherRightNow) { weatherTodayContainer.innerHTML = ` -

      Rain check on the weather!

      -

      Looks like today’s forecast is a no-show. Check back in a bit!

      - `; +

      Rain check on the weather!

      +

      Looks like today’s forecast is a no-show. Check back in a bit!

      + `; return; } @@ -510,36 +508,51 @@ const currentWeather = async (mockType = null) => { // Update the document title updateDocumentTitle(); - // Create Date objects for sunrise and sunset times - const sunriseTime = new Date(weatherRightNow.sys.sunrise * 1000); - const sunsetTime = new Date(weatherRightNow.sys.sunset * 1000); + // Extract the timezone offset (in seconds) from the API response + const timezoneOffsetInSeconds = weatherRightNow.timezone; + + // Create Date objects for sunrise and sunset times (adjusting for the city's timezone) + const sunriseTimeUTC = new Date(weatherRightNow.sys.sunrise * 1000); + const sunsetTimeUTC = new Date(weatherRightNow.sys.sunset * 1000); + + // Calculate local time for sunrise and sunset using the city's timezone offset + const sunriseTimeLocal = new Date( + sunriseTimeUTC.getTime() + timezoneOffsetInSeconds * 1000 + ); + const sunsetTimeLocal = new Date( + sunsetTimeUTC.getTime() + timezoneOffsetInSeconds * 1000 + ); // Format sunrise and sunset times to local time strings - const sunrise = sunriseTime.toLocaleTimeString("en-GB", { + const sunrise = sunriseTimeLocal.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", hour12: false, }); - const sunset = sunsetTime.toLocaleTimeString("en-GB", { + const sunset = sunsetTimeLocal.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", hour12: false, }); - // Get current time - const currentTime = new Date(); + // Get current time (in UTC) + const currentTimeUTC = new Date(); + + // Calculate local current time based on city's timezone + const currentTimeLocal = new Date( + currentTimeUTC.getTime() + timezoneOffsetInSeconds * 1000 + ); - // Determine if it's currently daytime + // Determine if it's currently daytime in the selected city const isDaytime = - useMockData && simulateNighttime === false - ? true // Force daytime during mock testing if simulateNighttime is false - : currentTime >= sunriseTime && currentTime < sunsetTime; + currentTimeLocal >= sunriseTimeLocal && + currentTimeLocal < sunsetTimeLocal; // Get mainTitle and imgSrc from typeOfWeather const { mainTitle, imgSrc } = typeOfWeather(weatherTypeToday, isDaytime); // Format for human-readable date - const humanReadableDate = currentTime.toLocaleDateString("en-GB", { + const humanReadableDate = currentTimeLocal.toLocaleDateString("en-GB", { weekday: "long", year: "numeric", month: "short", @@ -547,7 +560,7 @@ const currentWeather = async (mockType = null) => { }); // Format for machine-readable date (ISO 8601) - const isoDate = currentTime.toISOString().split("T")[0]; // 'YYYY-MM-DD' + const isoDate = currentTimeLocal.toISOString().split("T")[0]; // 'YYYY-MM-DD' let sunriseIcon = ` From bdc8da7d205345f72915c522bf834ec699b58367 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Tue, 1 Oct 2024 23:47:18 +0200 Subject: [PATCH 21/43] Styling tweak --- script.js | 1 - style.css | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/script.js b/script.js index 748c209bc..f8019d2f4 100644 --- a/script.js +++ b/script.js @@ -283,7 +283,6 @@ function updateInputWidth(inputElement) { temporarySpan.style.visibility = "hidden"; temporarySpan.style.position = "absolute"; temporarySpan.style.whiteSpace = "pre"; // Preserves spaces and prevents wrapping - temporarySpan.style.padding = "0 0.25rem"; temporarySpan.style.font = getComputedStyle(inputElement).font; temporarySpan.textContent = inputElement.value || inputElement.placeholder || ""; diff --git a/style.css b/style.css index 57008b977..e09d1be92 100644 --- a/style.css +++ b/style.css @@ -237,7 +237,7 @@ p { font-size: 1.75rem; font-weight: 700; color: currentColor; - padding: 0 0.25rem; + padding: 0; cursor: pointer; @media (min-width: 375px) { From cc9ff53496702635ddf18ec1ecee0806fc1524b9 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Tue, 1 Oct 2024 23:49:53 +0200 Subject: [PATCH 22/43] Styling tweak --- style.css | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/style.css b/style.css index e09d1be92..ad4859eac 100644 --- a/style.css +++ b/style.css @@ -103,7 +103,7 @@ main { h1 { font-size: 1.75rem; margin-top: 0; - margin-bottom: 2rem; + margin-bottom: 1.5rem; text-wrap: balance; @media (min-width: 375px) { @@ -113,7 +113,6 @@ h1 { @media (min-width: 668px) { font-size: 2.25rem; margin-top: 1.25rem; - margin-bottom: 1.5rem; } } @@ -127,7 +126,7 @@ p { } .text-large { - font-size: 1.5rem; + font-size: 1.375rem; margin-bottom: 1.5em; @media (min-width: 668px) { From 84609063e5bac0033434f554deb36af02d7f75ea Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Tue, 1 Oct 2024 23:59:39 +0200 Subject: [PATCH 23/43] Updated the No city found message --- script.js | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/script.js b/script.js index f8019d2f4..d39395f39 100644 --- a/script.js +++ b/script.js @@ -485,6 +485,20 @@ const handleSearch = (event) => { * Fetches and displays the current weather for the selected city */ const currentWeather = async (mockType = null) => { + // Get current time (in UTC) + const currentTimeUTC = new Date(); + + // Format for human-readable date + const humanReadableDate = currentTimeUTC.toLocaleDateString("en-GB", { + weekday: "long", + year: "numeric", + month: "short", + day: "numeric", + }); + + // Format for machine-readable date (ISO 8601) + const isoDate = currentTimeUTC.toISOString().split("T")[0]; // 'YYYY-MM-DD' + try { // Fetch current weather data for the city const weatherUrl = getWeatherUrl(apiQueryCity); @@ -534,9 +548,6 @@ const currentWeather = async (mockType = null) => { hour12: false, }); - // Get current time (in UTC) - const currentTimeUTC = new Date(); - // Calculate local current time based on city's timezone const currentTimeLocal = new Date( currentTimeUTC.getTime() + timezoneOffsetInSeconds * 1000 @@ -550,17 +561,6 @@ const currentWeather = async (mockType = null) => { // Get mainTitle and imgSrc from typeOfWeather const { mainTitle, imgSrc } = typeOfWeather(weatherTypeToday, isDaytime); - // Format for human-readable date - const humanReadableDate = currentTimeLocal.toLocaleDateString("en-GB", { - weekday: "long", - year: "numeric", - month: "short", - day: "numeric", - }); - - // Format for machine-readable date (ISO 8601) - const isoDate = currentTimeLocal.toISOString().split("T")[0]; // 'YYYY-MM-DD' - let sunriseIcon = ` @@ -626,7 +626,12 @@ const currentWeather = async (mockType = null) => { // Handle the error by displaying a message and keeping the input in the

      weatherTodayContainer.innerHTML = `
      -

      Oh no! City not found. Try another:

      + +

      + +

      +

      Sorry, we couldn't find a matching city. Please try another:

      +

      I pray the weather gods are with you this time.

      `; From a13bf9814dce08be30618f09c22a24e77d81a666 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Sat, 5 Oct 2024 22:23:03 +0200 Subject: [PATCH 24/43] Correct times for sunrise/sunset + code cleanup --- script.js | 121 +++++++++++++++++++++++------------------------------- 1 file changed, 52 insertions(+), 69 deletions(-) diff --git a/script.js b/script.js index d39395f39..4e7cb4d75 100644 --- a/script.js +++ b/script.js @@ -25,23 +25,12 @@ const weekdaysLong = [ Global Variables ********************************* */ -// Default city for initial weather display -let apiQueryCity = "Stockholm"; - -// Displayed city name (may differ in case or spelling) -let displayedCityName = "Stockholm"; - -// Current weather type (e.g., "Clouds", "Clear") -let weatherTypeToday = ""; - -// Current weather description -let weatherDescriptionToday = ""; - -// User input city name during a search -let userCityInput = ""; - -// Flag to track if the user has interacted -let userHasInteracted = false; +let apiQueryCity = "Stockholm"; // Default city for initial weather display +let displayedCityName = "Stockholm"; // Displayed city name (may differ in case or spelling) +let weatherTypeToday = ""; // Current weather type (e.g., "Clouds", "Clear") +let weatherDescriptionToday = ""; // Current weather description +let userCityInput = ""; // User input city name during a search +let userHasInteracted = false; // Flag to track if the user has interacted /* ********************************* Testing Configuration @@ -55,23 +44,20 @@ const simulateNighttime = false; // Set to true to force nighttime for testing DOM Selectors ********************************* */ -// Main app container -const app = document.getElementById("app"); - -// Container for today's weather -const weatherTodayContainer = document.getElementById("weather-today"); - -// Container for weather forecast title -const weatherForecastTitle = document.getElementById("weather-forecast__title"); - -// List element for the forecast data -const forecastList = document.getElementById("weather-forecast__list"); +const app = document.getElementById("app"); // Main app container +const weatherTodayContainer = document.getElementById("weather-today"); // Container for today's weather +const weatherForecastTitle = document.getElementById("weather-forecast__title"); // Container for weather forecast title +const forecastList = document.getElementById("weather-forecast__list"); // List element for the forecast data /* ********************************* Weather Types ********************************* */ -// Object containing data for different weather conditions +/** + * An object mapping weather types to their corresponding UI classes and messages. + * Each key represents a weather condition and contains properties for class names, + * messages, and image sources for day and night. + */ const weatherTypes = { clouds: { className: "is-cloudy", @@ -254,24 +240,26 @@ const updateDocumentTitle = () => { }; /** - * Converts a date string to a weekday name (short or long) - * @param {string} dateString - The date string to convert - * @param {boolean} [isLong=false] - Whether to return the long name (e.g., "Monday") - * @returns {string} - The weekday name (e.g., "mon" or "Monday") + * Returns the weekday name for a given date string. + * @param {string} dateString - The date string to convert. + * @param {boolean} [isLong=false] - Whether to return the full weekday name. + * @returns {string} - The weekday name. */ const getWeekdayName = (dateString, isLong = false) => { const date = new Date(dateString); - const weekdayNumber = date.getDay(); - return isLong ? weekdaysLong[weekdayNumber] : weekdaysShort[weekdayNumber]; + if (isNaN(date)) return ""; // Handle invalid date + const options = { weekday: isLong ? "long" : "short" }; + return date.toLocaleDateString("en-US", options); }; /** - * Formats the temperature value by appending ° symbol if it's a number - * @param {number|string} temp - The temperature value - * @returns {string} - Formatted temperature string + * Formats the temperature by appending the degree symbol. + * @param {number|string} temp - The temperature value. + * @returns {string} - The formatted temperature. */ const formatTemperature = (temp) => { - return typeof temp === "number" ? `${temp}°` : temp; + const temperature = parseFloat(temp); + return isNaN(temperature) ? "-" : `${Math.round(temperature)}°`; }; /** @@ -472,8 +460,8 @@ const handleSearch = (event) => { userHasInteracted = true; // Fetch new data - currentWeather(); - forecastedWeather(); + getCurrentWeather(); + getForecast(); } }; @@ -484,7 +472,7 @@ const handleSearch = (event) => { /** * Fetches and displays the current weather for the selected city */ -const currentWeather = async (mockType = null) => { +const getCurrentWeather = async (mockType = null) => { // Get current time (in UTC) const currentTimeUTC = new Date(); @@ -521,47 +509,41 @@ const currentWeather = async (mockType = null) => { // Update the document title updateDocumentTitle(); - // Extract the timezone offset (in seconds) from the API response - const timezoneOffsetInSeconds = weatherRightNow.timezone; + // Get local sunrise timestamp + const localSunriseTimestamp = + weatherRightNow.sys.sunrise + weatherRightNow.timezone; + const localSunsetTimestamp = + weatherRightNow.sys.sunset + weatherRightNow.timezone; - // Create Date objects for sunrise and sunset times (adjusting for the city's timezone) - const sunriseTimeUTC = new Date(weatherRightNow.sys.sunrise * 1000); - const sunsetTimeUTC = new Date(weatherRightNow.sys.sunset * 1000); - - // Calculate local time for sunrise and sunset using the city's timezone offset - const sunriseTimeLocal = new Date( - sunriseTimeUTC.getTime() + timezoneOffsetInSeconds * 1000 - ); - const sunsetTimeLocal = new Date( - sunsetTimeUTC.getTime() + timezoneOffsetInSeconds * 1000 - ); + // Create Date objects for sunrise and sunset times + const localSunrise = new Date(localSunriseTimestamp * 1000); + const localSunset = new Date(localSunsetTimestamp * 1000); // Format sunrise and sunset times to local time strings - const sunrise = sunriseTimeLocal.toLocaleTimeString("en-GB", { + const sunrise = localSunrise.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", hour12: false, + timeZone: "UTC", }); - const sunset = sunsetTimeLocal.toLocaleTimeString("en-GB", { + const sunset = localSunset.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", hour12: false, + timeZone: "UTC", }); // Calculate local current time based on city's timezone - const currentTimeLocal = new Date( - currentTimeUTC.getTime() + timezoneOffsetInSeconds * 1000 - ); + const currentTimeLocal = new Date(currentTimeUTC.getTime() * 1000); // Determine if it's currently daytime in the selected city const isDaytime = - currentTimeLocal >= sunriseTimeLocal && - currentTimeLocal < sunsetTimeLocal; + currentTimeLocal >= localSunrise && currentTimeLocal < localSunset; // Get mainTitle and imgSrc from typeOfWeather const { mainTitle, imgSrc } = typeOfWeather(weatherTypeToday, isDaytime); - let sunriseIcon = ` + const sunriseIcon = ` @@ -570,7 +552,7 @@ const currentWeather = async (mockType = null) => { `; - let sunsetIcon = ` + const sunsetIcon = ` @@ -623,6 +605,7 @@ const currentWeather = async (mockType = null) => { const announcement = `Weather updated for ${displayedCityName}. It is ${temp} degrees and ${weatherDescriptionToday}.`; updateAriaNotification(announcement); } catch (error) { + console.log(error); // Handle the error by displaying a message and keeping the input in the

      weatherTodayContainer.innerHTML = `
      @@ -652,7 +635,7 @@ const currentWeather = async (mockType = null) => { /** * Fetches and displays the weather forecast for the selected city */ -const forecastedWeather = async (mockType = null) => { +const getForecast = async (mockType = null) => { // Inline helper function to generate the forecast list const generateForecastList = (dailyTemperatures) => { const forecastListItem = document.createDocumentFragment(); @@ -794,10 +777,10 @@ const forecastedWeather = async (mockType = null) => { if (useMockData) { // Test with different weather types const testWeatherType = "snow"; // Change to test different types - currentWeather(testWeatherType); - forecastedWeather(testWeatherType); + getCurrentWeather(testWeatherType); + getForecast(testWeatherType); } else { // Use real API data - currentWeather(); - forecastedWeather(); + getCurrentWeather(); + getForecast(); } From 091b286f19b54753a10ef3e1bd92c99f0cbc51aa Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Sat, 5 Oct 2024 23:01:59 +0200 Subject: [PATCH 25/43] Separate mainTitle and input for screen readers --- script.js | 55 ++++++++++++++++++++++++++++++++++++++----------------- style.css | 4 ++-- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/script.js b/script.js index 4e7cb4d75..01b17fdb2 100644 --- a/script.js +++ b/script.js @@ -31,6 +31,7 @@ let weatherTypeToday = ""; // Current weather type (e.g., "Clouds", "Clear") let weatherDescriptionToday = ""; // Current weather description let userCityInput = ""; // User input city name during a search let userHasInteracted = false; // Flag to track if the user has interacted +let idx = 0; // Global index to count number of instances of an element /* ********************************* Testing Configuration @@ -219,17 +220,28 @@ const mockWeatherData = { * @param {string} city - The city name * @returns {string} - The API URL for fetching current weather */ +/** + * Generates the API URL for current weather of a given city. + * + * @param {string} city - The name of the city. + * @returns {string} The complete API URL for fetching current weather data. + */ const getWeatherUrl = (city) => { - return `${BASE_URL_WEATHER}?q=${city}&units=metric&APPID=${API_KEY}`; + return `${BASE_URL_WEATHER}?q=${encodeURIComponent( + city + )}&units=metric&APPID=${API_KEY}`; }; /** - * Generates the API URL for weather forecast of a given city - * @param {string} city - The city name - * @returns {string} - The API URL for fetching weather forecast + * Generates the API URL for weather forecast of a given city. + * + * @param {string} city - The name of the city. + * @returns {string} The complete API URL for fetching weather forecast data. */ const getForecastUrl = (city) => { - return `${BASE_URL_FORECAST}?q=${city}&units=metric&APPID=${API_KEY}`; + return `${BASE_URL_FORECAST}?q=${encodeURIComponent( + city + )}&units=metric&APPID=${API_KEY}`; }; /** @@ -294,7 +306,6 @@ const updateAriaNotification = (message) => { if (ariaNotification) { // Clear the content to ensure the screen reader detects the change ariaNotification.textContent = ""; - // Use setTimeout to ensure the content updates properly setTimeout(() => { ariaNotification.textContent = message; }, 100); @@ -356,10 +367,11 @@ const fetchWeather = async (weatherUrl, mockType = null) => { * @param {string} inputValue - The value to set in the input field. */ const createCityInput = (parentElement, inputValue = displayedCityName) => { + idx++; // Create the input element const cityInput = document.createElement("input"); cityInput.type = "text"; - cityInput.id = "city-input"; + cityInput.id = `city-input-${idx}`; cityInput.value = inputValue; cityInput.autocomplete = "off"; cityInput.setAttribute("aria-label", "Change city to update weather"); @@ -575,7 +587,10 @@ const getCurrentWeather = async (mockType = null) => {

      -

      +

      + + +

      It is ${temp}° and ${weatherDescriptionToday}.

      @@ -590,16 +605,22 @@ const getCurrentWeather = async (mockType = null) => {
      `; - const h1 = weatherTodayContainer.querySelector( - "#weather-today__greeting h1" - ); - // Split the mainTitle at the '{city}' placeholder and insert the input dynamically let parts = mainTitle.split("{city}"); - h1.innerHTML = ""; - h1.appendChild(document.createTextNode(parts[0])); - createCityInput(h1); // Append the city input field - h1.appendChild(document.createTextNode(parts[1])); + + // Create the visual H1 with the input field + const visualH1 = document.getElementById("h1-visual"); + visualH1.innerHTML = ""; + visualH1.appendChild(document.createTextNode(parts[0])); + createCityInput(visualH1); // Append the city input field + visualH1.appendChild(document.createTextNode(parts[1])); + + let screenH1 = document.getElementById("h1-screen"); + screenH1.appendChild( + document.createTextNode( + `${parts[0]}${displayedCityName}${parts[1]}${createCityInput(screenH1)}` + ) + ); // Announce the updated weather information to screen reader users const announcement = `Weather updated for ${displayedCityName}. It is ${temp} degrees and ${weatherDescriptionToday}.`; @@ -614,7 +635,7 @@ const getCurrentWeather = async (mockType = null) => {

      Sorry, we couldn't find a matching city. Please try another:

      -

      I pray the weather gods are with you this time.

      +

      We pray the weather gods are with you this time.

      `; diff --git a/style.css b/style.css index ad4859eac..d54a095bd 100644 --- a/style.css +++ b/style.css @@ -137,7 +137,7 @@ p { .weather-today__meta { display: flex; flex-flow: row; - gap: 0.5rem; + gap: 0.75rem; margin-bottom: 2.5rem; @media (min-width: 668px) { @@ -228,7 +228,7 @@ p { } } -#city-input { +[id^="city-input"] { background: transparent; border: none; border-radius: 0; From 3803057808b00d069b3030789827e799c9b368e6 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Sun, 6 Oct 2024 14:50:42 +0200 Subject: [PATCH 26/43] H1 for screen reader fix --- script.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/script.js b/script.js index 01b17fdb2..cd878b873 100644 --- a/script.js +++ b/script.js @@ -615,12 +615,12 @@ const getCurrentWeather = async (mockType = null) => { createCityInput(visualH1); // Append the city input field visualH1.appendChild(document.createTextNode(parts[1])); + // Create the title for screen readers and append the input field after the title let screenH1 = document.getElementById("h1-screen"); screenH1.appendChild( - document.createTextNode( - `${parts[0]}${displayedCityName}${parts[1]}${createCityInput(screenH1)}` - ) + document.createTextNode(`${parts[0]}${displayedCityName}${parts[1]}`) ); + createCityInput(screenH1); // Append the city input field // Announce the updated weather information to screen reader users const announcement = `Weather updated for ${displayedCityName}. It is ${temp} degrees and ${weatherDescriptionToday}.`; From 9e14206428693cfe7da95ce198b03d8d65b8a603 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Sun, 6 Oct 2024 16:02:44 +0200 Subject: [PATCH 27/43] Added label for screen readers --- script.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/script.js b/script.js index cd878b873..cbe99e696 100644 --- a/script.js +++ b/script.js @@ -368,13 +368,22 @@ const fetchWeather = async (weatherUrl, mockType = null) => { */ const createCityInput = (parentElement, inputValue = displayedCityName) => { idx++; + + // Create the label element + const cityLabel = document.createElement("label"); + cityLabel.setAttribute("for", `city-input-${idx}`); + cityLabel.textContent = "Enter city to update weather"; // Label text + cityLabel.classList.add("sr-only"); + + console.log(cityLabel); + // Create the input element const cityInput = document.createElement("input"); cityInput.type = "text"; cityInput.id = `city-input-${idx}`; cityInput.value = inputValue; cityInput.autocomplete = "off"; - cityInput.setAttribute("aria-label", "Change city to update weather"); + cityInput.setAttribute("aria-label", "Enter city to update weather"); // Disable common Password Managers from injecting themselves in the input field cityInput.setAttribute("data-1p-ignore", ""); @@ -382,7 +391,8 @@ const createCityInput = (parentElement, inputValue = displayedCityName) => { cityInput.setAttribute("data-lpignore", "true"); cityInput.setAttribute("data-form-type", "other"); - // Append the input and hidden city name to the parentElement + // Append the input and label to the parentElement + parentElement.appendChild(cityLabel); parentElement.appendChild(cityInput); // Event listener for 'input' to adjust the width dynamically From a842b66d12db7bf2f2a49c1edce8057591288559 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Sun, 6 Oct 2024 16:11:57 +0200 Subject: [PATCH 28/43] Fix for isDayTime --- script.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/script.js b/script.js index cbe99e696..c7c623dc6 100644 --- a/script.js +++ b/script.js @@ -555,12 +555,9 @@ const getCurrentWeather = async (mockType = null) => { timeZone: "UTC", }); - // Calculate local current time based on city's timezone - const currentTimeLocal = new Date(currentTimeUTC.getTime() * 1000); - // Determine if it's currently daytime in the selected city const isDaytime = - currentTimeLocal >= localSunrise && currentTimeLocal < localSunset; + currentTimeUTC >= localSunrise && currentTimeUTC < localSunset; // Get mainTitle and imgSrc from typeOfWeather const { mainTitle, imgSrc } = typeOfWeather(weatherTypeToday, isDaytime); From f75c1d78b691227a8bad805a8363f5a6c686b5e7 Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Sun, 6 Oct 2024 20:16:06 +0200 Subject: [PATCH 29/43] Fixed error with local current time + tested removing aria notification --- script.js | 59 +++++++++++++++++++++++++++++++------------------------ style.css | 2 +- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/script.js b/script.js index c7c623dc6..becb24bf3 100644 --- a/script.js +++ b/script.js @@ -296,21 +296,21 @@ function updateInputWidth(inputElement) { * Updates the ARIA live region to announce changes to screen reader users. * @param {string} message - The message to announce. */ -const updateAriaNotification = (message) => { - if (!userHasInteracted) { - // Do not announce if the user hasn't interacted yet - return; - } - - const ariaNotification = document.getElementById("aria-notification"); - if (ariaNotification) { - // Clear the content to ensure the screen reader detects the change - ariaNotification.textContent = ""; - setTimeout(() => { - ariaNotification.textContent = message; - }, 100); - } -}; +// const updateAriaNotification = (message) => { +// if (!userHasInteracted) { +// // Do not announce if the user hasn't interacted yet +// return; +// } + +// const ariaNotification = document.getElementById("aria-notification"); +// if (ariaNotification) { +// // Clear the content to ensure the screen reader detects the change +// ariaNotification.textContent = ""; +// setTimeout(() => { +// ariaNotification.textContent = message; +// }, 100); +// } +// }; /** * Fetches weather data from the given API URL or returns mock data for testing @@ -375,8 +375,6 @@ const createCityInput = (parentElement, inputValue = displayedCityName) => { cityLabel.textContent = "Enter city to update weather"; // Label text cityLabel.classList.add("sr-only"); - console.log(cityLabel); - // Create the input element const cityInput = document.createElement("input"); cityInput.type = "text"; @@ -497,6 +495,7 @@ const handleSearch = (event) => { const getCurrentWeather = async (mockType = null) => { // Get current time (in UTC) const currentTimeUTC = new Date(); + const currentTimestampUTC = Date.now(); // Format for human-readable date const humanReadableDate = currentTimeUTC.toLocaleDateString("en-GB", { @@ -536,12 +535,15 @@ const getCurrentWeather = async (mockType = null) => { weatherRightNow.sys.sunrise + weatherRightNow.timezone; const localSunsetTimestamp = weatherRightNow.sys.sunset + weatherRightNow.timezone; + const localCurrentTimestamp = + Math.floor(currentTimestampUTC / 1000) + weatherRightNow.timezone; - // Create Date objects for sunrise and sunset times + // Create local date objects for current time, sunrise and sunset const localSunrise = new Date(localSunriseTimestamp * 1000); const localSunset = new Date(localSunsetTimestamp * 1000); + const localTime = new Date(localCurrentTimestamp * 1000); - // Format sunrise and sunset times to local time strings + // Format local times to local time strings const sunrise = localSunrise.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", @@ -554,10 +556,15 @@ const getCurrentWeather = async (mockType = null) => { hour12: false, timeZone: "UTC", }); + const timeNow = localTime.toLocaleTimeString("en-GB", { + hour: "2-digit", + minute: "2-digit", + hour12: false, + timeZone: "UTC", + }); // Determine if it's currently daytime in the selected city - const isDaytime = - currentTimeUTC >= localSunrise && currentTimeUTC < localSunset; + const isDaytime = timeNow >= sunrise && timeNow < sunset; // Get mainTitle and imgSrc from typeOfWeather const { mainTitle, imgSrc } = typeOfWeather(weatherTypeToday, isDaytime); @@ -630,8 +637,8 @@ const getCurrentWeather = async (mockType = null) => { createCityInput(screenH1); // Append the city input field // Announce the updated weather information to screen reader users - const announcement = `Weather updated for ${displayedCityName}. It is ${temp} degrees and ${weatherDescriptionToday}.`; - updateAriaNotification(announcement); + // const announcement = `Weather updated for ${displayedCityName}. It is ${temp} degrees and ${weatherDescriptionToday}.`; + //updateAriaNotification(announcement); } catch (error) { console.log(error); // Handle the error by displaying a message and keeping the input in the

      @@ -654,9 +661,9 @@ const getCurrentWeather = async (mockType = null) => { // Update the document title to indicate an error document.title = `Weatherington – City not found`; - updateAriaNotification( - `Error: Unable to find a city with the name ${userCityInput}. Please try another.` - ); + // updateAriaNotification( + // `Error: Unable to find a city with the name ${userCityInput}. Please try another.` + // ); } }; diff --git a/style.css b/style.css index d54a095bd..8845e4751 100644 --- a/style.css +++ b/style.css @@ -87,7 +87,7 @@ main { max-width: 500px; display: flex; flex-direction: column; - min-height: 100vh; + min-height: 100svh; align-items: stretch; justify-content: space-between; From f003252b3ad4d4bf563020688c32202b23ea8a2c Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Sun, 6 Oct 2024 20:26:22 +0200 Subject: [PATCH 30/43] Refactored getCurrentWeather --- script.js | 242 +++++++++++++++++++++++------------------------------- 1 file changed, 104 insertions(+), 138 deletions(-) diff --git a/script.js b/script.js index becb24bf3..95779e28e 100644 --- a/script.js +++ b/script.js @@ -490,180 +490,145 @@ const handleSearch = (event) => { ********************************* */ /** - * Fetches and displays the current weather for the selected city + * Helper to format times based on local time in the city */ -const getCurrentWeather = async (mockType = null) => { - // Get current time (in UTC) - const currentTimeUTC = new Date(); - const currentTimestampUTC = Date.now(); +const formatLocalTime = (timestamp, timezone, options = {}) => { + return new Date((timestamp + timezone) * 1000).toLocaleTimeString("en-GB", { + hour: "2-digit", + minute: "2-digit", + hour12: false, + timeZone: "UTC", + ...options, + }); +}; - // Format for human-readable date - const humanReadableDate = currentTimeUTC.toLocaleDateString("en-GB", { +/** + * Helper to set weather today content + */ +const setWeatherTodayContent = ( + temp, + description, + mainTitle, + imgSrc, + sunrise, + sunset +) => { + // Define the sunrise and sunset icons + const sunriseIcon = ` + + + + + + + `; + + const sunsetIcon = ` + + + + + + + + + + + + + + `; + + weatherTodayContainer.innerHTML = ` +
      + +

      +

      +

      It is ${temp}° and ${description}.

      +
      +
      +

      Time of sunrise today is ${sunrise}

      + +

      Time of sunset today is ${sunset}

      + +
      + `; +}; +/** + * Simplified version of the getCurrentWeather function + */ +const getCurrentWeather = async (mockType = null) => { try { - // Fetch current weather data for the city - const weatherUrl = getWeatherUrl(apiQueryCity); - const weatherRightNow = await fetchWeather(weatherUrl, mockType); - + const weatherRightNow = await fetchWeather( + getWeatherUrl(apiQueryCity), + mockType + ); if (!weatherRightNow) { weatherTodayContainer.innerHTML = ` -

      Rain check on the weather!

      -

      Looks like today’s forecast is a no-show. Check back in a bit!

      - `; +

      Rain check on the weather!

      +

      Looks like today’s forecast is a no-show. Check back in a bit!

      + `; return; } - // Update global variables with the new data - displayedCityName = weatherRightNow.name; - weatherTypeToday = weatherRightNow.weather[0].main; - weatherDescriptionToday = weatherRightNow.weather[0].description; - const temp = Math.round(weatherRightNow.main.temp); - - // Update the document title + // Update global variables + const { name, weather, main, sys, timezone } = weatherRightNow; + displayedCityName = name; + weatherTypeToday = weather[0].main; + weatherDescriptionToday = weather[0].description; + const temp = Math.round(main.temp); updateDocumentTitle(); - // Get local sunrise timestamp - const localSunriseTimestamp = - weatherRightNow.sys.sunrise + weatherRightNow.timezone; - const localSunsetTimestamp = - weatherRightNow.sys.sunset + weatherRightNow.timezone; - const localCurrentTimestamp = - Math.floor(currentTimestampUTC / 1000) + weatherRightNow.timezone; - - // Create local date objects for current time, sunrise and sunset - const localSunrise = new Date(localSunriseTimestamp * 1000); - const localSunset = new Date(localSunsetTimestamp * 1000); - const localTime = new Date(localCurrentTimestamp * 1000); - - // Format local times to local time strings - const sunrise = localSunrise.toLocaleTimeString("en-GB", { - hour: "2-digit", - minute: "2-digit", - hour12: false, - timeZone: "UTC", - }); - const sunset = localSunset.toLocaleTimeString("en-GB", { - hour: "2-digit", - minute: "2-digit", - hour12: false, - timeZone: "UTC", - }); - const timeNow = localTime.toLocaleTimeString("en-GB", { - hour: "2-digit", - minute: "2-digit", - hour12: false, - timeZone: "UTC", - }); + // Determine sunrise, sunset, and current time + const sunrise = formatLocalTime(sys.sunrise, timezone); + const sunset = formatLocalTime(sys.sunset, timezone); + const timeNow = formatLocalTime(Date.now() / 1000, timezone); - // Determine if it's currently daytime in the selected city + // Determine if it's daytime and get weather type data const isDaytime = timeNow >= sunrise && timeNow < sunset; - - // Get mainTitle and imgSrc from typeOfWeather const { mainTitle, imgSrc } = typeOfWeather(weatherTypeToday, isDaytime); - const sunriseIcon = ` - - - - - - - `; - - const sunsetIcon = ` - - - - - - - - - - - - - - `; - - // Set the innerHTML of weatherTodayContainer - weatherTodayContainer.innerHTML = ` -
      - -

      - -

      -

      - - -

      -

      It is ${temp}° and ${weatherDescriptionToday}.

      -
      -
      -

      Time of sunrise today is ${sunrise}

      - -

      Time of sunset today is ${sunset}

      - -
      - `; - - // Split the mainTitle at the '{city}' placeholder and insert the input dynamically - let parts = mainTitle.split("{city}"); + // Update weather container + setWeatherTodayContent( + temp, + weatherDescriptionToday, + mainTitle, + imgSrc, + sunrise, + sunset + ); - // Create the visual H1 with the input field + // Update H1 input + const parts = mainTitle.split("{city}"); const visualH1 = document.getElementById("h1-visual"); - visualH1.innerHTML = ""; - visualH1.appendChild(document.createTextNode(parts[0])); + visualH1.innerHTML = parts[0]; createCityInput(visualH1); // Append the city input field visualH1.appendChild(document.createTextNode(parts[1])); - // Create the title for screen readers and append the input field after the title - let screenH1 = document.getElementById("h1-screen"); - screenH1.appendChild( - document.createTextNode(`${parts[0]}${displayedCityName}${parts[1]}`) - ); - createCityInput(screenH1); // Append the city input field - - // Announce the updated weather information to screen reader users - // const announcement = `Weather updated for ${displayedCityName}. It is ${temp} degrees and ${weatherDescriptionToday}.`; - //updateAriaNotification(announcement); + const screenH1 = document.getElementById("h1-screen"); + screenH1.textContent = `${parts[0]}${displayedCityName}${parts[1]}`; } catch (error) { console.log(error); - // Handle the error by displaying a message and keeping the input in the

      weatherTodayContainer.innerHTML = `
      -

      - -

      -

      Sorry, we couldn't find a matching city. Please try another:

      +

      Sorry, we couldn't find a matching city. Please try another:

      We pray the weather gods are with you this time.

      `; - const h1 = weatherTodayContainer.querySelector( "#weather-today__greeting h1" ); - createCityInput(h1, userCityInput); // Use helper to append input - - // Update the document title to indicate an error + createCityInput(h1, userCityInput); document.title = `Weatherington – City not found`; - - // updateAriaNotification( - // `Error: Unable to find a city with the name ${userCityInput}. Please try another.` - // ); } }; @@ -783,6 +748,7 @@ const getForecast = async (mockType = null) => { // Generate the forecast list with actual data generateForecastList(dailyTemperatures); } catch (error) { + console.log(error); // Generate days with dashes const today = new Date(); let dailyTemperatures = []; From 9bbc6a4397522e56da170e47cac801021596513d Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Sun, 6 Oct 2024 20:55:42 +0200 Subject: [PATCH 31/43] Re-added the aria notification --- index.html | 13 +++++++------ script.js | 41 +++++++++++++++++++++++++---------------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/index.html b/index.html index f18711776..4cd2e2330 100644 --- a/index.html +++ b/index.html @@ -19,13 +19,14 @@ +
      -
      +

      Loading weather...

      diff --git a/script.js b/script.js index 95779e28e..e115a3219 100644 --- a/script.js +++ b/script.js @@ -296,21 +296,21 @@ function updateInputWidth(inputElement) { * Updates the ARIA live region to announce changes to screen reader users. * @param {string} message - The message to announce. */ -// const updateAriaNotification = (message) => { -// if (!userHasInteracted) { -// // Do not announce if the user hasn't interacted yet -// return; -// } - -// const ariaNotification = document.getElementById("aria-notification"); -// if (ariaNotification) { -// // Clear the content to ensure the screen reader detects the change -// ariaNotification.textContent = ""; -// setTimeout(() => { -// ariaNotification.textContent = message; -// }, 100); -// } -// }; +const updateAriaNotification = (message) => { + if (!userHasInteracted) { + // Do not announce if the user hasn't interacted yet + return; + } + + const ariaNotification = document.getElementById("aria-notification"); + if (ariaNotification) { + // Clear the content to ensure the screen reader detects the change + ariaNotification.textContent = ""; + setTimeout(() => { + ariaNotification.textContent = message; + }, 100); + } +}; /** * Fetches weather data from the given API URL or returns mock data for testing @@ -585,7 +585,7 @@ const getCurrentWeather = async (mockType = null) => { weatherTypeToday = weather[0].main; weatherDescriptionToday = weather[0].description; const temp = Math.round(main.temp); - updateDocumentTitle(); + //updateDocumentTitle(); // Determine sunrise, sunset, and current time const sunrise = formatLocalTime(sys.sunrise, timezone); @@ -615,6 +615,11 @@ const getCurrentWeather = async (mockType = null) => { const screenH1 = document.getElementById("h1-screen"); screenH1.textContent = `${parts[0]}${displayedCityName}${parts[1]}`; + createCityInput(screenH1); // Append the city input field + + // Announce the updated weather information to screen reader users + const announcement = `${parts[0]}${displayedCityName}${parts[1]} It is ${temp} degrees and ${weatherDescriptionToday}.`; + updateAriaNotification(announcement); } catch (error) { console.log(error); weatherTodayContainer.innerHTML = ` @@ -629,6 +634,10 @@ const getCurrentWeather = async (mockType = null) => { ); createCityInput(h1, userCityInput); document.title = `Weatherington – City not found`; + + updateAriaNotification( + `Sorry, we couldn't find a city called ${userCityInput}. Please try another.` + ); } }; From 836fb271b8b4945fdab3d818ab1295368470742b Mon Sep 17 00:00:00 2001 From: HeleneWestrin Date: Sun, 6 Oct 2024 21:00:58 +0200 Subject: [PATCH 32/43] theme-color added --- index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/index.html b/index.html index 4cd2e2330..7981d3314 100644 --- a/index.html +++ b/index.html @@ -7,6 +7,7 @@ name="description" content="A weather app by Helene Westrin using the OpenWeatherMap API." /> + The Weatherington