block by nitaku 9314561

Clock

Full Screen

A clock!

index.js

(function() {
  var DAYS, LAT, LONG, MONTHS, altitude, get_day_night, get_season, last_day_night, last_seconds, last_sep, pad_time, update_05_secs, update_10_min;

  LAT = 43.62;

  LONG = 10.63;

  DAYS = ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'];

  MONTHS = ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'];

  /* add a zero in front of numbers < 10
  */

  pad_time = function(i) {
    if (i < 10) {
      return "0" + i;
    } else {
      return i;
    }
  };

  /* add methods to Date to check if DST is in effect
  */

  /* from http://stackoverflow.com/questions/11887934/check-if-daylight-saving-time-is-in-effect-and-if-it-is-for-how-many-hours
  */

  Date.prototype.stdTimezoneOffset = function() {
    var jan, jul;
    jan = new Date(this.getFullYear(), 0, 1);
    jul = new Date(this.getFullYear(), 6, 1);
    return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
  };

  Date.prototype.dst = function() {
    return this.getTimezoneOffset() < this.stdTimezoneOffset();
  };

  /* SEASONS
  */

  get_season = function(now) {
    var seasons;
    seasons = Date.getSeasons(now.getFullYear());
    if (now < seasons[1]) {
      return 'winter';
    } else if (now < seasons[2]) {
      return 'spring';
    } else if (now < seasons[3]) {
      return 'summer';
    } else if (now < seasons[4]) {
      return 'fall';
    } else {
      return 'winter';
    }
  };

  /* define a scale for SUN and MOON altitude
  */

  altitude = d3.scale.linear().domain([-1, 1]).range([600, 0]);

  /* DAY/NIGHT
  */

  get_day_night = function(now) {
    var times;
    times = SunCalc.getTimes(now, LAT, LONG);
    if (now < times.sunriseEnd) {
      return 'night';
    } else if (now < times.dusk) {
      return 'day';
    } else {
      return 'night';
    }
  };

  /* update every 0.5 seconds
  */

  last_seconds = -1;

  last_sep = false;

  last_day_night = null;

  update_05_secs = function() {
    /* CLOCK
    */
    var clock_string, day_night, h, m, moon_alt, moon_phase, now, s, sun_alt;
    now = new Date();
    h = now.getHours();
    m = now.getMinutes();
    s = now.getSeconds();
    h = pad_time(h);
    m = pad_time(m);
    s = pad_time(s);
    if (s !== last_seconds) {
      last_sep = !last_sep;
      last_seconds = s;
    }
    clock_string = h;
    if (last_sep) {
      clock_string += ':';
    } else {
      clock_string += ' ';
    }
    clock_string += m;
    d3.select('#clock').text(clock_string);
    /* DAYLIGHT SAVINGS TIME
    */
    d3.select('#dst').text(now.dst() ? "LEG" : "SOL");
    /* CALENDAR
    */
    d3.select('#calendar').text("" + DAYS[now.getDay()] + " " + (now.getDate()) + " " + MONTHS[now.getMonth()] + " " + (now.getFullYear()));
    /* SEASONS
    */
    d3.select('#season').text((function() {
      switch (get_season(now)) {
        case 'winter':
          return 'inverno';
        case 'spring':
          return 'primavera';
        case 'summer':
          return 'estate';
        case 'fall':
          return 'autunno';
      }
    })());
    /* NETWORK status
    */
    d3.select('#router').classed('ok', navigator.onLine);
    d3.select('#wifirouter').classed('ok', true);
    /* SUN and MOON simulation
    */
    sun_alt = Math.sin(SunCalc.getPosition(now, LAT, LONG).altitude);
    moon_alt = Math.sin(SunCalc.getMoonPosition(now, LAT, LONG).altitude);
    moon_phase = SunCalc.getMoonIllumination(now);
    d3.select('#sun').attr('y', altitude(sun_alt)).attr('fill', '#FFF268');
    d3.select('#moon').attr('y', altitude(moon_alt)).attr('fill', '#BDDBFD');
    d3.select('#moon_symbol > path').attr('d', moon_phase.angle > 0 ? "M0 30 A30 30 0 1 1 0 -30 A" + (60 * Math.abs(moon_phase.fraction - 0.5)) + " 30 0 1 " + (moon_phase.fraction > 0.5 ? 1 : 0) + " 0 30" : "M0 30 A" + (60 * Math.abs(moon_phase.fraction - 0.5)) + " 30 0 1 " + (moon_phase.fraction > 0.5 ? 1 : 0) + " 0 -30 A30 30 0 1 1 0 30");
    /* NIGHT and DAY
    */
    day_night = get_day_night(now);
    if (day_night !== last_day_night) {
      if (day_night === 'day') {
        d3.select('body').transition().duration(3000).style('background', '#00BCD1');
        d3.select('#land').transition().duration(3000).attr('fill', '#ABEC72');
      } else if (day_night === 'night') {
        d3.select('body').transition().duration(3000).style('background', '#434367');
        d3.select('#land').transition().duration(3000).attr('fill', '#0A8690');
      }
      last_day_night = day_night;
    }
    return setTimeout((function() {
      return update_05_secs();
    }), 500);
  };

  /* update every 10 minutes
  */

  update_10_min = function() {
    /* WHEATER from OpenWeatherMap
    */    d3.json('http://api.openweathermap.org/data/2.5/weather?q=Ponsacco,it&APPID=eaecf85a68297fd2c57d209840cb81dc&lang=it', function(data) {
      var status;
      if (!(data != null)) return;
      d3.select('#temperature').text("" + (Math.round(data.main.temp - 273.15)) + "°");
      d3.select('#humidity').text("" + (Math.round(data.main.humidity)) + "%");
      if ((data.weather != null) && (data.weather[0] != null)) {
        status = +data.weather[0].icon.slice(0, 2);
        /* set haze visibility
        */
        if (status < 4) {
          d3.select('#haze').transition().duration(3000).attr('fill-opacity', 0);
        } else {
          d3.select('#haze').transition().duration(3000).attr('fill-opacity', 0.5);
        }
        /* set clouds visibility
        */
        if (status < 2 || status === 50) {
          d3.select('#clouds').transition().duration(3000).attr('opacity', 0);
        } else {
          d3.select('#clouds').transition().duration(3000).attr('opacity', 1);
        }
        /* set more clouds visibility
        */
        if (status < 3 || status === 50) {
          return d3.select('#more_clouds').transition().duration(3000).attr('opacity', 0);
        } else {
          return d3.select('#more_clouds').transition().duration(3000).attr('opacity', 1);
        }
      }
    });
    return setTimeout((function() {
      return update_10_min();
    }), 600000);
  };

  update_05_secs();

  update_10_min();

}).call(this);

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
        <title>Clock</title>
        <link type="text/css" href="index.css" rel="stylesheet"/>
        <script src="//d3js.org/d3.v3.min.js"></script>
        <script src="suncalc.js"></script>
        <script src="seasons.js"></script>
    </head>
    <body>
        <svg width="1024" height="748">
            <defs>
                <symbol id="sun_symbol" viewBox="0 0 100 100">
                    <circle cx="50" cy="50" r="33.715"/>
                    <g>
                        <polygon points="50,5 47.275,13.334 52.725,13.334  "/>
                        <polygon points="50,95 47.275,86.667 52.725,86.667  "/>
                    </g>         
                    <g>          
                        <polygon points="95,50 86.666,47.276 86.666,52.725  "/>
                        <polygon points="5,50 13.333,47.276 13.333,52.725  "/>
                    </g>         
                    <g>          
                        <polygon points="18.18,18.18 22.146,26 26,22.146  "/>
                        <polygon points="81.82,81.82 74.002,77.854 77.854,74  "/>
                    </g>         
                    <g>          
                        <polygon points="81.82,18.18 74.002,22.146 77.854,25.999  "/>
                        <polygon points="18.18,81.82 22.146,74 26,77.854  "/>
                    </g>         
                    <g>          
                        <polygon points="32.961,8.351 33.595,17.095 38.638,15.032  "/>
                        <polygon points="67.039,91.649 61.361,84.968 66.405,82.904  "/>
                    </g>         
                    <g>          
                        <polygon points="91.649,32.961 82.905,33.595 84.969,38.638  "/>
                        <polygon points="8.351,67.039 15.032,61.362 17.096,66.405  "/>
                    </g>         
                    <g>          
                        <polygon points="8.501,32.598 15.133,38.333 17.239,33.308  "/>
                        <polygon points="91.499,67.402 82.76,66.691 84.867,61.668  "/>
                    </g>         
                    <g>          
                        <polygon points="67.402,8.501 61.668,15.133 66.691,17.24  "/>
                        <polygon points="32.598,91.499 33.308,82.761 38.333,84.868  "/>
                    </g>
                </symbol>
                <symbol id="moon_symbol" viewBox="-34 -34 68 68">
                    <circle r="30" style="fill: black; fill-opacity: 0.1;"/>
                    <path d="M0 30 A30 30 0 1 1 0 -30 A30 30 0 1 1 0 30"/>
                </symbol>
                <symbol id="cloud_symbol" viewBox="0 0 100 100">
                    <path d="M642.02-364.11c-0.772,0-1.519,0.114-2.229,0.313c-0.17-6.744-5.679-12.162-12.464-12.162   c-4.368,0-8.201,2.251-10.428,5.65c-1.38-2.61-4.117-4.393-7.275-4.393c-2.621,0-4.95,1.229-6.456,3.139   c-2.865-6.496-9.357-11.031-16.913-11.031c-8.99,0-16.479,6.42-18.138,14.926c-0.937-0.248-1.911-0.393-2.925-0.393   c-6.312,0-11.427,5.116-11.427,11.427c0,0.907,0.117,1.787,0.317,2.634h95.921c0.146-0.609,0.229-1.243,0.229-1.896   C650.233-360.433,646.556-364.11,642.02-364.11z"/>
                    <path d="M90.02,48.188c-0.771,0-1.518,0.113-2.229,0.312c-0.17-6.744-5.678-12.162-12.463-12.162   c-4.369,0-8.201,2.251-10.428,5.65c-1.381-2.61-4.117-4.393-7.275-4.393c-2.621,0-4.951,1.229-6.457,3.139   c-2.865-6.496-9.357-11.031-16.913-11.031c-8.99,0-16.479,6.42-18.138,14.926c-0.937-0.248-1.911-0.393-2.925-0.393   c-6.312,0-11.427,5.116-11.427,11.427c0,0.907,0.117,1.786,0.317,2.634h95.92c0.146-0.609,0.23-1.243,0.23-1.896   C98.234,51.863,94.557,48.188,90.02,48.188z"/>
                </symbol>
            </defs>
            <use id="sun" xlink:href="#sun_symbol" x="750" width="140" height="140"/>
            <use id="moon" xlink:href="#moon_symbol" x="148" width="100" height="100"/>
            <rect id="land" x="0" y="300" width="1024" height="448" fill="white"/>
            <rect id="haze" x="0" y="0" width="1024" height="748" fill="#888" fill-opacity="0"/>
            <g id="clouds" fill="white" fill-opacity="0.3" opacity="0">
                <use xlink:href="#cloud_symbol" x="800" y="0" width="100" height="100"/>
                <use xlink:href="#cloud_symbol" x="40" y="50" width="200" height="200"/>
                <use xlink:href="#cloud_symbol" x="360" y="180" width="140" height="140"/>
            </g>
            <g id="more_clouds" fill="white" fill-opacity="0.3" opacity="0">
                <use xlink:href="#cloud_symbol" x="240" y="-10" width="80" height="80"/>
                <use xlink:href="#cloud_symbol" x="10" y="84" width="160" height="160"/>
                <use xlink:href="#cloud_symbol" x="110" y="180" width="90" height="90"/>
                <use xlink:href="#cloud_symbol" x="710" y="60" width="280" height="280"/>
                <use xlink:href="#cloud_symbol" x="894" y="118" width="120" height="120"/>
                <use xlink:href="#cloud_symbol" x="590" y="-184" width="500" height="500"/>
            </g>
            
            <g transform="translate(0,25)">
                <line stroke="black" stroke-width="1" x1="0" x2="10" y1="0" y2="0"/>
                <circle r="3" cx="10"/>
            </g>
            
            <a xlink:href="//192.168.0.1" xlink:show="new" target="_blank">
                <g id="router" class="status" transform="translate(35,25)">
                    <path class="icon" transform="scale(0.5) translate(-50,-50)" d="M49.249,17.499c-17.782,0-32.25,14.469-32.25,32.25c0,17.784,14.468,32.252,32.25,32.252   c17.78,0,32.251-14.469,32.251-32.252C81.5,31.968,67.029,17.499,49.249,17.499z M74.211,62.84c-0.006,0-0.012-0.006-0.018-0.006   h-6.654c0.986-3.357,1.666-7.1,1.852-11.121h7.979C77.086,55.717,75.992,59.461,74.211,62.84z M51.381,77.855V66.762h10.584   c-2.064,4.939-4.484,8.549-5.855,10.371C54.58,77.52,53.002,77.732,51.381,77.855z M42.543,77.182   c-1.364-1.799-3.809-5.432-5.896-10.42h10.808v11.127C45.774,77.781,44.131,77.57,42.543,77.182z M24.283,62.84   c-1.778-3.379-2.878-7.123-3.161-11.127h8.094c0.186,4.021,0.866,7.764,1.852,11.121h-6.763   C24.296,62.834,24.291,62.84,24.283,62.84z M24.469,36.313c0.019,0.003,0.032,0.012,0.048,0.012h6.681   c-1.24,3.988-1.821,7.815-1.971,11.462h-8.105C21.413,43.644,22.582,39.78,24.469,36.313z M47.456,21.606v10.792H36.892   c1.489-3.33,3.466-6.745,6.065-10.211C44.413,21.853,45.924,21.705,47.456,21.606z M55.686,22.231   c2.584,3.453,4.549,6.851,6.031,10.167H51.381V21.643C52.848,21.751,54.293,21.905,55.686,22.231z M63.246,36.325   c1.357,3.996,2.006,7.828,2.174,11.462H51.381V36.325H63.246z M47.456,36.325v11.462H33.187c0.17-3.634,0.819-7.466,2.18-11.462   H47.456z M33.182,51.713h14.274v11.121H35.202C34.143,59.516,33.381,55.783,33.182,51.713z M51.381,62.834V51.713h14.045   c-0.203,4.07-0.959,7.803-2.02,11.121H51.381z M69.379,47.787c-0.146-3.647-0.729-7.474-1.971-11.462h6.568   c0.014,0,0.031-0.009,0.049-0.012c1.883,3.467,3.055,7.331,3.344,11.474H69.379z M71.465,32.398h-5.52   c-1.045-2.558-2.316-5.154-3.941-7.796C65.711,26.494,68.92,29.147,71.465,32.398z M36.675,24.501   c-1.651,2.678-2.955,5.307-4.012,7.897h-5.635C29.616,29.093,32.892,26.396,36.675,24.501z M26.749,66.762h5.641   c1.202,3.137,2.576,5.816,3.854,8.002C32.514,72.814,29.276,70.098,26.749,66.762z M62.414,74.668   c1.264-2.168,2.619-4.813,3.801-7.906h5.525C69.254,70.049,66.078,72.725,62.414,74.668z"/>
                    <circle r="20" fill="transparent"/>
                </g>
            </a>
            
            <g transform="translate(0,730)">
                <line stroke="black" stroke-width="1" x1="0" x2="10" y1="0" y2="0"/>
                <circle r="3" cx="10"/>
            </g>
            
            <a xlink:href="//192.168.0.4" xlink:show="new" target="_blank">
                <g id="wifirouter" class="status" transform="translate(33,720)">
                    <g transform="scale(0.15) translate(-125,-100)">
                        <path class="icon" d="M137.085,39.439c-48.16,0-91.761,19.869-123.06,51.829l17.67,17.669c26.77-27.434,64.121-44.497,105.39-44.497  c41.268,0,78.618,17.063,105.39,44.497l17.67-17.669C228.846,59.307,185.245,39.439,137.085,39.439z"/>
                        <path class="icon" d="M137.085,79.624c-37.081,0-70.638,15.361-94.658,40.046l17.669,17.669c19.492-20.159,46.801-32.715,76.989-32.715  s57.497,12.556,76.989,32.715l17.668-17.669C207.723,94.985,174.166,79.624,137.085,79.624z"/>
                        <path class="icon" d="M137.085,121.341c-25.58,0-48.71,10.681-65.173,27.813l17.684,17.685c11.932-12.608,28.802-20.498,47.489-20.498  s35.558,7.89,47.488,20.497l17.685-17.684C185.795,132.021,162.664,121.341,137.085,121.341z"/>
                    <circle class="icon" cx="136.754" cy="198.076" r="29.448"/>
                    </g>
                </g>
            </a>
            
            <text id="calendar" class="tile" x="512" y="10" dy="1em"/>
            <text id="season" class="tile" x="512" y="60" dy="1em"/>
            <text id="clock" class="tile" x="512" y="160" dy=".35em"/>
            <text id="dst" class="tile" x="670" y="240" dy=".35em"/>

            <text id="temperature" class="tile" x="1014" y="10" dy="1em"/>
            <text id="humidity" class="tile" x="1004" y="90" dy="1em"/>
            
        </svg>
    </body>
    <script src="index.js"></script>
