PHP-сценарий отображения погодной информации на любых устройствах, поддерживающих HTML 5.0

Наверное, некоторые из вас уже сталкивались с тем, что на многих устаревающих устройствах нельзя не только новое приложение поставить, но и старые ранее установленные приложение порой отказываются запускаться. Так, у меня остались несколько старых планшетов, которые даже нельзя в качестве погодных станций использовать, так как приложения с прогнозом погоды отказывались запускаться.

Но оставался вариант сделать самодельную web-страничку с прогнозом погоды, которая бы позволила ещё некоторое время старым устройствам принести некоторую пользу в домашнем быту. Рассказ об этом ниже.

Исходные данные

Изначально в домашнем «наборе» имелось:

Цель

Основная цель -- подключить старое устройство к источнику питания и заставить его в круглосуточном режиме отображать текущие климатические показания и прогноз погоды. При этом хочется разместить устройство в таких местах, чтобы было легко планировать свой выход на улицу: на видном месте в комнате или в коридоре около выхода из квартиры.

Для новых устройств эта цель достигается установкой соответствующего погодного приложения. Но старые устройства не только перестают поддерживаться производителями в части обновлений операционных систем, но на них перестают работать почти все старые ранее установленные сторонние приложения. Из работающих остаются только встроенные приложения, не требующие поддержки со стороны сервера производителя.

Примерный внешний вид желаемого результата

В результате на экране «погодной станции» хотелось иметь блок с текущими климатическими показаниями, прогнозом на ближайшие 12 часов, а также текущее время в виде стрелочного циферблата.

Шаги решения

Для начала я искал бесплатные сервисы с прогнозом погоды, которые бы автоматически в круглосуточном режиме обновляли данные внутри окна браузера. Но после некоторых итераций, перепробовав разные сервисы, я пришёл к выводу, что придется делать свою самодельную web-страницу.

В процессе поиска я нашёл сервис прогноза погоды «Open Weather Мap», который позволяет после регистрации по e-mail в бесплатном режиме получать данные по прогнозу погоды для произвольного населенного пункта на ближайшие несколько дней.

В качестве ответа погодный сервис возвращает в формате JSON массив записей с прогнозами погоды. Каждая запись сопровождается меткой времени, к которой привязаны прогнозные данные, а также прогнозные значения погодных показателей:

{
 "cod":                 "200",
 "message":                 0,
 "cnt":                    40,
 "list":
 [
  {
   "dt":           1580288400,   // Метка времени прогноза
   "main":
   {
    "temp":                 3,   // Ожидаемая температура
    "feels_like":       -0.75,   // Ожидаемые ощущения человека от температуры при данных погодных значениях
    "temp_min":          0.08,   // Минимальная ожидаемая температура
    "temp_max":             3,   // Максимальная ожидаемая температура
    "pressure":          1009,   // Давление в килопаскалях
    "sea_level":         1009,   // Давление в килопаскалях на уровне моря
    "grnd_level":         988,   // Давление в килопаскалях на высоте населенного пункта
    "humidity":            97,   // Влажность воздуха в процентах
    "temp_kf":           2.92    // Дальность видимости в км
   },
   "weather":
   [
    {
     "id":                 804,  // Код состояния облачности
     "main":          "Clouds",  // Обозначение состояния облачности
     "description": "пасмурно",  // Перевод обозначения облачности на указанный в параметрах язык
     "icon":             "04d"   // Код пиктограммы символа облачности
    }
   ],
   "clouds": {"all":97},         // Облачность
   "wind":
   {
     "speed":              3.1,  // Скорость ветра в м/с
     "deg":                172   // Направление ветра в градусах
	},
   "sys": {"pod":"d"},
   "dt_txt": "2020-01-29 09:00:00"
  },
  {
   "dt":            1580299200,  // Метка времени следующего прогноза
   "main":
   {
    "temp":               2.71,
    "feels_like":        -0.96,
    "temp_min":           0.52,
    "temp_max":           2.71,
    "pressure":           1009,
    "sea_level":          1009,
    "grnd_level":          988,
    "humidity":            100,
    "temp_kf":            2.19
   },
   "weather":
   [
    {
     "id":                 804,
     "main":          "Clouds",
     "description": "пасмурно",
     "icon":             "04d"
    }
   ],
   "clouds": {"all":99},
   "wind":
   {
    "speed":              3.03,
    "deg":                 130
   },
   "sys": {"pod":"d"},
   "dt_txt":"2020-01-29 12:00:00"
  },
 
  ...
 
 ]
}

