<div id="app"></div>
@import url('https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300');
$txtGrey: #CDD5C1;
$dawn-1: #123352;
$dawn-2: #1d5372;
$dawn-3: #3885a2;
$morning-1: #8dcdba;
$morning-2: #eee07a;
$morning-3: #f0eebc;
$afternoon-1: #e6756f;
$afternoon-2: #ee8049;
$afternoon-3: #facf62;
$evening-1: #291c6b;
$evening-2: #4a257d;
$evening-3: #884186;
$night-1: #111642;
$night-2: #011548;

*, *:before, *:after {
  box-sizing: inherit;
}
html {
  font-family: 'Open Sans Condensed', sans-serif;
  color: $txtGrey;
}
body {
  margin: 0;
}

h2 {
  text-transform: uppercase;
  font-weight: lighter;
  font-size: 40px;
  margin: 0;
}

p {
  margin: 0;
}

.panels {
  min-height: 100vh;
  overflow: hidden;
  display: flex;
  font-size: 20px;
}

.panel {
  flex: 1;
  text-align: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background: linear-gradient($dawn-1, $dawn-2, $dawn-3);
  border-right: 1px solid rgba($txtGrey, .3);
  cursor: pointer;
  transition:
        font-size 0.7s cubic-bezier(0.61,-0.19, 0.7,-0.11),
        flex 0.7s cubic-bezier(0.61,-0.19, 0.7,-0.11),
        background 0.3s;
  &.open {
    flex: 4;
    font-size: 30px;
  }
}

.panel > * {
  flex: 1 0 auto;
  margin: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  transition: transform 0.5s;
}
.panel > *:first-child {
  justify-content: flex-end;
}
.panel > *:last-child {
  justify-content: flex-start;
}

.dawn {
  background: linear-gradient($dawn-1, $dawn-2, $dawn-3);
}
.morning {
  background: linear-gradient($morning-1, $morning-2, $morning-3);
  color: #363c80;
}
.afternoon-1 {
  background: linear-gradient($morning-3, $afternoon-1, $afternoon-2);
  color: #fff;
}
.afternoon-2 {
  background: linear-gradient($afternoon-2, $afternoon-3, $afternoon-1);
  color: #fff;
}
.evening-1 {
  background: linear-gradient($afternoon-3, $afternoon-1, $evening-3);
  color: #fff;
}
.evening-2 {
  background: linear-gradient($evening-1, $evening-2, $evening-3);
  color: #9eaf81;
}
.night-1 {
  background: linear-gradient($evening-3, $night-1, $night-2);
}
.night-2 {
  background: linear-gradient($night-1, $dawn-2, $dawn-1);
}

.weather-icon i {
  font-family: 'weathericons';
  font-style: normal;
  font-weight: normal;
  line-height: 1;
  font-size: 40px;
  padding: 20px 0;
  &.icon-01d:before {
    content: '\f00d';
  }
  &.icon-01n:before {
    content: '\f02e';
  }
  &.icon-02d:before {
    content: '\f002';
  }
  &.icon-02n:before {
    content: '\f086';
  }
  &.icon-03d:before,
  &.icon-03n:before {
    content: '\f041';
  }
  &.icon-04d:before,
  &.icon-04n:before {
    content: '\f013';
  }
  &.icon-09d:before,
  &.icon-09n:before {
    content: '\f017';
  }
  &.icon-10d:before {
    content: '\f009';
  }
  &.icon-10n:before {
    content: '\f029';
  }
  &.icon-11d:before,
  &.icon-11n:before {
    content: '\f01d';
  }
  &.icon-13d:before,
  &.icon-13n:before {
    content: '\f01d';
  }
  &.icon-50d:before,
  &.icon-50n:before {
    content: '\f01b';
  }
}
View Compiled
class WeatherApp extends React.Component {
  constructor() {
    super();
    this.state = {
      currentTime: moment(),
      cities: {
        'San Mateo': {
          weatherId: 5391959,
          timeZone: 'America/Los_Angeles'
        },
        'Toronto' : {
          weatherId: 6167865,
          timeZone: 'America/Toronto'
        },
        'Paris': {
          weatherId: 2988507,
          timeZone: 'Europe/Paris'
        },
        'Sydney': {
          weatherId: 2147714,
          timeZone: 'Australia/Sydney'
        }
      }
    }
  }
  componentDidMount() {
    window.setInterval(() => this.setState({ currentTime: moment() }), 5000)
  }
  render() {
    const { cities, currentTime } = this.state;
    return (
      <div className="panels">
        {
          Object
            .keys(cities)
            .map(cityName =>
                 <City name={cityName}
                       weatherId = {cities[cityName].weatherId}
                       timeZone = {cities[cityName].timeZone}
                       bgImg = {cities[cityName].bgImg}
                       currentTime = {currentTime}
                       key={cityName}
                 />)
        }
      </div>
    )
  }
}