</html>

index.coffee

LAT = 43.62
LONG = 10.63

DAYS = ['Dom','Lun','Mar','Mer','Gio','Ven','Sab']
MONTHS = ['Gen','Feb','Mar','Apr','Mag','Giu','Lug','Ago','Set','Ott','Nov','Dic']

### add a zero in front of numbers < 10 ###
pad_time = (i) -> if i<10 then "0#{i}" else i

### add methods to Date to check if DST is in effect ###
### from http://stackoverflow.com/questions/11887934/check-if-daylight-saving-time-is-in-effect-and-if-it-is-for-how-many-hours ###
Date.prototype.stdTimezoneOffset = () ->
    jan = new Date(this.getFullYear(), 0, 1)
    jul = new Date(this.getFullYear(), 6, 1)
    return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset())

Date.prototype.dst = () -> this.getTimezoneOffset() < this.stdTimezoneOffset()

### SEASONS ###
get_season = (now) ->
    seasons = Date.getSeasons now.getFullYear()
    
    if now < seasons[1]
        return 'winter'
    else if now < seasons[2]
        return 'spring'
    else if now < seasons[3]
        return 'summer'
    else if now < seasons[4]
        return 'fall'
    else
        return 'winter'

### define a scale for SUN and MOON altitude ###
altitude = d3.scale.linear()
    .domain([-1,1])
    .range([600,0])
    