В качестве пиктограмм я использовал символы из шрифта «weathericons-regular-webfont»:

В первом приближении я сделал просто HTML-страницу, которая самостоятельно взаимодействовала с этим сервисом. Но потом, когда я осознал, что все перечисленные выше старые мобильные устройства не могут работать с локально сохраненной HTML-страницей, стало понятно, что придётся выкладывать её на некий HTTP-сервер. Благо, таковой у меня имеется под рукой.

А раз всё равно пришлось задействовать HTTP-сервер, то заодно я реализовал кеширование данных на стороне сервера, чтобы снизить нагрузку на используемый сервис и заодно уменьшить задержки при работе JavaScript-сценариев на HTML-странице. Более того, старые устройства не только были не способны запускать сторонние приложения, но и сохранили устаревшие настройки часовых поясов в устаревшей версии операционной системы. То есть невозможно было использовать эти устройства в режиме автоматического обновления времени (только ручная настройка), а в режиме ручной настройки, низкая точность отсчета времени в этих устройствах приводит к тому, что за несколько недель показания встроенных часов убегали от реальных на несколько минут.

Таким образом, на стороне сервера я разместил PHP-сценарий, который генерирует HTML-страницу, визуализирующую данные из сервиса «Open Weather Мap», а также разместил PHP-сценарий, кеширования данных с этого сервиса, и сценарий передачи точного времени:

Так как мне хотелось помимо прогноза погоды видеть еще и текущее время в виде стрелочного циферблата, то я нашел JavaScript-сценарий, отображающий текущее время в виде стрелочных часов. После недолгих поисков я остановился на варианте CoolClock Саймона Бейрда (Simon Baird), который поддерживал некоторый набор удобных пользовательских настроек. Благодаря этому я смог настроить CoolClock под свои ожидания.

Работа со структурой HTML-документа (DOM) в языке JavaScript для меня довольно громоздка и синтаксически приторна, поэтому я по привычке подключаю библиотеку JQuery.

Для дополнительного эстетического удовольствия я решил использовать различные фоновые изображения. Смену фоновых изображений я привязал к календарным месяцам. А так как сервис прогноза погоды выдает информацию о примерном времени восхода и заката солнца, то я дополнительно добавил опцию смены изображения при переходе от светлого времени суток к тёмному, и обратно.

Нюансы

Для загрузки и кеширования данных сервиса прогноза погоды изначально я использовал довольно качественную функцию file_get_contents(), но в течение нескольких месяцев эксплуатации я стал замечать, что периодически данная функция не отрабатывает нормально, и выдает пустой результат с ошибкой таймаута. Поэтому для загрузки данных с сервиса используется «костыль» (workaround), основанный на вызове системной утилиты curl.

Сценарии