class City extends React.Component {
  constructor(props) {
    super(props);
    const { timeZone, currentTime } = this.props;
    this.state = {
            weatherData: {},
            localTime: currentTime.tz(timeZone).format('HH:mm dddd'),
            currentHour: currentTime.tz(timeZone).format('HH'),
            open: false,
            bgGradient: ''
    }
    this.getWeatherInfo = this.getWeatherInfo.bind(this);
    this.updateCurrentTime = this.updateCurrentTime.bind(this);
    this.toggleOpen = this.toggleOpen.bind(this);
  };
  async getWeatherInfo(id) {
    const res = await fetch(`https://api.openweathermap.org/data/2.5/weather?id=${id}&units=metric&appid=c5baa00af2bfbc51b5a8bff68a069bb0`).then(res => res.json());
    const weatherInfo = {
      temp: res.main.temp,
      desc: res.weather[0].main,
      icon: `icon-${res.weather[0].icon}`
    };
    this.setState({
      weatherData: weatherInfo
    })
  }
  setGradient(currentHour) {
      if (currentHour < 3) {
        this.setState({ bgGradient : 'night-2'});
      } else if (currentHour < 6) {
        this.setState({ bgGradient : 'dawn'});
      } else if (currentHour < 9) {
        this.setState({ bgGradient : 'morning'});
      } else if (currentHour < 12) {
        this.setState({ bgGradient : 'afternoon-1'});
      } else if (currentHour < 15) {
        this.setState({ bgGradient : 'afternoon-2'});
      } else if (currentHour < 18) {
        this.setState({ bgGradient : 'evening-1'});
      } else if (currentHour < 21) {
        this.setState({ bgGradient : 'evening-2'});
      } else if (currentHour < 24) {
        this.setState({ bgGradient : 'night-1'});
      }
  }
  updateCurrentTime() {
    const { timeZone, currentTime } = this.props;
    this.setState({ localTime: currentTime.tz(timeZone).format('dddd HH:mm') });
    this.setGradient(this.state.currentHour);
  }
  componentDidMount() {
    const { weatherId } = this.props;
    this.getWeatherInfo(weatherId);
    window.setInterval(() => this.updateCurrentTime(), 5000);
    this.setGradient(this.state.currentHour);
  }
  toggleOpen() {
    const currentState = this.state.open;
    this.setState({ open: !currentState });
  }
  render() {
    const { name, bgImg } = this.props;
    const { localTime } = this.state;
    const { desc, temp, icon } = this.state.weatherData;
    const activeClass = this.state.open ? 'open': '';
    const gradientClass = this.state.bgGradient;
    return (
      <div className={`panel ${activeClass} ${gradientClass}`}
           onClick={this.toggleOpen}
        >
        <div>
          <h2>{ name }</h2>
          <p>{ localTime }</p>
        </div>
          <div className="weather-icon">
            <i className={icon}></i>
        {temp ?
            <span> { desc } { temp }°C </span>
            : ''
        }
          </div>
      </div>
    )
  }
}

ReactDOM.render(<WeatherApp />, document.querySelector('#app'));
View Compiled

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/weather-icons/2.0.9/css/weather-icons.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.0/moment.min.js
  4. https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.9/moment-timezone-with-data-2010-2020.min.js
  5. https://cdnjs.cloudflare.com/ajax/libs/babel-core/6.1.19/browser.min.js
  6. https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.29/browser-polyfill.min.js