### DAY/NIGHT ###
get_day_night = (now) ->
    times = SunCalc.getTimes(now, LAT, LONG)
    if now < times.sunriseEnd
        return 'night'
    else if now < times.dusk
        return 'day'
    else
        return 'night'
        
### update every 0.5 seconds ###
last_seconds = -1
last_sep = false
last_day_night = null

update_05_secs = () ->
    ### CLOCK ###
    now = new Date()
    h = now.getHours()
    m = now.getMinutes()
    s = now.getSeconds()
    
    h = pad_time(h)
    m = pad_time(m)
    s = pad_time(s)
    
    if s != last_seconds
        last_sep = not last_sep
        last_seconds = s
        
    clock_string = h
    if last_sep
        clock_string += ':'
    else
        clock_string += ' '
    clock_string += m
    
    d3.select('#clock')
        .text(clock_string)
    
    ### DAYLIGHT SAVINGS TIME ###
    d3.select('#dst')
        .text(if now.dst() then "LEG" else "SOL")
        
    ### CALENDAR ###
    d3.select('#calendar')
        .text("#{DAYS[now.getDay()]} #{now.getDate()} #{MONTHS[now.getMonth()]} #{now.getFullYear()}")
    
    ### SEASONS ###
    d3.select('#season')
        .text switch get_season(now)
                  when 'winter' then 'inverno'
                  when 'spring' then 'primavera'
                  when 'summer' then 'estate'
                  when 'fall' then 'autunno'
    
    ### NETWORK status ###
    d3.select('#router')
        .classed('ok', navigator.onLine) # FIXME check by ping
        
    d3.select('#wifirouter')
        .classed('ok', true) # FIXME check by ping
        
    ### SUN and MOON simulation ###
    sun_alt = Math.sin SunCalc.getPosition(now, LAT, LONG).altitude
    moon_alt = Math.sin SunCalc.getMoonPosition(now, LAT, LONG).altitude
    moon_phase = SunCalc.getMoonIllumination(now)
    
    d3.select('#sun')
        .attr('y', altitude(sun_alt))
        .attr('fill','#FFF268')
        
    d3.select('#moon')
        .attr('y', altitude(moon_alt))
        .attr('fill','#BDDBFD')
        
    d3.select('#moon_symbol > path')
        .attr('d', if moon_phase.angle > 0 then "M0 30 A30 30 0 1 1 0 -30 A#{60*Math.abs(moon_phase.fraction-0.5)} 30 0 1 #{if moon_phase.fraction > 0.5 then 1 else 0} 0 30" else "M0 30 A#{60*Math.abs(moon_phase.fraction-0.5)} 30 0 1 #{if moon_phase.fraction > 0.5 then 1 else 0} 0 -30 A30 30 0 1 1 0 30")
        
    ### NIGHT and DAY ###
    day_night = get_day_night(now)
    
    if day_night != last_day_night
        if day_night is 'day'
            d3.select('body')
                .transition().duration(3000)
                .style('background', '#00BCD1')
            d3.select('#land')
                .transition().duration(3000)
                .attr('fill', '#ABEC72')
        else if day_night is 'night'
            d3.select('body')
                .transition().duration(3000)
                .style('background', '#434367')
            d3.select('#land')
                .transition().duration(3000)
                .attr('fill', '#0A8690')
        last_day_night = day_night
    
    setTimeout((()->update_05_secs()),500)