index.htm

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv=content-type content="text/html; charset=utf-8">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <link rel="stylesheet" type="text/css" href="css/main.css">
 
    <title>Погода</title>
 
    <script type="text/javascript" src="js/jquery.js"></script>
    <script type="text/javascript" src="js/coolclock.js"></script>
    <script type="text/javascript" src="js/moreskins.js"></script>
    <script type="text/javascript" src="js/main.js"></script>
  </head>
  <body>
    <canvas id="clockid" class="CoolClock:customMax:170"></canvas>
 
    <div id="pogoda" class="pogoda">
      <div class="errorMessage">Ошибка загрузки данных</div>
      <div class="data">
        <div>
          <div id="tomorrow"></div>
          <div>Сейчас <small><span id="timeStamp"></span></small></div>
          <div id="sky"></div>
          <div id="temp"></div>
        </div>
        <table class="segments">
          <tr>
            <th>Ветер</th>
            <th>Давление</th>
            <th>Влажность</th>
            <th>Видимость</th>
          </tr>
          <tr>
            <td><div id="wind"></div></td>
            <td><div id="pressure"></div></td>
            <td><div id="humidity"></div></td>
            <td><div id="visibility"></div></td>
          </tr>
        </table>
        <div id="daylight"></div>
      </div>
    </div>
    <div id="forecast" class="pogoda forecastHours">
      <div class="errorMessage">Ошибка загрузки данных</div>
        <div class="data">
        <table class="segments">
          <tr>
            <td><div id="seg0"></div></td>
            <td><div id="seg1"></div></td>
            <td><div id="seg2"></div></td>
            <td><div id="seg3"></div></td>
            <td><div id="seg4"></div></td>
            <td><div id="seg5"></div></td>
          </tr>
        </table>
      </div>
    </div>
  </body>
</html>

main.js

var serverTimeDiff = 0;
var dataSrc = "weather.data.php";
var dataForeCastSrc = "forecast.data.php";
 
//https://openweathermap.org/weather-conditions
var skyIconName =
  {
    // Day
    "01d": "&#xf00d;", // Clear sky
    "02d": "&#xf002;", // Few clouds
    "03d": "&#xf041;", // scattered clouds
    "04d": "&#xf013;", // broken clouds
    "09d": "&#xf019;", // shower rain
    "10d": "&#xf008;", // rain
    "11d": "&#xf01e;", // thunderstorm
    "13d": "&#xf064;", // snow
    "50d": "&#xf021;", // mist
 
    // Night
    "01n": "&#xf02e;",  // Clear sky
    "02n": "&#xf031;",  // Few clouds
    "03n": "&#xf041;",  // scattered clouds
    "04n": "&#xf013;",  // broken clouds
    "09n": "&#xf019;",  // shower rain
    "10n": "&#xf028;",  // rain
    "11n": "&#xf01e;",  // thunderstorm
    "13n": "&#xf064;",  // snow
    "50n": "&#xf021;",  // mist
  };
var bgImages = {
  "day":
  [
    '01.day.jpg',
    '02.day.jpg',
    '03.day.jpg',
    '04.day.jpg',
    '05.day.jpg',
    '06.day.jpg',
    '07.day.jpg',
    '08.day.jpg',
    '09.day.jpg',
    '10.day.jpg',
    '11.day.jpg',
    '12.day.jpg' 
  ],
  "night":
  [ 
    '01.night.jpg',
    '02.night.jpg',
    '03.night.jpg',
    '04.night.jpg',
    '05.night.jpg',
    '06.night.jpg',
    '07.night.jpg',
    '08.night.jpg',
    '09.night.jpg',
    '10.night.jpg',
    '11.night.jpg',
    '12.night.jpg' 
  ]
};
var windNames = [ 'В', 'СВ', 'С', 'СЗ', 'З', 'ЮЗ', 'Ю', 'ЮВ' ];
var windIcons = 
[ 
  '&#xF061;', 
  '&#xF05E;', 
  '&#xF060;', 
  '&#xF05D;', 
  '&#xF059;', 
  '&#xF05A;', 
  '&#xF05C;', 
  '&#xF05B;' 
];
var timeIcons = 
[ 
  '&#xF089;', 
  '&#xF08A;', 
  '&#xF08B;', 
  '&#xF08C;', 
  '&#xF08D;', 
  '&#xF08E;', 
  '&#xF08F;', 
  '&#xF090;', 
  '&#xF091;', 
  '&#xF092;', 
  '&#xF093;', 
  '&#xF094;' 
];
var bgImageDir  = "images/";
var bgImage     = "";
 
 
function isset ( v )
{
  return typeof v !== "undefined"
}
 
