├── .eslintrc
├── .gitignore
├── LICENCE.md
├── README.md
├── app
├── css
│ └── theme.css
├── favicon.ico
├── fonts
│ ├── meteocons.eot
│ ├── meteocons.svg
│ ├── meteocons.ttf
│ ├── meteocons.woff
│ ├── meteocons.woff2
│ ├── roboto-light.eot
│ ├── roboto-light.svg
│ ├── roboto-light.ttf
│ ├── roboto-light.woff
│ └── roboto-light.woff2
├── index.html
├── js
│ ├── angular-animate.min.js
│ ├── angular.min.js
│ └── app.js
└── scss
│ ├── _animations.scss
│ ├── _fonts.scss
│ ├── _normalize.scss
│ └── theme.scss
└── package.json
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "angular"
4 | ],
5 |
6 | "env": {
7 | "browser": true,
8 | "node": true
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node junk
2 | node_modules
3 | npm-debug.log
4 |
--------------------------------------------------------------------------------
/LICENCE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016, Max Milton
2 |
3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
4 |
5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | A simple experiment using AngularJS, the OpenWeatherMap API, and CSS3.
2 |
3 | # Overview
4 |
5 | Mostly just a project to allow me to experiment with AngularJS. View a live version at https://labs.wearegenki.com/experiments/simple-weather-app/
6 |
7 | Source code available on [Github](https://github.com/MaxMilton/Simple-Weather-App).
8 |
9 | ## Interesting Points
10 |
11 | * Gets weather via the OpenWeatherMap API using JSONP requests.
12 | * Uses browser built in geolocation to get coordinates.
13 | * CSS3 animations on colour change and location panel appearing.
14 | * Shows weather from a random city on page load and button.
15 | * Able to run on https even though OpenWeatherMap is http only by using a proxy
16 |
17 | ## Development Instructions
18 |
19 | 1. Watch SCSS for changes: `npm run-script watch`
20 | 2. Start the local web server: `npm start`
21 | 3. Open a new browser tab and go to: http://localhost:8880/
22 |
23 | ## Made Using
24 |
25 | * Weather Icons - http://www.alessioatzeni.com/meteocons/
26 | * OpenWeatherMap API - http://openweathermap.org/api
27 | * Normalize.css - https://necolas.github.io/normalize.css/
28 |
--------------------------------------------------------------------------------
/app/css/theme.css:
--------------------------------------------------------------------------------
1 | html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@font-face{font-family:Roboto;src:url("../fonts/roboto-light.eot");src:url("../fonts/roboto-light.eot?#iefix") format("eot"),url("../fonts/roboto-light.woff2") format("woff2"),url("../fonts/roboto-light.woff") format("woff"),url("../fonts/roboto-light.ttf") format("truetype");font-weight:300;font-style:normal}@font-face{font-family:Meteocons;src:url("../fonts/meteocons.eot");src:url("../fonts/meteocons.eot?#iefix") format("eot"),url("../fonts/meteocons.woff2") format("woff2"),url("../fonts/meteocons.woff") format("woff"),url("../fonts/meteocons.ttf") format("truetype");font-weight:normal;font-style:normal;text-rendering:auto;-moz-osx-font-smoothing:grayscale;transform:translate(0, 0)}.pull-down{animation-name:pullDown;-webkit-animation-name:pullDown;animation-duration:0.7s;-webkit-animation-duration:0.7s;animation-timing-function:ease-out;-webkit-animation-timing-function:ease-out;transform-origin:50% 0%;-ms-transform-origin:50% 0%;-webkit-transform-origin:50% 0%}@keyframes pullDown{0%{transform:scaleY(0.1)}100%{transform:scaleY(1)}}@-webkit-keyframes pullDown{0%{-webkit-transform:scaleY(0.1)}100%{-webkit-transform:scaleY(1)}}[ng-cloak]{display:none !important}html{background-color:#E0E0E0;color:#212121;font:300 1.2em Roboto,"Roboto",sans-serif;margin-left:1em;margin-right:1em}header{margin-bottom:2em}h1{font-size:2.3em;font-weight:300}.lead{font-size:1.3em}button{background-color:transparent;font-family:Roboto,"Roboto",sans-serif;font-size:1.2em;font-weight:300;border:none;padding:0.4em;opacity:0.6}button:focus{outline:none}.btn-random{box-shadow:1px 1px 6px rgba(0,0,0,0.2);border-radius:5px}.btn-random:hover{box-shadow:1px 2px 8px rgba(0,0,0,0.4)}.btn-random>.icon{vertical-align:-2px}.btn-locate:hover{opacity:1}.card{transition:background-color 0.7s ease;color:#fff;padding:1em;border-radius:5px;box-shadow:0 0 15px rgba(0,0,0,0.1);max-width:30em;position:relative;z-index:2}.card.orange{background-color:#FF9800}.card.orange .location-panel{background-color:#F57C00}.card.orange+.card{color:#F57C00}.card.blue{background-color:#2196F3}.card.blue .location-panel{background-color:#1976D2}.card.blue+.card{color:#1976D2}.card.purple{background-color:#673AB7}.card.purple .location-panel{background-color:#512DA8}.card.purple+.card{color:#512DA8}.card.grey{background-color:#607D8B}.card.grey .location-panel{background-color:#455A64}.card.grey+.card{color:#455A64}.card.green{background-color:#4CAF50}.card.green .location-panel{background-color:#388E3C}.card.green+.card{color:#388E3C}.card+.card{background-color:#fff;padding-top:0.6em;margin-top:-0.5em;z-index:1}.card+.card>p{transition:color 0.7s ease}.top{padding:0.3em 1em 1em}.temp-now{font-size:6em;margin:0}.temp-now>span{font-size:0.6em;vertical-align:0.48em}.weather-now{font-size:2em;text-transform:capitalize;margin:0.1em 0 0.2em}.highlow{font-size:1.2em;margin:0 0 0.2em}.location-panel{transition:background-color 0.7s ease;font-size:1.1em;border-radius:5px}.search{display:inline}.location{background:url('data:image/svg+xml;utf8,') 0.4em 50% no-repeat;color:#fff;font-family:Roboto,"Roboto",sans-serif;font-size:1.4em;font-weight:300;background-color:transparent;border:none;padding:0.4em 0 0.4em 2em;width:11.8em}.location:focus{outline:none}.icon{font-family:"Meteocons", sans-serif}.icon.big{font-size:8em;float:right}.or{vertical-align:3px;opacity:0.4}.bottom{margin-top:0.9em}.bottom .icon{font-size:4em;margin-right:0.15em;vertical-align:bottom;position:relative;top:0.15em;float:left}.bottom:after{visibility:hidden;display:block;content:"";clear:both;height:0}.left{float:left;width:49.8%}.right{float:right;width:49.8%}.info{font-size:1.2em;vertical-align:middle}footer{text-align:center;margin:3.5em 0 2em}footer>a{color:#bababa;text-decoration:none}
2 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxmilton/Simple-Weather-App/01b3b52484d93ee8c8837c068f413365199002a3/app/favicon.ico
--------------------------------------------------------------------------------
/app/fonts/meteocons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxmilton/Simple-Weather-App/01b3b52484d93ee8c8837c068f413365199002a3/app/fonts/meteocons.eot
--------------------------------------------------------------------------------
/app/fonts/meteocons.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/fonts/meteocons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxmilton/Simple-Weather-App/01b3b52484d93ee8c8837c068f413365199002a3/app/fonts/meteocons.ttf
--------------------------------------------------------------------------------
/app/fonts/meteocons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxmilton/Simple-Weather-App/01b3b52484d93ee8c8837c068f413365199002a3/app/fonts/meteocons.woff
--------------------------------------------------------------------------------
/app/fonts/meteocons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxmilton/Simple-Weather-App/01b3b52484d93ee8c8837c068f413365199002a3/app/fonts/meteocons.woff2
--------------------------------------------------------------------------------
/app/fonts/roboto-light.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxmilton/Simple-Weather-App/01b3b52484d93ee8c8837c068f413365199002a3/app/fonts/roboto-light.eot
--------------------------------------------------------------------------------
/app/fonts/roboto-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/fonts/roboto-light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxmilton/Simple-Weather-App/01b3b52484d93ee8c8837c068f413365199002a3/app/fonts/roboto-light.ttf
--------------------------------------------------------------------------------
/app/fonts/roboto-light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxmilton/Simple-Weather-App/01b3b52484d93ee8c8837c068f413365199002a3/app/fonts/roboto-light.woff
--------------------------------------------------------------------------------
/app/fonts/roboto-light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxmilton/Simple-Weather-App/01b3b52484d93ee8c8837c068f413365199002a3/app/fonts/roboto-light.woff2
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Simple Weather App
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
21 |
22 |
23 |
24 |
25 | Simple Weather App
26 | A simple experiment using AngularJS, various APIs, and CSS3.
27 | Source code available on Github.
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
{{ main.icon }}
38 |
{{ main.kelvinToCelcius(main.dataCurrent.main.temp) }} °C
39 |
{{ main.dataCurrent.weather[0].description }}
40 |
▲ {{ main.kelvinToCelcius(main.dataDaily.list[0].temp.max) }} °C / ▼ {{ main.kelvinToCelcius(main.dataDaily.list[0].temp.min) }} °C
41 |
42 |
43 |
44 |
47 | or
48 |
49 |
50 |
51 |
52 |
53 |
F
54 |
Wind speed: {{ main.dataCurrent.wind.speed }} m/s
55 |
Humidity: {{ main.dataCurrent.main.humidity }}%
56 |
57 |
58 |
A
59 |
Sunrise: {{ main.dataCurrent.sys.sunrise * 1000 | date : 'shortTime' }}
60 |
Sunset: {{ main.dataCurrent.sys.sunset * 1000 | date : 'shortTime' }}
61 |
62 |
63 |
64 |
65 |
66 |
67 | Coordinates: [{{ main.lat }}, {{ main.lon }}]
68 |
69 |
70 |
71 |
72 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/app/js/angular-animate.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | AngularJS v1.5.0-rc.0
3 | (c) 2010-2015 Google, Inc. http://angularjs.org
4 | License: MIT
5 | */
6 | (function(J,t,Ta){'use strict';function xa(a,b,c){if(!a)throw ngMinErr("areq",b||"?",c||"required");return a}function ya(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;Z(a)&&(a=a.join(" "));Z(b)&&(b=b.join(" "));return a+" "+b}function Ka(a){var b={};a&&(a.to||a.from)&&(b.to=a.to,b.from=a.from);return b}function U(a,b,c){var d="";a=Z(a)?a:a&&H(a)&&a.length?a.split(/\s+/):[];w(a,function(a,r){a&&0=a&&(a=k,k=0,b.push(f),f=[]);f.push(h.fn);
27 | h.children.forEach(function(a){k++;c.push(a)});a--}f.length&&b.push(f);return b}(c)}var ea=[],t=O(a);return function(g,u,C){function ba(a){a=a.hasAttribute("ng-animate-ref")?[a]:a.querySelectorAll("[ng-animate-ref]");var b=[];w(a,function(a){var c=a.getAttribute("ng-animate-ref");c&&c.length&&b.push(a)});return b}function s(a){var b=[],c={};w(a,function(a,e){var d=G(a.element),k=0<=["enter","move"].indexOf(a.event),d=a.structural?ba(d):[];if(d.length){var f=k?"to":"from";w(d,function(a){var b=a.getAttribute("ng-animate-ref");
28 | c[b]=c[b]||{};c[b][f]={animationID:e,element:L(a)}})}else b.push(a)});var e={},d={};w(c,function(c,k){var f=c.from,z=c.to;if(f&&z){var n=a[f.animationID],p=a[z.animationID],h=f.animationID.toString();if(!d[h]){var B=d[h]={structural:!0,beforeStart:function(){n.beforeStart();p.beforeStart()},close:function(){n.close();p.close()},classes:y(n.classes,p.classes),from:n,to:p,anchors:[]};B.classes.length?b.push(B):(b.push(n),b.push(p))}d[h].anchors.push({out:f.element,"in":z.element})}else f=f?f.animationID:
29 | z.animationID,z=f.toString(),e[z]||(e[z]=!0,b.push(a[f]))});return b}function y(a,b){a=a.split(" ");b=b.split(" ");for(var c=[],e=0;ev.expectedEndTime)?A.cancel(v.timer):g.push(k)}m&&(s=A(c,s,!1),g[0]={timer:s,expectedEndTime:p},g.push(k),a.data("$$animateCss",g));a.on(n.join(" "),d);e.to&&(e.cleanupStyles&&Fa(u,l,Object.keys(e.to)),Aa(a,e))}}function c(){var b=a.data("$$animateCss");if(b){for(var e=1;e=O&&b>=K&&(J=!0,k())}if(!x)if(l.parentNode){var h,n=[],p=function(a){if(J)ca&&a&&(ca=!1,k());else if(ca=!a,F.animationDuration)if(a=pa(l,ca),ca)y.push(a);else{var b=y,c=b.indexOf(a);0<=a&&b.splice(c,1)}},g=0<$&&(F.transitionDuration&&0===S.transitionDuration||F.animationDuration&&0===S.animationDuration)&&Math.max(S.animationDelay,S.transitionDelay);g?A(b,Math.floor(g*$*1E3),!1):b();L.resume=function(){p(!0)};L.pause=function(){p(!1)}}else k()}var e=Ga(c),u={},l=G(a);if(!l||!l.parentNode||!t.enabled())return s();
39 | var e=ma(e),y=[],v=a.attr("class"),D=Ka(e),x,ca,J,m,L,H,O,K,R;if(0===e.duration||!h.animations&&!h.transitions)return s();var ga=e.event&&Z(e.event)?e.event.join(" "):e.event,V="",Q="";ga&&e.structural?V=U(ga,"ng-",!0):ga&&(V=ga);e.addClass&&(Q+=U(e.addClass,"-add"));e.removeClass&&(Q.length&&(Q+=" "),Q+=U(e.removeClass,"-remove"));e.applyClassesEarly&&Q.length&&ba(a,e);var da=[V,Q].join(" ").trim(),ka=v+" "+da,aa=U(da,"-active"),v=D.to&&0');
69 |
70 | // Set the OpenWeatherMap API URls (via a http to https proxy)
71 | _apiUrlCurrent = prepareUri('https://jsonp.afeld.me/?url=' + 'http://api.openweathermap.org/data/2.5/weather?lat=' + round(vm.lat) + '&lon=' + round(vm.lon) + '&APPID=' + _apiKey + '&callback=JSON_CALLBACK');
72 | _apiUrlDaily = prepareUri('https://jsonp.afeld.me/?url=' + 'http://api.openweathermap.org/data/2.5/forecast/daily?lat=' + round(vm.lat) + '&lon=' + round(vm.lon) + '&cnt=1&APPID=' + _apiKey + '&callback=JSON_CALLBACK');
73 |
74 | // Once we have the location request the weather data
75 | vm.weather.current();
76 | vm.weather.daily();
77 | };
78 |
79 | function error(err) {
80 | $log.error("navigator.geolocation error: ", err);
81 | };
82 | };
83 |
84 | vm.weather = {
85 | current: function() {
86 | // Do a JSONP AJAX request to get current weather data, use caching for performance
87 | $http.jsonp(_apiUrlCurrent, {cache: true})
88 | .success(function(data) {
89 |
90 | // $log.debug("Current weather data: %O", data);
91 | vm.dataCurrent = data;
92 |
93 | // Set search/location input to show current location
94 | vm.searchBox = vm.dataCurrent.name + ', ' + vm.dataCurrent.sys.country;
95 |
96 | // Show relevant icon
97 | vm.showIcon();
98 | })
99 | .error(function(err) {
100 | $log.error("$http error: ", err);
101 | });
102 | },
103 | daily: function() {
104 | // Do a JSONP AJAX request to get daily weather data, use caching for performance
105 | $http.jsonp(_apiUrlDaily, { cache: true })
106 | .success(function(data) {
107 | // $log.debug("Daily weather data: ", data);
108 | vm.dataDaily = data;
109 | })
110 | .error(function(err) {
111 | $log.error("$http error: ", err);
112 | });
113 | }
114 | };
115 |
116 | vm.showIcon = function() {
117 | // Choose the cosponsoring icon font letter to the correct weather type
118 | switch (vm.dataCurrent.weather[0].icon) {
119 | case '01d':
120 | // Clear sky, day
121 | vm.icon = 'B';
122 | vm.changeColour('orange');
123 | break;
124 | case '01n':
125 | // Clear sky, night
126 | vm.icon = 'C';
127 | vm.changeColour('purple');
128 | break;
129 | case '02d':
130 | // Few clouds, day
131 | vm.icon = 'H';
132 | vm.changeColour('orange');
133 | break;
134 | case '02n':
135 | // Few clouds, night
136 | vm.icon = 'I';
137 | vm.changeColour('purple');
138 | break;
139 | case '03d':
140 | case '03n':
141 | // Scattered clouds
142 | vm.icon = 'N';
143 | vm.changeColour('green');
144 | break;
145 | case '04d':
146 | case '04n':
147 | // Broken clouds
148 | vm.icon = 'Y';
149 | vm.changeColour('green');
150 | break;
151 | case '09d':
152 | case '09n':
153 | // Shower rain
154 | vm.icon = 'R';
155 | vm.changeColour('blue');
156 | break;
157 | case '10d':
158 | case '10n':
159 | // Rain
160 | vm.icon = 'Q';
161 | vm.changeColour('blue');
162 | break;
163 | case '11d':
164 | case '11n':
165 | // Thunderstorm
166 | vm.icon = 'O';
167 | vm.changeColour('blue');
168 | break;
169 | case '13d':
170 | case '13n':
171 | // Snow
172 | vm.icon = 'W';
173 | vm.changeColour('grey');
174 | break;
175 | case '50d':
176 | case '50n':
177 | // Mist
178 | vm.icon = 'M';
179 | vm.changeColour('grey');
180 | break;
181 | default:
182 | // Unsure of weather
183 | vm.icon = ')';
184 | vm.changeColour('grey');
185 | };
186 | };
187 |
188 | vm.changeColour = function (color) {
189 | // Change the colour of the .card CSS cl
190 | // Declare the controllers public APIass
191 | vm.cardColour = color;
192 | };
193 |
194 | vm.searchClick = function() {
195 | // Clear the contents of the search box but save the previous contents
196 | _currentLocation = vm.searchBox;
197 | vm.searchBox = '';
198 | };
199 |
200 | vm.searchBlur = function() {
201 | // If the user didn't type a location restore the previous one
202 | if (vm.searchBox === '') {
203 | vm.searchBox = _currentLocation;
204 | };
205 | };
206 |
207 | vm.randomCity = function () {
208 | // Bunch of cities for random selection
209 | var cities = [
210 | 'Sydney, AU',
211 | 'Melbourne, AU',
212 | 'Tokyo',
213 | 'Osaka',
214 | 'Seoul',
215 | 'Hong Kong',
216 | 'London',
217 | 'Amsterdam',
218 | 'Berlin',
219 | 'Paris',
220 | 'Barcelona',
221 | 'New York',
222 | 'Dubai',
223 | 'Antarctica'
224 | ];
225 |
226 | // Use a loop to prevent the same city as current (confusing for UX)
227 | do {
228 | // Generate a random number up to the array size
229 | var random = Math.floor(Math.random() * cities.length);
230 | } while (cities[random] === vm.searchBox);
231 |
232 | // Run initial search to populate the view
233 | vm.search(cities[random]);
234 | };
235 | }]);
236 | })();
237 |
--------------------------------------------------------------------------------
/app/scss/_animations.scss:
--------------------------------------------------------------------------------
1 | //////////////
2 | // pullDown //
3 | //////////////
4 | .pull-down{
5 | animation-name: pullDown;
6 | -webkit-animation-name: pullDown;
7 |
8 | animation-duration: $speed;
9 | -webkit-animation-duration: $speed;
10 |
11 | animation-timing-function: ease-out;
12 | -webkit-animation-timing-function: ease-out;
13 |
14 | transform-origin: 50% 0%;
15 | -ms-transform-origin: 50% 0%;
16 | -webkit-transform-origin: 50% 0%;
17 | }
18 |
19 | @keyframes pullDown {
20 | 0% {
21 | transform: scaleY(0.1);
22 | }
23 | 100% {
24 | transform: scaleY(1);
25 | }
26 | }
27 |
28 | @-webkit-keyframes pullDown {
29 | 0% {
30 | -webkit-transform: scaleY(0.1);
31 | }
32 | 100% {
33 | -webkit-transform: scaleY(1);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/scss/_fonts.scss:
--------------------------------------------------------------------------------
1 | ///////////////
2 | // Variables //
3 | ///////////////
4 | $FontPath: "../fonts" !default;
5 |
6 | ////////////
7 | // Mixins //
8 | ////////////
9 | @mixin fontdef-woff($FontPath, $FontName) {
10 | src: url('#{$FontPath}/#{$FontName}.eot');
11 | src: url('#{$FontPath}/#{$FontName}.eot?#iefix') format('eot'),
12 | url('#{$FontPath}/#{$FontName}.woff2') format('woff2'),
13 | url('#{$FontPath}/#{$FontName}.woff') format('woff'),
14 | url('#{$FontPath}/#{$FontName}.ttf') format('truetype');
15 | }
16 |
17 | /////////////////////////////
18 | // Roboto font declaration //
19 | /////////////////////////////
20 | @font-face {
21 | font-family: Roboto;
22 | @include fontdef-woff($FontPath, "roboto-light");
23 | font-weight: 300;
24 | font-style: normal;
25 | }
26 |
27 | ///////////////////////////
28 | // Icon font declaration //
29 | ///////////////////////////
30 | @font-face {
31 | font-family: Meteocons;
32 | @include fontdef-woff($FontPath, "meteocons");
33 | font-weight: normal;
34 | font-style: normal;
35 |
36 | text-rendering: auto; // Cross browser compatibility
37 | -moz-osx-font-smoothing: grayscale;
38 | transform: translate(0, 0); // Ensures no half-pixel rendering in firefox
39 | }
40 |
--------------------------------------------------------------------------------
/app/scss/_normalize.scss:
--------------------------------------------------------------------------------
1 | /* normalize.css v3.0.2 | MIT License | git.io/normalize */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS text size adjust after orientation change, without disabling
6 | * user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | -ms-text-size-adjust: 100%; /* 2 */
12 | -webkit-text-size-adjust: 100%; /* 2 */
13 | }
14 |
15 | /**
16 | * Remove default margin.
17 | */
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | /* HTML5 display definitions
24 | ========================================================================== */
25 |
26 | /**
27 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | * and Firefox.
30 | * Correct `block` display not defined for `main` in IE 11.
31 | */
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | /**
50 | * 1. Correct `inline-block` display not defined in IE 8/9.
51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | */
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; /* 1 */
59 | vertical-align: baseline; /* 2 */
60 | }
61 |
62 | /**
63 | * Prevent modern browsers from displaying `audio` without controls.
64 | * Remove excess height in iOS 5 devices.
65 | */
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | /**
73 | * Address `[hidden]` styling not present in IE 8/9/10.
74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
75 | */
76 |
77 | [hidden],
78 | template {
79 | display: none;
80 | }
81 |
82 | /* Links
83 | ========================================================================== */
84 |
85 | /**
86 | * Remove the gray background color from active links in IE 10.
87 | */
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | /**
94 | * Improve readability when focused and also mouse hovered in all browsers.
95 | */
96 |
97 | a:active,
98 | a:hover {
99 | outline: 0;
100 | }
101 |
102 | /* Text-level semantics
103 | ========================================================================== */
104 |
105 | /**
106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
107 | */
108 |
109 | abbr[title] {
110 | border-bottom: 1px dotted;
111 | }
112 |
113 | /**
114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
115 | */
116 |
117 | b,
118 | strong {
119 | font-weight: bold;
120 | }
121 |
122 | /**
123 | * Address styling not present in Safari and Chrome.
124 | */
125 |
126 | dfn {
127 | font-style: italic;
128 | }
129 |
130 | /**
131 | * Address variable `h1` font-size and margin within `section` and `article`
132 | * contexts in Firefox 4+, Safari, and Chrome.
133 | */
134 |
135 | h1 {
136 | font-size: 2em;
137 | margin: 0.67em 0;
138 | }
139 |
140 | /**
141 | * Address styling not present in IE 8/9.
142 | */
143 |
144 | mark {
145 | background: #ff0;
146 | color: #000;
147 | }
148 |
149 | /**
150 | * Address inconsistent and variable font size in all browsers.
151 | */
152 |
153 | small {
154 | font-size: 80%;
155 | }
156 |
157 | /**
158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
159 | */
160 |
161 | sub,
162 | sup {
163 | font-size: 75%;
164 | line-height: 0;
165 | position: relative;
166 | vertical-align: baseline;
167 | }
168 |
169 | sup {
170 | top: -0.5em;
171 | }
172 |
173 | sub {
174 | bottom: -0.25em;
175 | }
176 |
177 | /* Embedded content
178 | ========================================================================== */
179 |
180 | /**
181 | * Remove border when inside `a` element in IE 8/9/10.
182 | */
183 |
184 | img {
185 | border: 0;
186 | }
187 |
188 | /**
189 | * Correct overflow not hidden in IE 9/10/11.
190 | */
191 |
192 | svg:not(:root) {
193 | overflow: hidden;
194 | }
195 |
196 | /* Grouping content
197 | ========================================================================== */
198 |
199 | /**
200 | * Address margin not present in IE 8/9 and Safari.
201 | */
202 |
203 | figure {
204 | margin: 1em 40px;
205 | }
206 |
207 | /**
208 | * Address differences between Firefox and other browsers.
209 | */
210 |
211 | hr {
212 | -moz-box-sizing: content-box;
213 | box-sizing: content-box;
214 | height: 0;
215 | }
216 |
217 | /**
218 | * Contain overflow in all browsers.
219 | */
220 |
221 | pre {
222 | overflow: auto;
223 | }
224 |
225 | /**
226 | * Address odd `em`-unit font size rendering in all browsers.
227 | */
228 |
229 | code,
230 | kbd,
231 | pre,
232 | samp {
233 | font-family: monospace, monospace;
234 | font-size: 1em;
235 | }
236 |
237 | /* Forms
238 | ========================================================================== */
239 |
240 | /**
241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
242 | * styling of `select`, unless a `border` property is set.
243 | */
244 |
245 | /**
246 | * 1. Correct color not being inherited.
247 | * Known issue: affects color of disabled elements.
248 | * 2. Correct font properties not being inherited.
249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
250 | */
251 |
252 | button,
253 | input,
254 | optgroup,
255 | select,
256 | textarea {
257 | color: inherit; /* 1 */
258 | font: inherit; /* 2 */
259 | margin: 0; /* 3 */
260 | }
261 |
262 | /**
263 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
264 | */
265 |
266 | button {
267 | overflow: visible;
268 | }
269 |
270 | /**
271 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
272 | * All other form control elements do not inherit `text-transform` values.
273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
274 | * Correct `select` style inheritance in Firefox.
275 | */
276 |
277 | button,
278 | select {
279 | text-transform: none;
280 | }
281 |
282 | /**
283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
284 | * and `video` controls.
285 | * 2. Correct inability to style clickable `input` types in iOS.
286 | * 3. Improve usability and consistency of cursor style between image-type
287 | * `input` and others.
288 | */
289 |
290 | button,
291 | html input[type="button"], /* 1 */
292 | input[type="reset"],
293 | input[type="submit"] {
294 | -webkit-appearance: button; /* 2 */
295 | cursor: pointer; /* 3 */
296 | }
297 |
298 | /**
299 | * Re-set default cursor for disabled elements.
300 | */
301 |
302 | button[disabled],
303 | html input[disabled] {
304 | cursor: default;
305 | }
306 |
307 | /**
308 | * Remove inner padding and border in Firefox 4+.
309 | */
310 |
311 | button::-moz-focus-inner,
312 | input::-moz-focus-inner {
313 | border: 0;
314 | padding: 0;
315 | }
316 |
317 | /**
318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
319 | * the UA stylesheet.
320 | */
321 |
322 | input {
323 | line-height: normal;
324 | }
325 |
326 | /**
327 | * It's recommended that you don't attempt to style these elements.
328 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
329 | *
330 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
331 | * 2. Remove excess padding in IE 8/9/10.
332 | */
333 |
334 | input[type="checkbox"],
335 | input[type="radio"] {
336 | box-sizing: border-box; /* 1 */
337 | padding: 0; /* 2 */
338 | }
339 |
340 | /**
341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
342 | * `font-size` values of the `input`, it causes the cursor style of the
343 | * decrement button to change from `default` to `text`.
344 | */
345 |
346 | input[type="number"]::-webkit-inner-spin-button,
347 | input[type="number"]::-webkit-outer-spin-button {
348 | height: auto;
349 | }
350 |
351 | /**
352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
354 | * (include `-moz` to future-proof).
355 | */
356 |
357 | input[type="search"] {
358 | -webkit-appearance: textfield; /* 1 */
359 | -moz-box-sizing: content-box;
360 | -webkit-box-sizing: content-box; /* 2 */
361 | box-sizing: content-box;
362 | }
363 |
364 | /**
365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
366 | * Safari (but not Chrome) clips the cancel button when the search input has
367 | * padding (and `textfield` appearance).
368 | */
369 |
370 | input[type="search"]::-webkit-search-cancel-button,
371 | input[type="search"]::-webkit-search-decoration {
372 | -webkit-appearance: none;
373 | }
374 |
375 | /**
376 | * Define consistent border, margin, and padding.
377 | */
378 |
379 | fieldset {
380 | border: 1px solid #c0c0c0;
381 | margin: 0 2px;
382 | padding: 0.35em 0.625em 0.75em;
383 | }
384 |
385 | /**
386 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
388 | */
389 |
390 | legend {
391 | border: 0; /* 1 */
392 | padding: 0; /* 2 */
393 | }
394 |
395 | /**
396 | * Remove default vertical scrollbar in IE 8/9/10/11.
397 | */
398 |
399 | textarea {
400 | overflow: auto;
401 | }
402 |
403 | /**
404 | * Don't inherit the `font-weight` (applied by a rule above).
405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
406 | */
407 |
408 | optgroup {
409 | font-weight: bold;
410 | }
411 |
412 | /* Tables
413 | ========================================================================== */
414 |
415 | /**
416 | * Remove most spacing between table cells.
417 | */
418 |
419 | table {
420 | border-collapse: collapse;
421 | border-spacing: 0;
422 | }
423 |
424 | td,
425 | th {
426 | padding: 0;
427 | }
428 |
--------------------------------------------------------------------------------
/app/scss/theme.scss:
--------------------------------------------------------------------------------
1 | ///////////////
2 | // Variables //
3 | ///////////////
4 | // Fonts
5 | $font-family: Roboto, "Roboto", sans-serif !default;
6 | $font-weight: 300 !default;
7 |
8 | // Colours
9 | $white: #FFF;
10 | $black: #212121;
11 | $grey-bg: #E0E0E0;
12 | $orange: #F57C00; // Orange 700
13 | $orange-light: #FF9800; // Orange 500
14 | $blue: #1976D2; // Blue 700
15 | $blue-light: #2196F3; // Blue 500
16 | $purple: #512DA8; // Purple 700
17 | $purple-light: #673AB7; // Purple 500
18 | $green: #388E3C; // Green 700
19 | $green-light: #4CAF50; // Green 500
20 | $grey: #455A64; // Grey 700
21 | $grey-light: #607D8B; // Grey 500
22 |
23 | // Misc
24 | $radius: 5px !default;
25 | $speed: 0.7s !default;
26 |
27 |
28 | /////////////
29 | // Imports //
30 | /////////////
31 | @import 'normalize';
32 | @import 'fonts';
33 | @import 'animations';
34 |
35 |
36 | ///////////////////////
37 | // AngularJS Helpers //
38 | ///////////////////////
39 | [ng-cloak] {
40 | display: none !important;
41 | }
42 |
43 |
44 | ///////////////////
45 | // Global Styles //
46 | ///////////////////
47 | html {
48 | background-color: $grey-bg;
49 | color: $black;
50 | font: $font-weight 1.2em $font-family;
51 | margin-left: 1em;
52 | margin-right: 1em;
53 | }
54 |
55 | header { margin-bottom: 2em; }
56 |
57 | h1 {
58 | font-size: 2.3em;
59 | font-weight: $font-weight;
60 | }
61 |
62 | .lead { font-size: 1.3em; }
63 |
64 | button {
65 | background-color: transparent;
66 | font-family: $font-family;
67 | font-size: 1.2em;
68 | font-weight: $font-weight;
69 | border: none;
70 | padding: 0.4em;
71 | opacity: 0.6;
72 |
73 | &:focus {
74 | outline:none;
75 | }
76 | }
77 |
78 | .btn-random {
79 | box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.2);
80 | border-radius: $radius;
81 |
82 | &:hover {
83 | box-shadow: 1px 2px 8px rgba(0, 0, 0, 0.4);
84 | }
85 |
86 | & > .icon {
87 | vertical-align: -2px;
88 | }
89 | }
90 |
91 | .btn-locate:hover {
92 | opacity: 1;
93 | }
94 |
95 | .card {
96 | // Animate background colour changes
97 | transition: background-color $speed ease;
98 |
99 | color: $white;
100 | padding: 1em;
101 | border-radius: $radius;
102 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
103 | max-width: 30em;
104 | position: relative;
105 | z-index: 2;
106 |
107 | &.orange {
108 | background-color: $orange-light;
109 |
110 | .location-panel {
111 | background-color: $orange;
112 | }
113 |
114 | & + .card {
115 | color: $orange;
116 | }
117 | }
118 |
119 | &.blue {
120 | background-color: $blue-light;
121 |
122 | .location-panel {
123 | background-color: $blue;
124 | }
125 |
126 | & + .card {
127 | color: $blue;
128 | }
129 | }
130 |
131 | &.purple {
132 | background-color: $purple-light;
133 |
134 | .location-panel {
135 | background-color: $purple;
136 | }
137 |
138 | & + .card {
139 | color: $purple;
140 | }
141 | }
142 |
143 | &.grey {
144 | background-color: $grey-light;
145 |
146 | .location-panel {
147 | background-color: $grey;
148 | }
149 |
150 | & + .card {
151 | color: $grey;
152 | }
153 | }
154 |
155 | &.green {
156 | background-color: $green-light;
157 |
158 | .location-panel {
159 | background-color: $green;
160 | }
161 |
162 | & + .card {
163 | color: $green;
164 | }
165 | }
166 |
167 | & + .card {
168 | background-color: $white;
169 | padding-top: 0.6em;
170 | margin-top: -0.5em;
171 | z-index: 1;
172 |
173 | & > p {
174 | // Animate text colour changes
175 | transition: color $speed ease;
176 | }
177 | }
178 | }
179 |
180 | .top { padding: 0.3em 1em 1em; }
181 |
182 | .temp-now {
183 | font-size: 6em;
184 | margin: 0;
185 |
186 | & > span {
187 | font-size: 0.6em;
188 | vertical-align: 0.48em;
189 | }
190 | }
191 |
192 | .weather-now {
193 | font-size: 2em;
194 | text-transform: capitalize;
195 | margin: 0.1em 0 0.2em;
196 | }
197 |
198 | .highlow {
199 | font-size: 1.2em;
200 | margin: 0 0 0.2em;
201 | }
202 |
203 | .location-panel {
204 | // Animate background colour changes
205 | transition: background-color $speed ease;
206 | font-size: 1.1em;
207 | border-radius: $radius;
208 | }
209 |
210 | .search { display: inline; }
211 |
212 | .location {
213 | // Inline SVG: Search icon
214 | background: url('data:image/svg+xml;utf8,') 0.4em 50% no-repeat;
215 | color: $white;
216 | font-family: $font-family;
217 | font-size: 1.4em;
218 | font-weight: $font-weight;
219 | background-color: transparent;
220 | border: none;
221 | padding: 0.4em 0 0.4em 2em;
222 | width: 11.8em;
223 |
224 | &:focus {
225 | outline:none;
226 | }
227 | }
228 |
229 | .icon {
230 | font-family: "Meteocons", sans-serif;
231 |
232 | &.big {
233 | font-size: 8em;
234 | float: right;
235 | }
236 | }
237 |
238 | .or {
239 | vertical-align: 3px;
240 | opacity: 0.4;
241 | }
242 |
243 | .bottom {
244 | margin-top: 0.9em;
245 |
246 | .icon {
247 | font-size: 4em;
248 | margin-right: 0.15em;
249 | vertical-align: bottom;
250 | position: relative;
251 | top: 0.15em;
252 | float: left;
253 | }
254 |
255 | &:after {
256 | // CLearfix hack
257 | visibility: hidden;
258 | display: block;
259 | content: "";
260 | clear: both;
261 | height: 0;
262 | }
263 | }
264 |
265 | .left {
266 | float: left;
267 | width: 49.8%;
268 | }
269 |
270 | .right {
271 | float: right;
272 | width: 49.8%;
273 | }
274 |
275 | .info {
276 | font-size: 1.2em;
277 | vertical-align: middle;
278 | }
279 |
280 | footer {
281 | text-align: center;
282 | margin: 3.5em 0 2em;
283 |
284 | & > a {
285 | color: darken($grey-bg, 15%);
286 | text-decoration: none;
287 | }
288 | }
289 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "simple-weather-app",
3 | "version": "1.0.0",
4 | "description": "A simple experiment using AngularJS, the OpenWeatherMap API, and CSS3.",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "http-server -a 127.0.0.1 -p 8880 ./app",
8 | "watch": "node-sass -w --output-style compressed app/scss/theme.scss -o app/css/",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/MaxMilton/Simple-Weather-App.git"
14 | },
15 | "author": "Max Milton ",
16 | "license": "ISC",
17 | "bugs": {
18 | "url": "https://github.com/MaxMilton/Simple-Weather-App/issues"
19 | },
20 | "homepage": "https://github.com/MaxMilton/Simple-Weather-App#readme",
21 | "devDependencies": {
22 | "http-server": "^0.8.5",
23 | "node-sass": "^3.4.2"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------