### update every 10 minutes ###
update_10_min = () ->
    ### WHEATER from OpenWeatherMap ###
    d3.json 'http://api.openweathermap.org/data/2.5/weather?q=Ponsacco,it&APPID=eaecf85a68297fd2c57d209840cb81dc&lang=it', (data) ->
        if not data?
            return
            
        d3.select('#temperature')
            .text("#{Math.round(data.main.temp - 273.15)}°") # temp is in Kelvin degrees
            
        d3.select('#humidity')
            .text("#{Math.round(data.main.humidity)}%")
            
        if data.weather? and data.weather[0]?
            # console.debug data.weather[0].icon
            status = +data.weather[0].icon[0...2]
            
            ### set haze visibility ###
            if status < 4
                d3.select('#haze')
                  .transition().duration(3000)
                    .attr('fill-opacity', 0)
            else
                d3.select('#haze')
                  .transition().duration(3000)
                    .attr('fill-opacity', 0.5)
                    
            ### set clouds visibility ###
            if status < 2 or status is 50
                d3.select('#clouds')
                  .transition().duration(3000)
                    .attr('opacity', 0)
            else
                d3.select('#clouds')
                  .transition().duration(3000)
                    .attr('opacity', 1)
                    
            ### set more clouds visibility ###
            if status < 3 or status is 50
                d3.select('#more_clouds')
                  .transition().duration(3000)
                    .attr('opacity', 0)
            else
                d3.select('#more_clouds')
                  .transition().duration(3000)
                    .attr('opacity', 1)
                    
            # if data.weather[0].icon[2] is 'd'
                # day()
            # else
                # night()
            # .attr('xlink:href', "http://openweathermap.org/img/w/#{data.weather[0].icon}.png")
        # else
            # d3.select('#weather_icon')
                # .attr('xlink:href', '')
                
    setTimeout((()->update_10_min()),600000) # OpenWeatherMap time limit is 10 minutes
    