function loadServerTime ()
{
  $.ajax
  (
    {
      url: "server.time.php",
    }
  ).done ( function ( d ) {
    data = JSON.parse ( d );
    if ( isset ( data ) && isset ( data.status ) && data.status == "OK" && isset ( data.timestamp ) )
      serverTimeDiff = new Date ( data.timestamp * 1000 ) - new Date ();
  });
  window.setTimeout( loadServerTime, 12*60*60*1000 ); // 12 hours server sync
}
 
 
function addLeadingZero ( str, len )
{
  str = String ( str );
  for ( var i = str.length; i < len; ++i )
    str = "0" + str;
  return str;
}
 
function screenDrawError ( node )
{
  $( node + " > .errorMessage" ).removeClass ( "hidden" );
  $( node + " > .data" ).addClass ( "hidden" );
}
 
function decorateTemp ( temp, fraq )
{
  temp = mathRound ( temp, fraq );
  return ( 0 < temp ) ? 
          "<span class='tempSign'>&plus;</span>"  + temp + "°" : 
          ( temp ) ? "<span class='tempSign'>&minus;</span>" + Math.abs ( temp ) + "°" : "0°";
}
 
function decorateServiceTime ( serviceTime, style )
{
  var toDay = new Date ();
  var time  = new Date ( serviceTime * 1000 );
 
  if ( style == 1 )
    return time.getHours();// + " ч";
  else if ( style == 2 )
    return addLeadingZero ( time.getHours(), 2 ) + "<sup>" + addLeadingZero ( time.getMinutes(), 2 ) + "</sup>";
  else
    return addLeadingZero ( time.getHours(), 2 ) + ":" + addLeadingZero ( time.getMinutes(), 2 );
}
 
function mathRound ( number, digits )
{
  if ( isNaN (digits) || digits < 0 || 10 < digits )
    return Math.round ( number );
  else
  {
    var tens = 10;
    for ( var i = 1; i < digits; ++i )
      tens *= 10;
    return Math.round ( number * tens ) / tens;
  }
}
 
function screenDraw ( data )
{
  var toDay           = new Date();
  var timeSecs        = Math.round ( toDay.getTime () / 1000 );
  var timeDataLoad    = toDay;
  var timeStr         = addLeadingZero ( timeDataLoad.getHours(), 2 ) + ":" + addLeadingZero ( timeDataLoad.getMinutes(), 2 );
  var timeSunriseStr  = decorateServiceTime ( data.sys.sunrise );
  var timeSunsetStr   = decorateServiceTime ( data.sys.sunset );
  var pressure        = Math.round ( data.main.pressure * 7.50062 ) / 10 - 13;
  var visibility      = data.visibility;
  var dataTemp        = mathRound ( data.main.temp, 1 );
  var skyDesc         = "";
  var skyIcon         = "";
  var windSpeed       = mathRound ( data.wind.speed, 1);
  var windIndex       = Math.floor ( ( data.wind.deg ) % 360 / 45 );
  var wind            = "тихо";
  var windIcon        = "";
 
 
  if ( isset ( data.wind.deg ) )
  {
    wind      = windNames [ windIndex ];
    windIcon  = windIcons [ windIndex ];
  }
 
  if ( data.sys.sunrise < timeSecs && timeSecs < data.sys.sunset )
  {
    skyClass = "day";
    bgImage = bgImageDir + bgImages.day[toDay.getMonth ()];
  }
  else
  {
    skyClass = "night";
    bgImage = bgImageDir + bgImages.night[toDay.getMonth ()];
  }
 
  dataTemp  = decorateTemp ( dataTemp );
  skyDesc   = data.weather[0].description;
  skyIcon   = skyIconName[data.weather[0].icon];
 
  if ( isset ( visibility ) )
  {
    if ( 1000 <= visibility )
      visibility = Math.round ( visibility / 100 ) / 10 + "<small> км</small>";
    else
      visibility = visibility + "<small> м</small>";
  }
  else
    visibility = "&#151;";
 
  $("#timeStamp").text ( timeStr );
  $("#sky").html ( '<div id="skyIcon"></div><span class="' + skyClass + '"> ' + skyDesc + '</span>' );
  $("#skyIcon").html ( skyIcon ).addClass ( skyClass );
  $("#temp").html ( dataTemp );
  $("#wind").html ( "<div class='skyIcon' style='color:gray'>" + windIcon + "</div> " + windSpeed + "<small> м/с, " + wind + "</small>" );
  $("#pressure").html ( pressure  + "<small> мм рт. ст.</small>" );
  $("#visibility").html ( visibility );
  $("#humidity").html ( data.main.humidity + "<small> %</small>" );
  $("#daylight").html ( 
    "<small><div class='skyIcon'>&#xF051;</div> " +  timeSunriseStr + 
    " &mdash; <div class='skyIcon'>&#xF052;</div> " + timeSunsetStr + "</small>" 
  );
  $("html").css ( "background-image", "url(" + bgImage + ")" );
}
 
