diff --git a/scripts/weather.js b/scripts/weather.js
--- a/scripts/weather.js
+++ b/scripts/weather.js
@@ -1,50 +1,186 @@
 /*
- * Weather plugin using Yahoo! Weather API.
+ * Weather plugin using Open Weather Map.
  *
- * The plugin requires 'jquery' to be available through RequireJS.
+ * The plugin requires 'jquery' and 'moment' to be available through
+ * RequireJS. For localization, 'moment' must include language data
+ * (this can be done by providing 'moment-with-langs.js' as 'moment').
  *
- * At least on webkit-0.12.4, geolocation needed by this plugin is not
+ * At least on webkit-0.12.5, geolocation needed by this plugin is not
  * functional, so location_shim.js is required to be loaded before.
  */
-define(['jquery'], function ($) {
-  var api_key = 'a54ebf597a8e77198d9a47c798d42c51';
-  var woeid;
+define(['jquery', 'moment'], function ($, moment) {
+  var APPID = '41fff6331bf67509b73740427d14c562';
 
+  var DETAILED_FORECAST = 4;
+
+  var SECOND = 1000; // ms
+  var MINUTE = 60 * SECOND;
+  var HOUR = 60 * MINUTE;
+
+  var NEWLINE = '&#13;';
+
+  var coords;
+  var weather;
+  var forecast;
+  var forecastWindow = null;
+
   function unescapeHTML(html) {
     return $('<span/>').html(html).text();
   }
 
-  function updateWeather() {
-    $.ajax('http://weather.yahooapis.com/forecastrss?' +
-      'w=' + woeid + '&u=c')
-    .success(function (weather) {
-      var temp = $('condition', weather).attr('temp');
-      var units = $('units', weather).attr('temperature');
-      units = " &deg;" + units;
-      var forecast = $('forecast', weather).map(function (k, el) {
-        el = $(el);
-        return el.attr('day') + ": " + el.attr('text') + ", " +
-          el.attr('low') + "&ndash;" + el.attr('high') +
-          units;
+  function weatherIcon(data, doc) {
+    // TODO: Why would there be multiple weather?
+    var weather = data.weather[0];
+    // TODO: Styles should not be explicitly specified here
+    // but are needed to make the image inline
+    return $('<img/>', doc || document)
+      .attr('src', 'http://openweathermap.org/img/w/' + weather.icon + '.png')
+      .attr('title', weather.description)
+      .addClass('weather-icon')
+      .css({
+        'height': '1.5em',
+        'display': 'inline',
+        'vertical-align': 'middle'
       });
-      forecast = Array.prototype.join.call(forecast, '&#13;');
-      $('.widget-weather').html(temp + units);
-      $('.widget-weather').attr('title', unescapeHTML(forecast));
+  }
+
+  function weatherUrl(method) {
+    return [
+      'http://api.openweathermap.org/data/2.5/', method,
+      '?APPID=', APPID,
+      '&lat=', coords.latitude,
+      '&lon=', coords.longitude,
+      '&units=', 'metric',
+      '&lang=', navigator.language,
+      ''
+    ].join('');
+  }
+
+  function updateWeather() {
+    $.ajax(weatherUrl('weather')).success(function (result) {
+      weather = result;
+      render();
     });
   }
 
+  function updateForecast() {
+    $.ajax(weatherUrl('forecast')).success(function (result) {
+      forecast = result;
+      render();
+    });
+  }
+
+  function formatTemperature(temp, temp2) {
+    var result = Math.round(temp);
+    if (temp2 !== undefined) {
+      result += '..' + Math.round(temp2);
+    }
+    result += " &deg;C";
+    return result;
+  }
+
+  function getWidget() {
+    return $('.widget-weather');
+  }
+
+  function showForecast() {
+    if (forecastWindow === null || forecastWindow.closed) {
+      var widget = getWidget();
+      var offset = widget.offset();
+      forecastWindow = window.open('about:blank', 'forecast',
+        [
+          'left=' + offset.left,
+          'top=' + offset.top,
+          ''
+        ].join(';'));
+    } else {
+      forecastWindow.close();
+    }
+
+    render();
+  }
+
+  function render() {
+    var text = [];
+    var tooltip = [];
+    if (weather) {
+      text.push(weatherIcon(weather));
+      text.push(formatTemperature(weather.main.temp));
+
+      tooltip.push(weather.name);
+    }
+    if (forecastWindow !== null && !forecastWindow.closed) {
+      var forecastBody = $(forecastWindow.document.body);
+      forecastBody.empty();
+      var forecastHtml = [];
+      if (forecast) {
+        // Split forecast by days
+        var days = [];
+        var daily = [];
+        var prevDate = null;
+        for (var i in forecast.list) {
+          var fc = forecast.list[i];
+          var fcTime = new Date(fc.dt * SECOND);
+          var fcDate = new Date(fcTime);
+          fcDate.setHours(0, 0, 0, 0);
+          if (prevDate && prevDate.getTime() != fcDate.getTime()) {
+            // A new day has started, dump all daily forecast
+            days.push(daily);
+            daily = [];
+          } else {
+            daily.push(fc);
+          }
+          prevDate = fcDate;
+        }
+        if (daily.length > 0) {
+          days.push(daily);
+        }
+
+        for (var d in days) {
+          // Parse forecast: minimum and maximum for each day
+          var day = days[d];
+          var dailyEl = $('<p/>', forecastBody)
+            .addClass('forecast-day');
+          dailyEl.append(
+            moment(new Date(day[0].dt * SECOND)).format('DD MMM'));
+          var minTemp = null, maxTemp = null;
+          var dfc;
+          for (var j in day) {
+            dfc = day[j];
+            if (minTemp === null || minTemp > dfc.main.temp) {
+              minTemp = dfc.main.temp;
+            }
+            if (maxTemp === null || maxTemp < dfc.main.temp) {
+              maxTemp = dfc.main.temp;
+            }
+          }
+          dailyEl.append(' ' + formatTemperature(minTemp, maxTemp));
+          dailyEl.append($('<br/>', forecastBody));
+          for (j in day) {
+            dfc = day[j];
+            dailyEl.append(weatherIcon(dfc));
+          }
+          forecastBody.append(dailyEl);
+        }
+      }
+    }
+
+    var widget = getWidget();
+    widget.empty();
+    text.forEach(function (textEl) {
+      widget.append(textEl);
+    });
+    widget.attr('title', unescapeHTML(tooltip.join('')));
+  }
+
   $(document).ready(function () {
     navigator.geolocation.getCurrentPosition(function (pos) {
-      $.getJSON('http://query.yahooapis.com/v1/public/yql?q=' +
-        'select%20place.woeid%20from%20flickr.places%20where%20' +
-        'lat%3D' + pos.coords.latitude + '%20and%20' +
-        'lon%3D' + pos.coords.longitude + '%20and%20' +
-        'api_key=%22' + api_key + '%22&format=json')
-      .success(function (yloc) {
-        woeid = yloc.query.results.places.place.woeid;
-        updateWeather();
-        setInterval(updateWeather, 5 * 60 * 1000);
-      });
+      coords = pos.coords;
+      updateWeather();
+      updateForecast();
+      setInterval(updateWeather, 10 * MINUTE);
+      setInterval(updateForecast, 3 * HOUR);
     });
+    $('.widget-weather').click(showForecast);
   });
 });
diff --git a/tianbar.cabal b/tianbar.cabal
--- a/tianbar.cabal
+++ b/tianbar.cabal
@@ -1,5 +1,5 @@
 name:                tianbar
-version:             0.2.2.1
+version:             0.3.0.0
 synopsis:            A desktop bar based on WebKit
 description:
   A desktop bar using WebKit for rendering as much as possible.