update_05_secs()
update_10_min()

index.css

html, body {
    padding: 0;
    margin: 0;
    overflow: hidden;
    width: 1024px;
    height: 748px;
    background: gray;
}

@font-face {
  font-family: "DroidSansMono";
  src: url("DroidSansMono.ttf");
}

.tile {
    font-family: "DroidSansMono";
    fill: white;
}

#calendar {
    font-size: 40px;
    font-family: "Georgia";
    text-anchor: middle;
}
#season {
    text-transform: uppercase;
    font-weight: bold;
    text-anchor: middle;
}
#clock {
    font-size: 120px;
    text-anchor: middle;
}
#dst {
    font-size: 16px;
    font-weight: bold;
    text-anchor: end;
}

#temperature {
    font-size: 60px;
    text-anchor: end;
}
#humidity {
    font-size: 40px;
    text-anchor: end;
}

/* statuses */
.status .icon {
    fill: red;
}
.status.ok .icon {
    fill: white;
}

seasons.js

Date.fromJulian= function(j){
    j= (+j)+(30.0/(24*60*60));
    var A= Date.julianArray(j, true);
    return new Date(Date.UTC.apply(Date, A));
}
Date.julianArray= function(j, n){
    var F= Math.floor;
    var j2, JA, a, b, c, d, e, f, g, h, z;
    j+= .5;
    j2= (j-F(j))*86400.0;
    z= F(j);
    f= j-z;
    if(z< 2299161) a= z;
    else{
        g= F((z-1867216.25)/36524.25);
        a= z+1+g-F(g/4);
    }
    b= a+1524;
    c= F((b-122.1)/365.25);
    d= F(365.25*c);
    e= F((b-d)/30.6001);
    h= F((e< 14)? (e-1): (e-13));
    var JA= [F((h> 2)? (c-4716): (c-4715)),
    h-1, F(b-d-F(30.6001*e)+f)];
    var JB= [F(j2/3600), F((j2/60)%60), Math.round(j2%60)];
    JA= JA.concat(JB);
    if(typeof n== 'number') return JA.slice(0, n);
    return JA;
}
Date.getSeasons= function(y, wch){
    y= y || new Date().getFullYear();
    if(y<1000 || y> 3000) throw y+' is out of range';
    var Y1= (y-2000)/1000, Y2= Y1*Y1, Y3= Y2*Y1, Y4= Y3*Y1;
    var jd, t, w, d, est= 0, i= 0, Cos= Math.degCos, A= [y],
    e1= [485, 203, 199, 182, 156, 136, 77, 74, 70, 58, 52, 50, 45, 44, 29, 18, 17, 16, 14, 12, 12, 12, 9, 8],
    e2= [324.96, 337.23, 342.08, 27.85, 73.14, 171.52, 222.54, 296.72, 243.58, 119.81, 297.17, 21.02,
    247.54, 325.15, 60.93, 155.12, 288.79, 198.04, 199.76, 95.39, 287.11, 320.81, 227.73, 15.45],
    e3= [1934.136, 32964.467, 20.186, 445267.112, 45036.886, 22518.443,
    65928.934, 3034.906, 9037.513, 33718.147, 150.678, 2281.226,
    29929.562, 31555.956, 4443.417, 67555.328, 4562.452, 62894.029,
    31436.921, 14577.848, 31931.756, 34777.259, 1222.114, 16859.074];
    while(i< 4){
        switch(i){
            case 0: jd= 2451623.80984 + 365242.37404*Y1 + 0.05169*Y2 - 0.00411*Y3 - 0.00057*Y4;
            break;
            case 1: jd= 2451716.56767 + 365241.62603*Y1 + 0.00325*Y2+ 0.00888*Y3 - 0.00030*Y4;
            break;
            case 2: jd= 2451810.21715 + 365242.01767*Y1 - 0.11575*Y2 + 0.00337*Y3 + 0.00078*Y4;
            break;
            case 3: jd= 2451900.05952 + 365242.74049*Y1 - 0.06223*Y2 - 0.00823*Y3 + 0.00032*Y4;
            break;
        }
        var t= (jd- 2451545.0)/36525,
        w= 35999.373*t - 2.47,
        d= 1 + 0.0334*Cos(w)+ 0.0007*Cos(2*w);
        est= 0;
        for(var n= 0; n<24; n++){
            est += e1[n]*Cos(e2[n]+(e3[n]*t));
        }
        jd+= (0.00001*est)/d;
        A[++i]= Date.fromJulian(jd);
    }
    return wch && A[wch]? A[wch]: A;
}
Math.degRad= function(d){
    return (d*Math.PI)/180.0
}
Math.degSin= function(d){
    return Math.sin(Math.degRad(d))
}
Math.degCos= function(d){
    return Math.cos(Math.degRad(d))
}