function screenDrawForeCast ( data )
{
  var toDay = new Date ();
  var time;
 
  for ( var i = 0; i < 6; ++i )
  {
    time = new Date ( data.list[i].dt * 1000 );
    $("#seg" + i).html ( 
      "<small><div class='skyIcon'>" + timeIcons [ time.getHours () % 12 ] + "</div> " + 
      decorateServiceTime ( data.list[i].dt, 1 ) + "&nbsp;&nbsp;<div class='skyIcon' style='color:white'>" + 
      skyIconName[data.list[i].weather[0].icon] + "</div></small><br/> <big>" + 
      decorateTemp ( data.list[i].main.temp ) + "</big>" 
    );
  }
 
  // find tomorrow day and night forecast
  var dayForecastTemp   = 0;
  var nightForecastTemp = 0;
  var dayTempFound  	  = false;
  var nightTempFound    = false;
 
  for ( var i = 4; i < 20; ++i )
  {
    time = new Date ( data.list[i].dt * 1000 );
    if ( !dayTempFound && 12 < time.getHours () && time.getHours () < 16 )
    {
      dayForecastTemp = decorateTemp ( data.list[i].main.temp );
      dayTempFound = true;
    }
    if ( !nightTempFound && 2 < time.getHours () && time.getHours () < 4 )
    {
      nightForecastTemp = decorateTemp ( data.list[i].main.temp );
      nightTempFound = true;
    }
  }
  res = "Завтра<div><div class='foreCastTemp'>" + dayForecastTemp + 
        "</div></div><div><div class='foreCastTemp'>" + nightForecastTemp + "</div></div>";
  $("#tomorrow").html (res);
}
 
function loadData ()
{
  var timeoutDataID = window.setTimeout( loadData, 300000 ); // 5 minutes update period
          $(".errorMessage").addClass ( "hidden" );
          $(".data" ).removeClass ( "hidden" );
  $.ajax
  (
    {
      url: dataSrc,
    }
  ).fail ( function() {
    screenDrawError ( "#pogoda" );
  }).done ( function( data ) {
    if ( data == "{error, 'Fail to load data'}" )
      screenDrawError ( "#pogoda" );
    else
      screenDraw ( data );
  });
  $.ajax
  (
    {
      url: dataForeCastSrc
    }
  ).fail ( function() {
    screenDrawError ( "#forecast" );
  }).done ( function( data ) {
    if ( data == "{error, 'Fail to load data'}" )
      screenDrawError ( "#forecast" );
    else
      screenDrawForeCast ( data );
  });
}
 
