<div class="main-app">
    <header>
      <h1 class="title my-transition">Weather App</h1>
      <p class="subtitle my-transition">Simple Weather App using
        <a href="https://darksky.net/dev/">Dark Sky API</a>,
        <a href="https://darkskyapp.github.io/skycons/">Skycons</a>.</p>
    </header>

    <section class="currently my-box-shadow my-transition">
      <div class="heading">
        <div class="title">Currently</div>
        <div class="menu">
          <span class="item active" unit="F">&deg;F</span>
          <span class="item" unit="C">&deg;C</span>
        </div>
      </div>
      <div class="content">
        <div class="item location"></div>
        <div class="item temp"></div>
        <div class="item icon">
          <canvas id="current-icon"></canvas>
        </div>
        <div class="item summary"></div>
        <div class="item humidity">
          <i class="fa fa-tint"></i>
          <span class="detail"></span>
        </div>
      </div>
    </section>

    <section class="next-48h my-box-shadow my-transition">
      <div class="heading">
        <div class="title">Next 48 Hours</div>
        <div class="select-style">
          <select>
          </select>
        </div>
      </div>
      <div class="content">
        <div class="item time"></div>
        <div class="item temp"></div>
        <div class="item icon">
          <canvas id="next48h-icon"></canvas>
        </div>
        <div class="item summary"></div>
        <div class="item humidity">
          <i class="fa fa-tint"></i>
          <span class="detail"></span>
        </div>
      </div>
    </section>

    <section class="next-7d my-box-shadow my-transition">
      <div class="heading">
        <div class="title">Next 7 Days</div>
        <div class="select-style">
          <select>
          </select>
        </div>
      </div>
      <div class="content">
        <div class="item day"></div>
        <div class="item temp"></div>
        <div class="item icon">
          <canvas id="next7d-icon"></canvas>
        </div>
        <div class="item summary"></div>
        <div class="item humidity">
          <i class="fa fa-tint"></i>
          <span class="detail"></span>
        </div>
      </div>
    </section>

    <footer>Made with
      <i class="fa fa-heart heart"></i> by
      <a href="http://about.phamvanlam.com">Lam Pham</a>
    </footer>
  </div>

  <div class="loading">
    <div>Please, waiting for retrieving your location and the local weather.</div>
    <div class="lds-ellipsis">
      <div></div>
      <div></div>
      <div></div>
      <div></div>
    </div>
  </div>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
  <script src="https://rawgit.com/darkskyapp/skycons/master/skycons.js"></script>