suncalc.js

/*
 (c) 2011-2014, Vladimir Agafonkin
 SunCalc is a JavaScript library for calculating sun/mooon position and light phases.
 https://github.com/mourner/suncalc
*/

(function () { "use strict";

// shortcuts for easier to read formulas

var PI   = Math.PI,
    sin  = Math.sin,
    cos  = Math.cos,
    tan  = Math.tan,
    asin = Math.asin,
    atan = Math.atan2,
    acos = Math.acos,
    rad  = PI / 180;

// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas


// date/time constants and conversions

var dayMs = 1000 * 60 * 60 * 24,
    J1970 = 2440588,
    J2000 = 2451545;

function toJulian(date) {
    return date.valueOf() / dayMs - 0.5 + J1970;
}
function fromJulian(j) {
    return new Date((j + 0.5 - J1970) * dayMs);
}
function toDays(date) {
    return toJulian(date) - J2000;
}


// general calculations for position

var e = rad * 23.4397; // obliquity of the Earth

function getRightAscension(l, b) {
    return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l));
}
function getDeclination(l, b) {
    return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l));
}
function getAzimuth(H, phi, dec) {
    return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi));
}
function getAltitude(H, phi, dec) {
    return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H));
}
function getSiderealTime(d, lw) {
    return rad * (280.16 + 360.9856235 * d) - lw;
}