function timeDraw ()
{
  var toDay = new Date();
  var time  = addLeadingZero ( toDay.getHours(), 2 ) + ":" + 
              addLeadingZero ( toDay.getMinutes(), 2 ) + " <span class='seconds'>" + 
              addLeadingZero ( toDay.getSeconds(), 2 ) + "</span>";
  $( "#time" ).html ( time );
  var timeoutTimeID = window.setTimeout ( timeDraw, 1000 );
}
 
$(document).ready ( function ()
{
  loadServerTime ();
  CoolClock.findAndCreateClocks();
  $(document).ready ( loadData () );
});

server.time.php

Сценарий «server.time.php» возвращает метку текущего серверного времени для того, чтобы на старом устройстве отображалось верное время. На многих старых устройствах функция автоматической установки точного времени может работать некорректно, так как за то время, когда эти устройства были обновлены в последний удачный раз, в Российской Федерации могли несколько раз поменять региональную схему часовых поясов. Поэтому приходится выключать функцию автоматического обновления, а в автономном режиме встроенные в устройство часы слишком быстро убегают от точного времени за пару месяцев эксплуатации.

<?php
print "{\"status\":\"OK\",\"timestamp\":" . time () . "}";
?>

Ответ будет выглядеть примерно так:

{"status":"OK","timestamp":1582353218}

weather.data.php

Сценарий «weather.data.php» отдает кешированный результат обращения к сервису погоды по запросу текущих климатических показаний. Если кеш пуст или просрочился, то сценарий обновляет данные в файл ".cache/weather.json".

<?php
  header ( "Content-Type: application/json; charset=utf-8" );
  $weatherCache   = ".cache/weather.json";
  $cacheTimout    = 60 * 10; // 10 min
  $cachePreTimout = 60 * 9;  // 9 min
 
  function file_get_contents_curl ( $url )
  {
    $ch = curl_init();
    curl_setopt ( $ch, CURLOPT_HEADER, 0 );
    curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, 1 ); // Set curl to return the data instead of printing it to the browser.
    curl_setopt ( $ch, CURLOPT_URL, $url );
    $data = curl_exec ( $ch );
    curl_close ( $ch );
    return $data;
  }
 
  function updateData ( bool $out = TRUE )
  {
    global $weatherCache;
    $dataSrc = "http://api.openweathermap.org/data/2.5/weather?" +
               "id=___Вставьте_код_вашего_населённого_пункта___&" +
               "appid=___Вставьте_сюда_ваш_ключ_аутентификации___&units=metric&lang=ru";
 
    $result  = file_get_contents_curl ( $dataSrc );
 
    if ( $result === FALSE )
      print "{error, 'Fail to load data'}";
    else
    {
      if ( $out )
      {
        print $result;
        flush ();
      }
      $fd = fopen ( $weatherCache, "w" );
      fwrite ( $fd, $result );
      fclose ( $fd );
    }
  }
 
  if ( file_exists ( $weatherCache ) )
  {
    $term = time () - filemtime ( $weatherCache );
    if ( $term < $cacheTimout )
    {
      // Send cache
      readfile ( $weatherCache );
      flush ();
 
      if ( $cachePreTimout < $term )
        updateData ( FALSE );
    }
    else
      updateData ();
  }
  else
    updateData ();
?>

forecast.data.php

Сценарий «forecast.data.php» отдает кешированный результат обращения к сервису погоды по запросу прогноза погоды на ближайшие несколько часов. Если кеш пуст или просрочился, то сценарий обновляет данные в файл ".cache/forecast.json".