* {box-sizing: border-box;}
body {
  margin: 15px;
  background-color: #ededed;
  color: #404040;
  font: normal normal normal 1rem/1.6 Nunito Sans, Helvetica, Arial, sans-serif;
}
header .title{
  line-height: 1.2;
  font-weight: 700;
  font-size: 2.5rem;
  margin: 45px auto;
  margin-bottom: 0;
  text-align: center;
}
header .subtitle {
  text-align: center;
  font-size: 0.8rem;
  margin-bottom: 45px;
  color: #606060 
}
a {
  text-decoration: none;
  color: #3da4ab;
  cursor: pointer;
  -webkit-transition: all 0.35s ease-in-out;
  -moz-transition: all 0.35s ease-in-out;
  transition: all 0.35s ease-in-out;
}
a:hover {
  color: #fe4a49;
}
footer{
  text-align: center;
  margin-top: 15px;
  margin-bottom: 25px;
}
footer .heart {color: #fe4a49;}
p {
  margin-top: 0px;
  margin-bottom: 0px;
}
.my-box-shadow{
  box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
  -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
  -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
}
.my-transition {
  -webkit-transition: all 0.35s ease-in-out;
  -moz-transition: all 0.35s ease-in-out;
  transition: all 0.35s ease-in-out;
}
section {
  width: 100%;
  background-color: #fff;
  margin-bottom: 15px;
}
section:hover {
  box-shadow: 0 2px 5px rgba(0,0,0,0.12), 0 3px 5px rgba(0,0,0,0.24);
  -webkit-box-shadow: 0 2px 5px rgba(0,0,0,0.12), 0 3px 5px rgba(0,0,0,0.24);
  -moz-box-shadow: 0 2px 5px rgba(0,0,0,0.12), 0 3px 5px rgba(0,0,0,0.24);
}
section::after {
  content: "";
  clear: both;
  display: table;
}
.heading {
  position: relative;
  background: #3da4ab;
  color: #fff;
  height: 50px;
  display: flex;
  display: -webkit-flex;
  align-items: center;
  justify-content: space-between;
}
.heading .title {
  font-weight: 500;
  padding-left: 15px;
  float: left;
}
.heading .title
.heading .menu {
  float: right;
}
.heading .menu .item {
  text-align: left;
  background-color: #3da4ab;
  padding: 10px 15px 10px 0px;
}
.heading .menu .item:not(.active):hover {
  text-decoration: underline;
  cursor: pointer;
}
.heading .menu .item.active {
  color: #f6cd61;
}
.select-style {
  border: 1px solid #ccc;
  border-radius: 3px;
  background: #fafafa;
  margin-right: 15px;
  text-align: right;
}
.select-style select {
  cursor: pointer;
  padding: 5px 8px;
  border: none;
  box-shadow: none;
  background: transparent;
  background-image: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  font: normal normal normal 1rem/1.6 Nunito Sans, Helvetica, Arial, sans-serif !important;
}
.select-style select:focus {
  outline: none;
}
.content {
  text-align: center;
  padding: 15px;
}
.content .item {
  margin-bottom: 5px;
}
.content .location, 
.content .time,
.content .day {
  text-transform: uppercase;
  font-weight: 500;
  font-size: 1.15rem;
  margin: 15px auto;
}
.content .temp {font-size: 2.5rem;}
.content .icon canvas {
  width: 150px;
  height: 75px;
}
.main-app {
  opacity: 0.1;
}
/* Loading */
.loading {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  display: flex;
  display: -webkit-flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}
.lds-ellipsis {
  display: inline-block;
  position: relative;
  width: 64px;
  height: 64px;
}
.lds-ellipsis div {
  position: absolute;
  top: 27px;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: #fe4a49;
  animation-timing-function: cubic-bezier(0, 1, 1, 0);
}
.lds-ellipsis div:nth-child(1) {
  left: 6px;
  animation: lds-ellipsis1 0.6s infinite;
}
.lds-ellipsis div:nth-child(2) {
  left: 6px;
  animation: lds-ellipsis2 0.6s infinite;
}
.lds-ellipsis div:nth-child(3) {
  left: 26px;
  animation: lds-ellipsis2 0.6s infinite;
}
.lds-ellipsis div:nth-child(4) {
  left: 45px;
  animation: lds-ellipsis3 0.6s infinite;
}
@keyframes lds-ellipsis1 {
  0% {
    transform: scale(0);
  }
  100% {
    transform: scale(1);
  }
}
@keyframes lds-ellipsis3 {
  0% {
    transform: scale(1);
  }
  100% {
    transform: scale(0);
  }
}
@keyframes lds-ellipsis2 {
  0% {
    transform: translate(0, 0);
  }
  100% {
    transform: translate(19px, 0);
  }
}
/* End of Loading */
@media (min-width: 576px) {
  header .title {
    font-size: 2.75rem;
    margin: 55px auto;
    margin-bottom: 0;
  }
}
/* Medium devices (tablets, 768px and up) */
@media (min-width: 768px) { 
  .heading {font-size: 1.2rem;}
  section {
    width: 800px;
    margin: 30px auto;
  }
  header .title {
    font-size: 4rem;
    margin: 70px auto;
    margin-bottom: 0px;
  }
  header .subtitle {
    font-size: 1.2rem;
  }
  footer {font-size: 1.1rem;}
  .content .location, 
  .content .time,
  .content .day {
    font-weight: 500;
    font-size: 2.2rem;
    margin: 20px auto;
  }
}
/* Large devices (desktops, 992px and up) */
@media (min-width: 992px) { 
  header .title {    
    font-size: 4.7rem;
    margin: 100px auto;
    margin-bottom: 0px;
  }
  .content item {margin-bottom: 20px;}
  .content .location, 
  .content .time,
  .content .day {
    font-weight: 500;
    font-size: 2.75rem;
    margin: 25px auto;
  }
  .content .summary,
  .content .humidity {font-size: 1.2rem;}
  .content .temp {font-size: 4rem;}
  .content .icon canvas {
    width: 200px;
    height: 100px;
  }
}
$(document).ready(() => {
  let $currentContent = $(".currently .content");
  let $location = $currentContent.find(".location");
  let $currentTemp = $currentContent.find(".temp");
  let $currentSummary = $currentContent.find(".summary");
  let $currentHumidity = $currentContent.find(".humidity .detail");

  let $next48hContent = $(".next-48h .content");
  let $time = $next48hContent.find(".time");
  let $next48hTemp = $next48hContent.find(".temp");
  let $next48hSummary = $next48hContent.find(".summary");
  let $next48hHumidity = $next48hContent.find(".humidity .detail");
  let $next48hSelect = $(".next-48h .heading .select-style select");
  let dataNext48h, indexNext48h = 1, next48hSelectInit = false;

  let $next7dContent = $(".next-7d .content");
  let $day = $next7dContent.find(".day");
  let $next7dTemp = $next7dContent.find(".temp");
  let $next7dSummary = $next7dContent.find(".summary");
  let $next7dHumidity = $next7dContent.find(".humidity .detail");
  let $next7dSelect = $(".next-7d .heading .select-style select");
  let dataNext7d, indexNext7d = 1, next7dSelectInit = false;
  
  let skycons = new Skycons({"color": "#3da4ab"});
  let F2C = F => ((F - 32) * 5 / 9).toFixed();
  let C2F = C => (C * 9 / 5 + 32).toFixed();
  let currentUnit = "F";

  $(".icon").on("click", event => {
    let $target = $(event.target);
    $target.closest(".heading").find(".menu").toggleClass("responsive");
  });

  $(".menu .item").on("click", event => {
    let $target = $(event.target);
    let unit = $target.attr("unit");
    $target.closest(".menu").children().removeClass("active");
    $target.addClass("active");
    
    if (unit !== currentUnit) {
      currentUnit = unit;
      switchTemperatureUnit();
    }
  });

  // Automatically enable cross-domain requests when needed
  $.ajaxPrefilter(options => {
    if (options.crossDomain && $.support.cors) {
      options.url = `https://cors-anywhere.herokuapp.com/${options.url}`;
    }
  });

  $.getJSON("https://ipinfo.io/", onLocationGot);

  function onLocationGot(info) {
    $location.text(`${info.city}, ${info.region}`);
    updateWeatherInfo.apply(null, info.loc.split(","));
  }

  function updateWeatherInfo(lat, lon) {
    let secretKey = "9b0af02f1ad5f739d0fd7a754e459238";
    let requestURL = `https://api.darksky.net/forecast/${secretKey}/${lat},${lon}`;

    $.getJSON(requestURL, info => {
      highlightView();
      updateWeatherCurrently(info.currently, $currentTemp, $currentSummary, $currentHumidity, "current-icon");
      updateWeatherNext48h(info.hourly.data, indexNext48h);
      updateWeatherNext7d(info.daily.data, indexNext7d);
    });
  }

  function highlightView() {
    $(".loading").css("display", "none");
    $(".main-app").css("opacity", 1);
  }

  function updateWeatherCurrently(infoCurrently, $temp, $summary, $humidity, idIcon) {
    let curTemp = infoCurrently.temperature.toFixed();
    $temp.text(`${curTemp}°${currentUnit}`); 
    $temp.attr("value", curTemp);

    $summary.text(infoCurrently.summary);
    $humidity.text(`${(infoCurrently.humidity * 100).toFixed()}%`);
    setWeatherIcon(idIcon, infoCurrently.icon);
  }

  function updateWeatherNext48h(infoHourlyData, index) {
    dataNext48h = infoHourlyData;
    let dataChoosen = dataNext48h[index];
    let date = new Date(dataChoosen.time * 1000);
    let formatDate = date.toLocaleString("en-US", {weekday: 'long', hour: '2-digit', minute:'2-digit'});
    $time.text(formatDate);

    updateWeatherCurrently(dataChoosen, $next48hTemp, $next48hSummary, $next48hHumidity, "next48h-icon");

    if(next48hSelectInit == false) {
      let dataSelect = [];
      for(let i = 1; i < dataNext48h.length; i += 6) {
        date = new Date(dataNext48h[i].time * 1000);
        formatDate = date.toLocaleString("en-US", {weekday: 'long', hour: '2-digit', minute:'2-digit'});
        dataSelect.push(formatDate);
      }
      initSelect($next48hSelect, dataSelect, updateWeatherNext48h, 6, dataNext48h);
      next48hSelectInit = true;
    }
  }

  function updateWeatherNext7d(infoDailyData, index) {
    dataNext7d = infoDailyData;
    let dataChoosen = dataNext7d[index];
    let date = new Date(dataChoosen.time * 1000);
    let formatDate = date.toLocaleString("en-US", {weekday: 'long'});
    $day.text(formatDate);

    let minTemp = dataChoosen.temperatureMin.toFixed();
    let maxTemp = dataChoosen.temperatureMax.toFixed();
    $next7dTemp.text(`${minTemp}°${currentUnit} / ${maxTemp}°${currentUnit}`);
    $next7dTemp.attr("min", minTemp);
    $next7dTemp.attr("max", maxTemp);

    $next7dSummary.text(dataChoosen.summary);
    $next7dHumidity.text(`${dataChoosen.humidity * 100}%`);
    setWeatherIcon("next7d-icon", dataChoosen.icon);

    if(next7dSelectInit == false) {
      let dataSelect = [];
      for(let i = 1; i < dataNext7d.length; i++) {
        date = new Date(dataNext7d[i].time * 1000);
        formatDate = date.toLocaleString("en-US", {weekday: 'long'});
        dataSelect.push(formatDate);
      }
      initSelect($next7dSelect, dataSelect, updateWeatherNext7d, 1, dataNext7d);
      next7dSelectInit = true;
    }
  }

  function initSelect($select, dataSelect, updateFunc, step, data) {
    dataSelect.forEach(element => {
      $select.append(`<option>${element}</option>`);
    });
    $select.on("change", (event) => {
      let $target = $(event.target);
      let index = $target[0].selectedIndex * step + 1;
      updateFunc(data, index);
    });
  }

  function setWeatherIcon(id, type) {
    if (type === "clear-day") {
      skycons.set(id, Skycons.CLEAR_DAY);
    } else if (type === "clear-night") {
      skycons.set(id, Skycons.CLEAR_NIGHT);
    } else if (type === "rain") {
      skycons.set(id, Skycons.RAIN);
    } else if (type === "snow") {
      skycons.set(id, Skycons.SNOW);
    } else if (type === "sleet") {
      skycons.set(id, Skycons.SLEET);
    } else if (type === "wind") {
      skycons.set(id, Skycons.WIND);
    } else if (type === "fog") {
      skycons.set(id, Skycons.FOG);
    } else if (type === "cloudy") {
      skycons.set(id, Skycons.CLOUDY);
    } else if (type === "partly-cloudy-day") {
      skycons.set(id, Skycons.PARTLY_CLOUDY_DAY);
    } else if (type === "partly-cloudy-night") {
      skycons.set(id, Skycons.PARTLY_CLOUDY_NIGHT);
    } else {
      console.log("Other Weather Icon");
    }
    skycons.play();
  }

  function switchTemperatureUnit() {
    switchTemperatureUnitByItem($currentTemp);
    switchTemperatureUnitByItem($next48hTemp);

    // next 7 days
    let minTemp = $next7dTemp.attr("min"), maxTemp = $next7dTemp.attr("max");
    let newMin, newMax;

    if(currentUnit === "F") {
      newMin = C2F(minTemp);
      newMax = C2F(maxTemp);
    }
    else {
      newMin = F2C(minTemp);
      newMax = F2C(maxTemp); 
    }
    $next7dTemp.text(`${newMin}°${currentUnit} / ${newMax}°${currentUnit}`);
    $next7dTemp.attr("min", newMin);
    $next7dTemp.attr("max", newMax);
  }

  function switchTemperatureUnitByItem($item) {
    let currentTemp = $item.attr("value"), newTemp;

    if(currentUnit === "F") newTemp = C2F(currentTemp);
    else newTemp = F2C(currentTemp);

    $item.text(`${newTemp}°${currentUnit}`);
    $item.attr("value", newTemp);
  }
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.