// general sun calculations

function getSolarMeanAnomaly(d) {
    return rad * (357.5291 + 0.98560028 * d);
}
function getEquationOfCenter(M) {
    return rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M));
}
function getEclipticLongitude(M, C) {
    var P = rad * 102.9372; // perihelion of the Earth
    return M + C + P + PI;
}
function getSunCoords(d) {

    var M = getSolarMeanAnomaly(d),
        C = getEquationOfCenter(M),
        L = getEclipticLongitude(M, C);

    return {
        dec: getDeclination(L, 0),
        ra: getRightAscension(L, 0)
    };
}


var SunCalc = {};


// calculates sun position for a given date and latitude/longitude

SunCalc.getPosition = function (date, lat, lng) {

    var lw  = rad * -lng,
        phi = rad * lat,
        d   = toDays(date),

        c  = getSunCoords(d),
        H  = getSiderealTime(d, lw) - c.ra;

    return {
        azimuth: getAzimuth(H, phi, c.dec),
        altitude: getAltitude(H, phi, c.dec)
    };
};


// sun times configuration (angle, morning name, evening name)

var times = [
    [-0.83, 'sunrise',       'sunset'      ],
    [ -0.3, 'sunriseEnd',    'sunsetStart' ],
    [   -6, 'dawn',          'dusk'        ],
    [  -12, 'nauticalDawn',  'nauticalDusk'],
    [  -18, 'nightEnd',      'night'       ],
    [    6, 'goldenHourEnd', 'goldenHour'  ]
];