<?php
  header ( "Content-Type: application/json; charset=utf-8" );
  $weatherCache   = ".cache/forecast.json";
  $cacheTimout    = 60 * 10; // 10 min
  $cachePreTimout = 60 * 9;  // 9 min
 
  function file_get_contents_curl ( $url )
  {
    $ch = curl_init ();
    curl_setopt ( $ch, CURLOPT_HEADER, 0 );
    curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, 1 ); // Set curl to return the data instead of printing it to the browser.
    curl_setopt ( $ch, CURLOPT_URL, $url );
    $data = curl_exec ( $ch );
    curl_close ( $ch );
    return $data;
  }
 
  function updateData ( bool $out = TRUE )
  {
    global $weatherCache;
    $dataSrc = "http://api.openweathermap.org/data/2.5/forecast?" +
               "id=___Вставьте_код_вашего_населённого_пункта___&" +
               "appid=___Вставьте_сюда_ваш_ключ_аутентификации___&units=metric&lang=ru";
 
    $result  = file_get_contents_curl ( $dataSrc );
 
    if ( $result === FALSE )
      print "{error, 'Fail to load data'}";
    else
    {
      if ( $out )
      {
        print $result;
        flush ();
      }
      $fd = fopen ( $weatherCache, "w" );
      fwrite ( $fd, $result );
      fclose ( $fd );
    }
  }
 
  if ( file_exists ( $weatherCache ) )
  {
    $term = time () - filemtime ( $weatherCache );
    if ( $term < $cacheTimout )
    {
      // Send cache
      readfile ( $weatherCache );
      flush ();
 
      if ( $cachePreTimout < $term )
        updateData ( FALSE );
    }
    else
      updateData ();
  }
  else
    updateData ();
?>

main.css

@font-face {
  font-family: 'PT Sans';
  font-style: normal;
  font-weight: 400;
  src: local('PT Sans'), local('PTSans-Regular'),
  url(http://themes.googleusercontent.com/static/fonts/ptsans/v5/LKf8nhXsWg5ybwEGXk8UBQ.woff) format('woff');
}
@font-face {
  font-family: 'PT Sans';
  font-style: normal;
  font-weight: 700;
  src: local('PT Sans Bold'), local('PTSans-Bold'),
  url(http://themes.googleusercontent.com/static/fonts/ptsans/v5/0XxGQsSc1g4rdRdjJKZrNBsxEYwM7FgeyaSgU71cLG0.woff) format('woff');
}
@font-face {
  font-family: 'PT Sans';
  font-style: italic;
  font-weight: 400;
  src: local('PT Sans Italic'), local('PTSans-Italic'),
  url(http://themes.googleusercontent.com/static/fonts/ptsans/v5/PIPMHY90P7jtyjpXuZ2cLD8E0i7KZn-EPnyo3HZu7kw.woff) format('woff');
}
@font-face {
  font-family: 'PT Sans';
  font-style: italic;
  font-weight: 700;
  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'),
  url(http://themes.googleusercontent.com/static/fonts/ptsans/v5/lILlYDvubYemzYzN7GbLkHhCUOGz7vYGh680lGh-uXM.woff) format('woff');
}
 
@font-face {
  font-family: 'PT Serif';
  font-style: normal;
  font-weight: 400;
  src: local('PT Serif'), local('PTSerif-Regular'),
  url(http://themes.googleusercontent.com/static/fonts/ptserif/v5/sDRi4fY9bOiJUbgq53yZCfesZW2xOQ-xsNqO47m55DA.woff) format('woff');
}
@font-face {
  font-family: 'PT Serif';
  font-style: normal;
  font-weight: 700;
  src: local('PT Serif Bold'), local('PTSerif-Bold'),
  url(http://themes.googleusercontent.com/static/fonts/ptserif/v5/QABk9IxT-LFTJ_dQzv7xpIbN6UDyHWBl620a-IRfuBk.woff) format('woff');
}
@font-face {
  font-family: 'PT Serif';
  font-style: italic;
  font-weight: 400;
  src: local('PT Serif Italic'), local('PTSerif-Italic'),
  url(http://themes.googleusercontent.com/static/fonts/ptserif/v5/03aPdn7fFF3H6ngCgAlQzBsxEYwM7FgeyaSgU71cLG0.woff) format('woff');
}
@font-face {
  font-family: 'PT Serif';
  font-style: italic;
  font-weight: 700;
  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'),
  url(http://themes.googleusercontent.com/static/fonts/ptserif/v5/Foydq9xJp--nfYIx2TBz9QFhaRv2pGgT5Kf0An0s4MM.woff) format('woff');
}
 
@font-face {
  font-family: 'weathericons';
  src: url('/pogoda/fonts/weathericons-regular-webfont.eot');
  src: url('/pogoda/fonts/weathericons-regular-webfont.eot?#iefix') format('embedded-opentype'),
  url('/pogoda/fonts/weathericons-regular-webfont.woff2') format('woff2'),
  url('/pogoda/fonts/weathericons-regular-webfont.woff') format('woff'),
  url('/pogoda/fonts/weathericons-regular-webfont.ttf') format('truetype'),
  url('/pogoda/fonts/weathericons-regular-webfont.svg#weather_iconsregular') format('svg');
  font-weight: normal;
  font-style: normal;
}
 
html
{
  background                  : url() no-repeat center center fixed;
 -webkit-background-size      : cover;
 -moz-background-size         : cover;
 -o-background-size           : cover;
  background-size             : cover;
  height                      : 100%;
}
 
body
{
  margin                      : 0px 0px 0px 0px;
  padding                     : 0px 0px 0px 0px;
  font-size                   : 12pt;
  color                       : white;
  font-family                 : 'PT Sans', sans-serif;
}
 
.pogoda
{
  background                  : rgba(0,0,0,0.7);
  margin                      : 20px;
  padding                     : 40px;
  width                       : 500px;
  border-radius               : 20px;
 -moz-border-radius           : 20px;
}
 
.forecastHours
{
  position                    : absolute;
  left                        : 0px;
  right                       : 0px;
  bottom                      : 0px;
  width                       : auto;
}
 
.forecastHours td small
{
  font-size                   : 100%;
  color                       : #aaa;
}
 
.forecastHours big
{
  font-size                   : 240%;
}
 
#tomorrow
{
  float                       : right;
  width                       : 140px;
  height                      : 100%;
  color                       : #ddd;
  border-left                 : 1px #444 solid;
}
 
#tomorrow div
{
  margin-top                  : 35px;
  font-size                   : 80%;
}
 
#forecast
{
  text-align                  : left;
  color                       : #aaa;
}
 
#tomorrow .foreCastTemp
{
  font-size                   : 350%;
  margin-top                  : 0px;
}
 
table.segments
{
  width                       : 100%;
}
 
.segments th
{
  font-weight                 : normal;
  font-size                   : 80%;
  color                       : #aaa;
}
 
.segments td
{
  text-align                  : center;
  font-size                   : 140%;
}
 
.segments small
{
  font-size                   : 60%;
}
 
.pogoda div
{
  text-align                  : center;
}
 
#sky
{
  font-size                   : 40px;
}
 
#temp
{
  font-size                   : 120px;
}
 
#time
{
  font-size                   : 50px;
  color                       : #004488;
}
 
#time .seconds
{
  font-size                   : 60%;
  vertical-align              : super;
}
 
#skyIcon, .skyIcon
{
  display                     : inline-block;
  font-family                 : 'weathericons';
  font-style                  : normal;
  font-weight                 : normal;
  line-height                 : 1;
 -webkit-font-smoothing       : antialiased;
 -moz-osx-font-smoothing      : grayscale;
}
 
.segments sup
{
  font-size                   : 70%;
}
 
.day
{
  color                       : #ffbb00;
}
.night
{
  color                       : #0088ff;
}
 
#daylight
{
  color                       : #aaa;
}
 
.tempSign
{
  font-family                 : sans-serif;
}
 
#clockid
{
  width                       : 340px;
  height                      : 340px;
  float                       : right;
  margin-right                : 20px;
}
 
.hidden
{
  display                     : none;
  visibility                  : hidden;
}

21 февраля 2020—21 февраля 2020
Максим Проскурня
Реклама от хост-провайдера
Коттеджи в Подмосковье
© 1997–2022 Axofiber, axofiber.ru, axofiber.info