// adds a custom time to the times config

SunCalc.addTime = function (angle, riseName, setName) {
    times.push([angle, riseName, setName]);
};


// calculations for sun times

var J0 = 0.0009;

function getJulianCycle(d, lw) {
    return Math.round(d - J0 - lw / (2 * PI));
}
function getApproxTransit(Ht, lw, n) {
    return J0 + (Ht + lw) / (2 * PI) + n;
}
function getSolarTransitJ(ds, M, L) {
    return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L);
}
function getHourAngle(h, phi, d) {
    return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d)));
}


// calculates sun times for a given date and latitude/longitude

SunCalc.getTimes = function (date, lat, lng) {

    var lw  = rad * -lng,
        phi = rad * lat,
        d   = toDays(date),

        n  = getJulianCycle(d, lw),
        ds = getApproxTransit(0, lw, n),

        M = getSolarMeanAnomaly(ds),
        C = getEquationOfCenter(M),
        L = getEclipticLongitude(M, C),

        dec = getDeclination(L, 0),

        Jnoon = getSolarTransitJ(ds, M, L);


    // returns set time for the given sun altitude
    function getSetJ(h) {
        var w = getHourAngle(h, phi, dec),
            a = getApproxTransit(w, lw, n);

        return getSolarTransitJ(a, M, L);
    }


    var result = {
        solarNoon: fromJulian(Jnoon),
        nadir: fromJulian(Jnoon - 0.5)
    };

    var i, len, time, angle, morningName, eveningName, Jset, Jrise;

    for (i = 0, len = times.length; i < len; i += 1) {
        time = times[i];

        Jset = getSetJ(time[0] * rad);
        Jrise = Jnoon - (Jset - Jnoon);

        result[time[1]] = fromJulian(Jrise);
        result[time[2]] = fromJulian(Jset);
    }

    return result;
};


// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas

function getMoonCoords(d) { // geocentric ecliptic coordinates of the moon

    var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
        M = rad * (134.963 + 13.064993 * d), // mean anomaly
        F = rad * (93.272 + 13.229350 * d),  // mean distance

        l  = L + rad * 6.289 * sin(M), // longitude
        b  = rad * 5.128 * sin(F),     // latitude
        dt = 385001 - 20905 * cos(M);  // distance to the moon in km

    return {
        ra: getRightAscension(l, b),
        dec: getDeclination(l, b),
        dist: dt
    };
}

SunCalc.getMoonPosition = function (date, lat, lng) {

    var lw  = rad * -lng,
        phi = rad * lat,
        d   = toDays(date),

        c = getMoonCoords(d),
        H = getSiderealTime(d, lw) - c.ra,
        h = getAltitude(H, phi, c.dec);

    // altitude correction for refraction
    h = h + rad * 0.017 / tan(h + rad * 10.26 / (h + rad * 5.10));

    return {
        azimuth: getAzimuth(H, phi, c.dec),
        altitude: h,
        distance: c.dist
    };
};


// calculations for illumination parameters of the moon,
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus
// (Willmann-Bell, Richmond) 1998.

SunCalc.getMoonIllumination = function (date) {

    var d = toDays(date),
        s = getSunCoords(d),
        m = getMoonCoords(d),

        sdist = 149598000, // distance from Earth to Sun in km

        phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
        inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi));

    return {
        fraction: (1 + cos(inc)) / 2,
        angle: atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec)
            - cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra))
    };
};


// export as AMD module / Node module / browser variable

if (typeof define === 'function' && define.amd) {
    define(SunCalc);
} else if (typeof module !== 'undefined') {
    module.exports = SunCalc;
} else {
    window.SunCalc = SunCalc;
}

}());