├── nodemon.json ├── .flowconfig ├── .babelrc ├── server ├── index.js └── server.js ├── client ├── assets │ ├── images │ │ ├── cap.png │ │ ├── react.png │ │ ├── sucker.png │ │ ├── t-shirt.png │ │ └── ipad-mini.png │ └── css │ │ ├── main.css │ │ └── uikit.almost-flat.min.css └── index.js ├── common ├── components │ ├── NotFound.jsx │ ├── ProductItemContainer.jsx │ ├── ProductsList.jsx │ ├── TodoApp.jsx │ ├── DevTools.jsx │ ├── CartContainer.jsx │ ├── ProductItem.jsx │ ├── Cart.jsx │ ├── ProductsContainer.jsx │ └── ProductDetail.jsx ├── reducers │ ├── index.js │ ├── CartReducer.js │ └── ProductReducer.js ├── utils │ ├── createReducer.js │ ├── fetchComponentData.js │ ├── configureStore.js │ └── WebAPIUtils.js ├── api │ ├── products.js │ └── shopdb.js ├── constants │ ├── ActionTypes.js │ └── Types.js ├── routes │ └── routing.js ├── middlewares │ └── PromiseMiddleware.js └── actions │ └── ShopActions.js ├── .editorconfig ├── .jscsrc ├── .gitignore ├── CHANGELOG.md ├── webpack.config.js ├── package.json └── README.md /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "execMap": { 3 | "js": "babel-node --stage 0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/.* 3 | 4 | [include] 5 | 6 | [libs] 7 | 8 | [options] 9 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: [ "es2015", "stage-0", "react"], 3 | plugins: [ "transform-decorators-legacy" ] 4 | } 5 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | // use babel-register to precompile ES6 syntax 2 | require('babel-register') 3 | require('./server') 4 | -------------------------------------------------------------------------------- /client/assets/images/cap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coodoo/react-redux-isomorphic-example/HEAD/client/assets/images/cap.png -------------------------------------------------------------------------------- /client/assets/images/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coodoo/react-redux-isomorphic-example/HEAD/client/assets/images/react.png -------------------------------------------------------------------------------- /client/assets/images/sucker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coodoo/react-redux-isomorphic-example/HEAD/client/assets/images/sucker.png -------------------------------------------------------------------------------- /client/assets/images/t-shirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coodoo/react-redux-isomorphic-example/HEAD/client/assets/images/t-shirt.png -------------------------------------------------------------------------------- /client/assets/images/ipad-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coodoo/react-redux-isomorphic-example/HEAD/client/assets/images/ipad-mini.png -------------------------------------------------------------------------------- /common/components/NotFound.jsx: -------------------------------------------------------------------------------- 1 | // react 0.14 - stateless function component 2 | // https://goo.gl/E3kDlv 3 | var React = require('react'); 4 | export default props => { 5 | return
404 Page Not Found
; 6 | } 7 | -------------------------------------------------------------------------------- /common/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import products from "./ProductReducer"; 4 | import carts from "./CartReducer"; 5 | 6 | export default combineReducers({ 7 | products, 8 | carts, 9 | }); 10 | -------------------------------------------------------------------------------- /client/assets/css/main.css: -------------------------------------------------------------------------------- 1 | .shop-wrap { 2 | max-width: 900px; 3 | width: 60%; 4 | margin: 20px 25px; 5 | } 6 | .cart { 7 | position: fixed; 8 | left: 25px; 9 | /*top: 195px;*/ 10 | top: 755px; 11 | width: 455px; 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = tab 3 | indent_size = 4 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [package.json] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [*.md] 14 | trim_trailing_whitespace = true 15 | -------------------------------------------------------------------------------- /common/utils/createReducer.js: -------------------------------------------------------------------------------- 1 | export default function createReducer (initialState, actionHandlers) { 2 | return (state = initialState, action) => { 3 | const reduceFn = actionHandlers[action.type]; 4 | if (reduceFn) { 5 | return reduceFn(state, action); 6 | } else { 7 | return state; 8 | } 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset":"airbnb", 3 | "fileExtensions": [ 4 | ".js", 5 | ".jsx" 6 | ], 7 | "excludeFiles": [ 8 | "node_modules/**", 9 | "Dropbox/**", 10 | "lib/**", 11 | ".jscsrc/**", 12 | "data/**", 13 | "package/**", 14 | "shared/**" 15 | ], 16 | "validateIndentation": "\t", 17 | "requireSpacesInsideParentheses": "all" 18 | } 19 | -------------------------------------------------------------------------------- /common/components/ProductItemContainer.jsx: -------------------------------------------------------------------------------- 1 | var React = require( 'react' ); 2 | var ProductItem = require( './ProductItem.jsx' ); 3 | 4 | export default React.createClass( { 5 | render: function() { 6 | return ( 7 | 9 | ); 10 | } 11 | } ); 12 | 13 | -------------------------------------------------------------------------------- /common/components/ProductsList.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | export default React.createClass({ 4 | propTypes: { 5 | title: React.PropTypes.string.isRequired 6 | }, 7 | 8 | render: function () { 9 | return ( 10 |
11 |

{this.props.title}

12 |
{this.props.children}
13 |
14 | ); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /common/api/products.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | id: 'a1', 4 | image: "/assets/images/ipad-mini.png", 5 | inventory: 2, 6 | quantity: 0, 7 | price: 500.01, 8 | title: "iPad 4 Mini" 9 | }, 10 | { 11 | id: 'a2', 12 | image: "/assets/images/t-shirt.png", 13 | inventory: 10, 14 | quantity: 0, 15 | price: 10.99, 16 | title: "H&M T-Shirt White" 17 | }, 18 | { 19 | id: 'a3', 20 | image: "/assets/images/sucker.png", 21 | inventory: 5, 22 | quantity: 0, 23 | price: 19.99, 24 | title: "Charli XCX - Sucker CD" 25 | } 26 | 27 | ] 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 27 | node_modules 28 | 29 | # Build Directory 30 | build 31 | -------------------------------------------------------------------------------- /common/components/TodoApp.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import DevTools from './DevTools'; 3 | 4 | if ( 'undefined' !== typeof window ) { 5 | require( '../../client/assets/css/main.css' ); 6 | } 7 | 8 | export default class TodoApp extends Component { 9 | 10 | static contextTypes = { 11 | store: React.PropTypes.object.isRequired, 12 | }; 13 | 14 | render() { 15 | 16 | let tool = ( 'undefined' !== typeof window && true == window.$REDUX_DEVTOOL ) ? : null; 17 | 18 | let nodes = ( 19 |
20 | {this.props.main} 21 | {this.props.cart} 22 | {tool} 23 |
24 | ) 25 | 26 | return nodes; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /common/constants/ActionTypes.js: -------------------------------------------------------------------------------- 1 | import constants from 'flux-constants'; 2 | 3 | // constants plugin will return object in this format: { ROUTE_CHANGE: "ROUTE_CHANGE" } 4 | // so I don't have to type out duped strings 5 | export default constants([ 6 | 7 | "ADD_TO_CART_REQUEST", 8 | "ADD_TO_CART_SUCCESS", 9 | "ADD_TO_CART_ERROR", 10 | 11 | "CART_CHECKOUT_REQUEST", 12 | "CART_CHECKOUT_SUCCESS", 13 | "CART_CHECKOUT_ERROR", 14 | 15 | "LOAD_ALL_PRODUCTS", 16 | "LOAD_ONE_PRODUCT", 17 | 18 | "READ_ALL_PRODUCTS_REQUEST", 19 | "READ_ALL_PRODUCTS_SUCCESS", 20 | "READ_ALL_PRODUCTS_ERROR", 21 | 22 | "READ_ONE_PRODUCT_REQUEST", 23 | "READ_ONE_PRODUCT_SUCCESS", 24 | "READ_ONE_PRODUCT_ERROR", 25 | ]); 26 | -------------------------------------------------------------------------------- /common/routes/routing.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route } from 'react-router' 3 | import TodoApp from '../components/TodoApp'; 4 | import ProductsContainer from '../components/ProductsContainer'; 5 | import ProductDetail from '../components/ProductDetail'; 6 | import CartContainer from '../components/CartContainer'; 7 | import NotFound from '../components/NotFound'; 8 | 9 | export default ( 10 | 11 | 12 | 13 | 15 | 16 | 18 | 19 | 21 | 22 | 23 | ) 24 | -------------------------------------------------------------------------------- /common/components/DevTools.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | // Exported from redux-devtools 4 | import { createDevTools } from 'redux-devtools'; 5 | 6 | // Monitors are separate packages, and you can make a custom one 7 | import LogMonitor from 'redux-devtools-log-monitor'; 8 | import DockMonitor from 'redux-devtools-dock-monitor'; 9 | 10 | // createDevTools takes a monitor and produces a DevTools component 11 | const DevTools = createDevTools( 12 | // Monitors are individually adjustable with props. 13 | // Consult their repositories to learn about those props. 14 | // Here, we put LogMonitor inside a DockMonitor. 15 | 17 | 18 | 19 | ); 20 | 21 | export default DevTools; 22 | -------------------------------------------------------------------------------- /common/utils/fetchComponentData.js: -------------------------------------------------------------------------------- 1 | // for use on server to guarantee data was fetched before rendering pages for user 2 | export default function fetchComponentData(dispatch, components, params) { 3 | 4 | const needs = components.reduce( (prev, current) => { 5 | 6 | return Object.keys(current).reduce( (acc, key) => { 7 | return current[key].hasOwnProperty('needs') ? current[key].needs.concat(acc) : acc 8 | }, prev) 9 | 10 | }, []); 11 | 12 | const promises = needs.map(need => dispatch(need(params))); 13 | 14 | return Promise.all(promises); 15 | } 16 | 17 | // for client side use, let each component trigger it's fetching data logics 18 | // might as well to add in dupe check to avoid fetching when data is already there 19 | export function fetchNeeds( needs, props ){ 20 | const { params, dispatch } = props; 21 | needs.map( need => dispatch(need(params)) ) 22 | } 23 | -------------------------------------------------------------------------------- /common/components/CartContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Cart from './Cart.jsx'; 3 | import { connect } from 'react-redux'; 4 | import { bindActionCreators } from 'redux'; 5 | import * as ShopActions from '../actions/ShopActions'; 6 | 7 | class CartContainer extends Component { 8 | 9 | constructor(props, context){ 10 | super(props, context); 11 | this.actions = bindActionCreators(ShopActions, props.dispatch); 12 | } 13 | 14 | onCheckoutClicked() { 15 | 16 | if ( this.props.carts.cartsById.size == 0 ) { 17 | return; 18 | } 19 | 20 | this.actions.cartCheckout(this.props.carts.cartsById); 21 | } 22 | 23 | render() { 24 | 25 | return ( 26 | 30 | ); 31 | } 32 | 33 | } 34 | 35 | export default connect( (state, ownProps) => (state) )(CartContainer); 36 | -------------------------------------------------------------------------------- /common/middlewares/PromiseMiddleware.js: -------------------------------------------------------------------------------- 1 | export default function promiseMiddleware( objMethods ) { 2 | 3 | return (next) => (action) => { 4 | 5 | const { promise, types, ...rest } = action; 6 | 7 | // 假如傳來的 action 內沒有 promise 屬性,代表不需 async 處理,直接略過 8 | if (!promise) { 9 | // console.log( 'promiseMiddleware > 沒 promise > 不處理,且接丟給後手' ); 10 | return next(action); 11 | } 12 | 13 | // 這裏聰明的將外界傳入的變數,透過 destructuring 轉為常數 14 | // 因此 middleware 可適用於各種不同情境 15 | const [REQUEST, SUCCESS, ERROR] = types; 16 | 17 | // console.log( '建立 Const: ', REQUEST, SUCCESS, ERROR ); 18 | 19 | // 進行第一次的廣播,讓畫面立即更新,也就是 optimistic update 20 | next({ ...rest, type: REQUEST }); 21 | 22 | // 然後偵聽 WebAPI promise 操作結束,發出第二次廣播 23 | // 這次 type 改為 SUCCESS,因此 store 內知道要依 tid 更新 uid 24 | return promise.then( 25 | (result) => next({ ...rest, result, type: SUCCESS }), 26 | (error) => next({ ...rest, error, type: ERROR }) 27 | ); 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /common/constants/Types.js: -------------------------------------------------------------------------------- 1 | 2 | import Immutable from 'immutable'; 3 | 4 | // define ProductState shape inside redux state 5 | export const ProductState = Immutable.Record({ 6 | productsById: Immutable.Map(), 7 | total: '0', 8 | }) 9 | 10 | // define Product record shape 11 | export const ProductRecord = Immutable.Record({ 12 | id: null, 13 | image: "", 14 | inventory: 0, 15 | quantity: 0, 16 | price: 0, 17 | title: "", 18 | tid: null, // transaction id for optimistic update 19 | }) 20 | 21 | // define CartState shape inside redux state 22 | export const CartState = Immutable.Record({ 23 | cartsById: Immutable.List() 24 | }) 25 | 26 | 27 | export function convertToRecordMap( arr, Def ){ 28 | return arr.reduce( (acc, item) => acc.set( item.id, new Def(item) ), Immutable.Map() ); 29 | } 30 | 31 | export function convertMapToImmutable( map, Def ){ 32 | return Object.keys(map) 33 | .reduce( (acc, key) => { 34 | let item = map[key]; 35 | return acc.set( item.id, new Def(item) ); 36 | }, Immutable.Map() ); 37 | } 38 | -------------------------------------------------------------------------------- /common/components/ProductItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Link} from 'react-router'; 3 | 4 | var ProductItem = React.createClass( { 5 | propTypes: { 6 | product: React.PropTypes.shape( { 7 | image: React.PropTypes.string.isRequired, 8 | title: React.PropTypes.string.isRequired, 9 | price: React.PropTypes.number.isRequired, 10 | inventory: React.PropTypes.number.isRequired 11 | } ).isRequired, 12 | 13 | onAddToCartClicked: React.PropTypes.func.isRequired 14 | }, 15 | 16 | render: function() { 17 | var product = this.props.product; 18 | 19 | return ( 20 |
21 | 22 |

{product.title} - €{product.price}

23 |

inventory: {product.inventory}

24 | 30 |
31 |
32 |
33 | details 34 |
35 |
36 | ); 37 | } 38 | } ); 39 | 40 | module.exports = ProductItem; 41 | -------------------------------------------------------------------------------- /common/utils/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import promiseMiddleware from '../middlewares/PromiseMiddleware'; 3 | import createLogger from 'redux-logger'; 4 | import combinedReducers from '../reducers'; 5 | import DevTools from '../components/DevTools'; 6 | 7 | // toggle redux-devtool panel 8 | window.$REDUX_DEVTOOL = false; 9 | 10 | const logger = createLogger({ 11 | level: 'info', 12 | collapsed: true, 13 | // predicate: (getState, action) => action.type !== AUTH_REMOVE_TOKEN 14 | }); 15 | 16 | const enhancer = compose( 17 | applyMiddleware( promiseMiddleware, logger ), 18 | DevTools.instrument() // comment this out if you don't need redux-devtools 19 | ) 20 | 21 | export default function configureStore( initialState = undefined ) { 22 | 23 | // 重要:如果有 server rendering,就直接用預先埋好的資料而不用重撈了,省一趟 24 | const store = createStore( combinedReducers, initialState, enhancer); 25 | 26 | // module 是 webpack 包過一層時提供的,signature 如下: 27 | // function(module, exports, __webpack_require__) { 28 | if (module.hot) { 29 | // Enable Webpack hot module replacement for reducers 30 | module.hot.accept('../reducers', () => { 31 | const nextRootReducer = require('../reducers'); 32 | store.replaceReducer(nextRootReducer); 33 | }); 34 | } 35 | 36 | return store; 37 | } 38 | -------------------------------------------------------------------------------- /common/components/Cart.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var Product = React.createClass({ 4 | render: function () { 5 | return
{this.props.children}
; 6 | } 7 | }); 8 | 9 | export default React.createClass({ 10 | 11 | render: function () { 12 | let products = this.props.products.productsById; 13 | let carts = this.props.carts.cartsById; 14 | 15 | let hasProducts = carts.size > 0; 16 | 17 | let nodes = !hasProducts ? 18 |
Please add some products to cart.
: 19 | carts.map( pid => { 20 | let p = products.get(pid); 21 | return {p.title} - €{p.price} x {p.quantity} 22 | }) 23 | 24 | return ( 25 |
26 |
Your Cart
27 |
{nodes}
28 |
Total: €{this.props.products.total}
29 | 34 |
35 | ); 36 | }, 37 | }); 38 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { render } from 'react-dom'; 3 | import Immutable from 'immutable'; 4 | import { Router, Route, RouterContext, browserHistory } from 'react-router'; 5 | import { Provider } from 'react-redux'; 6 | import configureStore from '../common/utils/configureStore'; 7 | import { ProductState, ProductRecord, CartState, convertMapToImmutable } from '../common/constants/Types'; 8 | import routes from '../common/routes/routing'; 9 | 10 | let state = null; 11 | if ( window.$REDUX_STATE ) { 12 | 13 | // 解開 server 預先傳來的資料包,稍後會放入 store 成為 initState 14 | state = window.$REDUX_STATE; 15 | 16 | // begin marshalling data into Immutable types 17 | state.products = new ProductState( { 18 | $fetched: document.location.pathname == '/', 19 | productsById: convertMapToImmutable( state.products.productsById, ProductRecord ), 20 | total: state.products.total, 21 | } ); 22 | 23 | state.carts = new CartState( { 24 | cartsById: Immutable.List.of( ...state.carts.cartsById ), 25 | } ); 26 | 27 | // console.log( 'server-rendering state restored: ', state ); 28 | } 29 | 30 | const store = configureStore( state ) 31 | 32 | // 注意 是 react-redux 提供的元件,不屬於 react-router 33 | render( 34 | 35 | 36 | , 37 | document.querySelector( '.container' ) 38 | ); 39 | 40 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | - Apr 25, 2016 3 | 4 | history ^2.0.1 → ^2.1.0 5 | immutable ^3.7.5 → ^3.8.1 6 | react ^15.0.0-rc.2 → ^15.0.1 7 | react-dom ^15.0.0-rc.2 → ^15.0.1 8 | react-redux ^4.0.0 → ^4.4.5 9 | react-router ^2.0.0 → ^2.3.0 10 | redux ^3.0.4 → ^3.5.2 11 | babel-core ^6.6.4 → ^6.7.7 12 | redbox-react ^1.1.1 → ^1.2.3 13 | redux-devtools ^3.1.1 → ^3.2.0 14 | redux-devtools-dock-monitor ^1.1.0 → ^1.1.1 15 | redux-devtools-log-monitor ^1.0.5 → ^1.0.11 16 | style-loader ^0.13.0 → ^0.13.1 17 | webpack ^1.12.3 → ^1.13.0 18 | webpack-dev-middleware ^1.2.0 → ^1.6.1 19 | 20 | - Mar. 22, 2016 21 | 22 | - upgrade everything to latest, including react v15-rc2 23 | - simplified the universal approach a bit 24 | - re-enabled `redux-devtools`, could toggle it on/off [here](https://github.com/coodoo/react-redux-isomorphic-example/blob/master/common/utils/configureStore.js#L8), be advised if you toggle `redux-devtools` on it will break universal rendering (you will see warnings in browser console saying react failed to reuse markup generated on server...etc), but `redux-devtools` was meant for use only in development anyway so just remember to turn it off before deploy. 25 | -------------------------------------------------------------------------------- /common/actions/ShopActions.js: -------------------------------------------------------------------------------- 1 | 2 | import types from '../constants/ActionTypes' 3 | import WebAPIUtils from '../utils/WebAPIUtils' 4 | 5 | export function readAll() { 6 | console.log( 'readAll run' ) 7 | return { 8 | // REQUEST | SUCCESS | ERROR were declared for optimistic update 9 | types: [ types.READ_ALL_PRODUCTS_REQUEST, types.READ_ALL_PRODUCTS_SUCCESS, types.READ_ALL_PRODUCTS_ERROR ], 10 | // WebAPIUtils 會操作遠端 REST API 獲取資料 11 | promise: WebAPIUtils.getAllProducts()//.then( result => console.log( 'preview result: ', result ) ) 12 | }; 13 | } 14 | 15 | export function readOne( {id} ) { 16 | 17 | console.log( 'readOne >id: ', id, ' >arguments: ', arguments ); 18 | 19 | return { 20 | types: [ types.READ_ONE_PRODUCT_REQUEST, types.READ_ONE_PRODUCT_SUCCESS, types.READ_ONE_PRODUCT_ERROR ], 21 | promise: WebAPIUtils.getOneProduct(id) 22 | }; 23 | 24 | } 25 | 26 | export function addToCart(product) { 27 | 28 | // optimistic update: add transacation_id for updating the object after it's returned from server 29 | product = product.set( 'tid', 't_' + Math.random()*100 ); 30 | 31 | return { 32 | types: [ types.ADD_TO_CART_REQUEST, types.ADD_TO_CART_SUCCESS, types.ADD_TO_CART_ERROR ], 33 | promise: WebAPIUtils.addToCart(product), 34 | result: product, 35 | }; 36 | } 37 | 38 | 39 | export function cartCheckout(products) { 40 | return { 41 | types: [ types.CART_CHECKOUT_REQUEST, types.CART_CHECKOUT_SUCCESS, types.CART_CHECKOUT_ERROR ], 42 | promise: WebAPIUtils.checkoutProducts(products), 43 | products 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var webpack = require('webpack'); 5 | 6 | module.exports = { 7 | 8 | devtool: '#inline-source-map', 9 | 10 | entry: [ 11 | 'webpack-hot-middleware/client', // for hot reload 12 | './client/index.js' // entry point for the client app 13 | ], 14 | 15 | // 16 | output: { 17 | path: path.join(__dirname, 'build'), 18 | filename: 'bundle.js', 19 | publicPath: '/static/' 20 | }, 21 | 22 | // 23 | plugins: [ 24 | new webpack.optimize.OccurenceOrderPlugin(), 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoErrorsPlugin() 27 | ], 28 | 29 | // 30 | resolve: { 31 | alias: { 32 | }, 33 | // require() file without adding .jsx and .js .suffix 34 | extensions: ['', '.js', '.jsx'] 35 | }, 36 | 37 | // be sure to add 'stage-0' in .babelrc to support es7 syntax 38 | module: { 39 | loaders: [ 40 | { 41 | test: /\.jsx?$/, 42 | loader: 'babel', 43 | exclude: /node_modules/, 44 | include: __dirname, 45 | query: { 46 | presets: [ 'react-hmre', "es2015", "stage-0", "react" ], 47 | plugins: [ "transform-decorators-legacy" ], 48 | } 49 | /*query: { 50 | "presets": [ "es2015", "stage-0", "react"], 51 | "plugins": [ "transform-decorators-legacy", 'react-transform'], 52 | // 這裏就是直接貼上原本寫在 .babelrc 內的設定字串 53 | "env": { 54 | "development": { 55 | "presets": ["react-hmre"] 56 | } 57 | } 58 | }*/ 59 | }, 60 | { 61 | test: /\.css$/, 62 | loader: "style!css", 63 | }, 64 | ] 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /common/components/ProductsContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { bindActionCreators } from 'redux'; 4 | 5 | import ProductItem from './ProductItem.jsx'; 6 | import ProductsList from './ProductsList.jsx'; 7 | import ProductItemContainer from './ProductItemContainer.jsx'; 8 | import * as ShopActions from '../actions/ShopActions'; 9 | import { fetchNeeds } from '../utils/fetchComponentData'; 10 | 11 | class ProductsContainer extends Component { 12 | 13 | static needs = [ 14 | ShopActions.readAll 15 | ]; 16 | 17 | // props contains dispatch fn and all reducers, passed in by @connect 18 | // dispatch: function 19 | // products: Record 20 | // carts: Record 21 | constructor(props, context) { 22 | super(props, context); 23 | this.actions = bindActionCreators(ShopActions, props.dispatch); 24 | } 25 | 26 | componentDidMount() { 27 | fetchNeeds( ProductsContainer.needs, this.props ) 28 | } 29 | 30 | render() { 31 | 32 | let products = this.props.products; 33 | 34 | // if( !products ) debugger; 35 | 36 | // convert Immutable.Map to Sequence, e.g. [ReactElement, ReactElement, ReactElement] 37 | var nodes = products.productsById.valueSeq().map( product => { 38 | return ; 42 | }); 43 | 44 | return ( 45 | 46 | {nodes} 47 | 48 | ); 49 | } 50 | 51 | }; 52 | 53 | // 使用 connect 精準獲取這個 view 需要的資料源,如此可減少日後不必要的 redraw 54 | export default connect( (state, ownProps) => ({ products: state.products }) )(ProductsContainer); 55 | -------------------------------------------------------------------------------- /common/reducers/CartReducer.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'immutable'; 2 | import {CartState} from '../constants/Types'; 3 | import createReducer from '../utils/createReducer'; 4 | import types from '../constants/ActionTypes'; 5 | 6 | // optimistic update 7 | // while we are waiting for server to ack persisting result, we can up date the view first 8 | // the only thing we need from server is the object's unique id 9 | function ADD_TO_CART_REQUEST( state, action ){ 10 | 11 | let { id, tid } = action.result; 12 | 13 | console.log( '[optimisitc update] transaction id: ', tid ) 14 | 15 | return state.update('cartsById', list => { 16 | return (list.indexOf(id) != -1) ? list : list.push(id); 17 | }) 18 | 19 | } 20 | 21 | function ADD_TO_CART_SUCCESS( state, action ){ 22 | 23 | let { id, tid } = action.result; 24 | 25 | // hint: use tid to update local record's unqiute id, as returned by server 26 | // console.log( 'obj transaction id: ', tid ) 27 | 28 | return state.update('cartsById', list => { 29 | return (list.indexOf(id) != -1) ? list : list.push(id); 30 | }) 31 | } 32 | 33 | 34 | function CART_CHECKOUT_REQUEST( state, action ){ 35 | return state; 36 | } 37 | 38 | function CART_CHECKOUT_ERROR( state, action ){ 39 | return state; 40 | } 41 | 42 | function CART_CHECKOUT_SUCCESS( state, action ){ 43 | return state.set('cartsById', state.cartsById.clear() ); 44 | } 45 | 46 | const handlers = 47 | { 48 | [types.ADD_TO_CART_REQUEST]: ADD_TO_CART_REQUEST, 49 | [types.ADD_TO_CART_SUCCESS]: ADD_TO_CART_SUCCESS, 50 | [types.CART_CHECKOUT_REQUEST]: CART_CHECKOUT_REQUEST, 51 | [types.CART_CHECKOUT_ERROR]: CART_CHECKOUT_ERROR, 52 | [types.CART_CHECKOUT_SUCCESS]: CART_CHECKOUT_SUCCESS, 53 | } 54 | 55 | export default createReducer( new CartState(), handlers ); 56 | -------------------------------------------------------------------------------- /common/utils/WebAPIUtils.js: -------------------------------------------------------------------------------- 1 | import shopdb from '../api/shopdb'; 2 | import {List, ProductRecord, convertToRecordMap } from '../constants/Types'; 3 | 4 | // WebAPIUitl is responsible to interacting with remote REST APIs 5 | export default { 6 | 7 | // 從 REST API 取回一包 JSON string,立即 parse 後再轉回為 Immutable.Record 物件 8 | // 然後才允許此物件進入系統內流通 9 | getAllProducts: function() { 10 | console.log( '\tWebAPIUtil::getAllProducts run' ); 11 | return shopdb.getProducts().then( result => { 12 | return convertToRecordMap( JSON.parse(result), ProductRecord ) 13 | }); 14 | }, 15 | 16 | getOneProduct: function(id) { 17 | console.log( '\tWebAPIUtil::getOneProduct run' ); 18 | return shopdb.getOneProduct(id) 19 | .then( result => new ProductRecord(JSON.parse(result)) ) 20 | /*.then(result=>{ 21 | console.log( '偷看 result: ', result.toJS() ); 22 | return result; 23 | });*/ 24 | }, 25 | 26 | addToCart: function(product){ 27 | // console.log( '\n\tWebAPIUtil::addToCart run' ); 28 | 29 | // product is a Immutable.Record, need to serialize it before sending back to server 30 | return shopdb.addToCart( product.toJSON() ) 31 | // when we get the result back need to marshall it into Immutable.Record again 32 | .then( result => new ProductRecord(JSON.parse(result)) ); 33 | }, 34 | 35 | // 36 | checkoutProducts: function( cartsByid:List ) { 37 | // console.log( '\n\tWebAPIUtil::checkoutProducts run' ); 38 | return shopdb.checkoutProducts( JSON.stringify(cartsByid) ) 39 | .then( result => JSON.parse(result) ) 40 | .catch( err => console.log( '交易出錯: ', err ) ); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /common/api/shopdb.js: -------------------------------------------------------------------------------- 1 | /* 2 | Simulate server's REST API 3 | everything goes in and out of it should be serialized in JSON format, so data could be transmitted across the wire 4 | */ 5 | 6 | import products from './products.js'; 7 | 8 | const TIMEOUT = 100; // simluate network delay 9 | 10 | export default { 11 | 12 | getProducts: function( timeout = TIMEOUT ) { 13 | return new Promise( ( resolve, reject ) => { 14 | // REST API always return JSON string 15 | setTimeout( () => resolve( JSON.stringify( products ) ), timeout ); 16 | } ) 17 | }, 18 | 19 | getOneProduct: function( id, timeout = TIMEOUT ) { 20 | return new Promise( ( resolve, reject ) => { 21 | setTimeout( () => { 22 | for ( let item of products ) { 23 | if ( item.id == id ) { 24 | resolve( JSON.stringify( item ) ); 25 | } 26 | } 27 | 28 | // product not found, throw an exception 29 | reject( 'Product not found: ' + id ); 30 | }, timeout ); 31 | 32 | } ) 33 | }, 34 | 35 | addToCart: function( product, timeout = TIMEOUT ) { 36 | return new Promise( ( resolve, reject ) => { 37 | setTimeout( function() { 38 | // console.log( 'products: ', products, ' >product: ', id ); 39 | 40 | let { id, tid } = product; 41 | 42 | let item = products.find( item => item.id == id ) 43 | 44 | if ( !item ) reject( 'item not found: ', id ); 45 | 46 | // added for optimistic update, sending tid back to client so it can update itself there 47 | item.tid = tid; 48 | 49 | item.inventory--; 50 | item.quantity++; 51 | 52 | resolve( JSON.stringify( item ) ); 53 | 54 | }, timeout ); 55 | 56 | } ) 57 | }, 58 | 59 | // 60 | checkoutProducts: function( cartsByid, timeout = TIMEOUT ) { 61 | return new Promise( ( resolve, reject ) => { 62 | setTimeout( function() { 63 | console.log( 'shopdb > purchase completed: ', JSON.parse( cartsByid ) ); 64 | resolve( JSON.stringify( {msg:'checkout completed'} ) ); 65 | }, timeout ); 66 | 67 | } ) 68 | }, 69 | 70 | } 71 | 72 | -------------------------------------------------------------------------------- /common/components/ProductDetail.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { bindActionCreators } from 'redux'; 4 | import * as ShopActions from '../actions/ShopActions'; 5 | import { Link } from 'react-router'; 6 | import { fetchNeeds } from '../utils/fetchComponentData'; 7 | 8 | export default class ProductDetail extends Component { 9 | 10 | static needs = [ 11 | ShopActions.readOne 12 | ]; 13 | 14 | constructor(props, context) { 15 | super( props, context ); 16 | this.actions = bindActionCreators( ShopActions, props.dispatch ); 17 | } 18 | 19 | componentDidMount(){ 20 | // check if product already existed to avoid unnecessary fetching 21 | if( !this.props.products ){ 22 | fetchNeeds( ProductDetail.needs, this.props ) 23 | return
Loading...
24 | } 25 | } 26 | 27 | render() { 28 | 29 | const { productsById } = this.props.products; 30 | const { id:currentProductId } = this.props.params; // provided by router params 31 | const product = productsById.get( currentProductId ); 32 | 33 | 34 | var styles = { 35 | backgroundColor: '#FFDC00' 36 | } 37 | 38 | return ( 39 |
40 |

← BACK

41 | 42 |

{product.title} - €{product.price}

43 |

inventory: {product.inventory}

44 | 49 |
50 | ); 51 | } 52 | 53 | onAddToCartClicked( p, evt ) { 54 | this.actions.addToCart( p ); 55 | } 56 | } 57 | 58 | // refer from using decorator before it became a standarized 59 | // @connect( (state, ownProps) => { products: state.products } ) 60 | export default connect( (state, ownProps) => ({ products: state.products }) )(ProductDetail) 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-isomprphic-example", 3 | "version": "1.0.0", 4 | "description": "An isomorphic example built with react, redux and react-router, see readme for detailed instructions", 5 | "main": "dev.js", 6 | "scripts": { 7 | "start": "node server/index.js", 8 | "start2": "npm run build& npm run server", 9 | "build": "webpack -d --progress --watch", 10 | "server": "node server.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/coodoo/react-redux-isomorphic-example" 15 | }, 16 | "keywords": [ 17 | "react", 18 | "reactjs", 19 | "hot", 20 | "reload", 21 | "hmr", 22 | "live", 23 | "edit", 24 | "webpack", 25 | "flux", 26 | "todomvc" 27 | ], 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/coodoo/react-redux-isomorphic-example/issues" 31 | }, 32 | "homepage": "https://github.com/coodoo/react-redux-isomorphic-example/", 33 | "dependencies": { 34 | "babel-plugin-transform-decorators": "^6.6.5", 35 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 36 | "babel-preset-es2015": "^6.6.0", 37 | "babel-preset-react": "^6.5.0", 38 | "babel-preset-react-hmre": "^1.1.1", 39 | "babel-preset-stage-0": "^6.5.0", 40 | "classnames": "^2.2.3", 41 | "flux-constants": "^0.2.2", 42 | "history": "^2.1.0", 43 | "immutable": "^3.8.1", 44 | "react": "^15.0.1", 45 | "react-dom": "^15.0.1", 46 | "react-redux": "^4.4.5", 47 | "react-router": "^2.3.0", 48 | "redux": "^3.5.2" 49 | }, 50 | "devDependencies": { 51 | "babel-core": "^6.7.7", 52 | "babel-loader": "^6.2.4", 53 | "babel-plugin-react-transform": "^2.0.2", 54 | "babel-register": "^6.7.2", 55 | "css-loader": "^0.23.1", 56 | "express": "^4.13.4", 57 | "node-libs-browser": "^1.0.0", 58 | "raw-loader": "^0.5.1", 59 | "react-transform-catch-errors": "^1.0.2", 60 | "react-transform-hmr": "^1.0.4", 61 | "redbox-react": "^1.2.3", 62 | "redux-devtools": "^3.2.0", 63 | "redux-devtools-dock-monitor": "^1.1.1", 64 | "redux-devtools-log-monitor": "^1.0.11", 65 | "redux-logger": "^2.6.1", 66 | "style-loader": "^0.13.1", 67 | "todomvc-app-css": "^2.0.4", 68 | "webpack": "^1.13.0", 69 | "webpack-dev-middleware": "^1.6.1", 70 | "webpack-hot-middleware": "^2.10.0" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /common/reducers/ProductReducer.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'immutable'; 2 | import {ProductState, ProductRecord, convertToRecordMap } from '../constants/Types'; 3 | import {CartState} from '../constants/Types'; 4 | import createReducer from '../utils/createReducer'; 5 | import types from '../constants/ActionTypes'; 6 | 7 | function READ_ALL_PRODUCTS_REQUEST( state, action ){ return state; } 8 | function READ_ALL_PRODUCTS_ERROR( state, action ){ return state; } 9 | function READ_ALL_PRODUCTS_SUCCESS( state, action ){ 10 | return state.update( 'productsById', map => action.result ) 11 | } 12 | 13 | function READ_ONE_PRODUCT_REQUEST( state, action ){ return state; } 14 | function READ_ONE_PRODUCT_ERROR( state, action ){ console.error( action ); return state; } 15 | function READ_ONE_PRODUCT_SUCCESS( state, action ){ 16 | 17 | // 但如果真的有回 server 撈資料,就要繼續跑這段 18 | if( !state.productsById.get(action.result.id) ){ 19 | state = state.update('productsById', map => { 20 | return map.set( action.result.id, action.result ); 21 | }) 22 | } 23 | 24 | return state; 25 | } 26 | 27 | // checkout CartReducer.js#ADD_TO_CART_REQUEST() for example on optimistic update example 28 | // REQUEST 事件時做 optimistic update 的下手處,這裏先改變資料狀態,觸發 view 重繪 29 | // 通常是將物件加上 tid (transaction_id) 30 | // 等 SUCCESS 事件時,再依 server 返還的正式 uuid 來更新物件內容 31 | function ADD_TO_CART_REQUEST( state, action ){ return state; } 32 | function ADD_TO_CART_ERROR( state, action ){ return state; } 33 | function ADD_TO_CART_SUCCESS( state, action ){ 34 | 35 | // marshalling always happens in reducer 36 | var addedProduct = action.result; 37 | 38 | state = state 39 | .update( 'productsById', idMap =>{ 40 | return idMap.map( p => { 41 | return ( p.id == addedProduct.id ) ? addedProduct : p; 42 | }) 43 | }) 44 | 45 | // calculate new total after adding new item 46 | return state.update('total', num => { 47 | return state.productsById.reduce( (acc, item) => { 48 | return acc + (item.quantity * item.price) 49 | }, 0 ).toFixed(2) 50 | }) 51 | } 52 | 53 | function CART_CHECKOUT_SUCCESS( state, action ){ 54 | return state 55 | .update('productsById', list => { 56 | return list.map( item => item.set('quantity', 0) ) 57 | }) 58 | .update('total', num => '0'); 59 | } 60 | 61 | 62 | const handlers = 63 | { 64 | [types.READ_ALL_PRODUCTS_REQUEST]: READ_ALL_PRODUCTS_REQUEST, 65 | [types.READ_ALL_PRODUCTS_SUCCESS]: READ_ALL_PRODUCTS_SUCCESS, 66 | [types.READ_ALL_PRODUCTS_ERROR]: READ_ALL_PRODUCTS_ERROR, 67 | [types.READ_ONE_PRODUCT_REQUEST]: READ_ONE_PRODUCT_REQUEST, 68 | [types.READ_ONE_PRODUCT_ERROR]: READ_ONE_PRODUCT_ERROR, 69 | [types.READ_ONE_PRODUCT_SUCCESS]: READ_ONE_PRODUCT_SUCCESS, 70 | [types.ADD_TO_CART_REQUEST]: ADD_TO_CART_REQUEST, 71 | [types.ADD_TO_CART_SUCCESS]: ADD_TO_CART_SUCCESS, 72 | [types.ADD_TO_CART_ERROR]: ADD_TO_CART_ERROR, 73 | [types.CART_CHECKOUT_SUCCESS]: CART_CHECKOUT_SUCCESS, 74 | } 75 | 76 | export default createReducer( new ProductState(), handlers ); 77 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import express from 'express'; 3 | import path from 'path'; 4 | 5 | import React from 'react' 6 | import { renderToString } from 'react-dom/server' 7 | 8 | import { Router, RouterContext, match } from 'react-router'; 9 | import routes from '../common/routes/routing'; 10 | 11 | import { applyMiddleware, createStore } from 'redux'; 12 | import { Provider } from 'react-redux'; 13 | 14 | import promiseMiddleware from '../common/middlewares/PromiseMiddleware'; 15 | import combinedReducers from '../common/reducers'; 16 | 17 | import fetchComponentData from '../common/utils/fetchComponentData'; 18 | 19 | const finalCreateStore = applyMiddleware(promiseMiddleware)( createStore ); 20 | 21 | // console.log( 'env: ', process.env.NODE_ENV ) 22 | 23 | const app = express(); 24 | 25 | app.use('/assets', express.static(path.join(__dirname, '../client/assets'))) 26 | 27 | // initialize webpack HMR 28 | const webpack = require('webpack') 29 | const webpackDevMiddleware = require('webpack-dev-middleware') 30 | const webpackHotMiddleware = require('webpack-hot-middleware') 31 | const config = require('../webpack.config') 32 | const compiler = webpack(config) 33 | app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath })) 34 | app.use(webpackHotMiddleware(compiler)) 35 | 36 | // server rendering 37 | app.use( ( req, res, next ) => { 38 | 39 | const store = finalCreateStore(combinedReducers); 40 | 41 | // react-router 42 | match( {routes, location: req.url}, ( error, redirectLocation, renderProps ) => { 43 | 44 | if ( error ) 45 | return res.status(500).send( error.message ); 46 | 47 | if ( redirectLocation ) 48 | return res.redirect( 302, redirectLocation.pathname + redirectLocation.search ); 49 | 50 | if ( renderProps == null ) { 51 | // return next('err msg: route not found'); // yield control to next middleware to handle the request 52 | return res.status(404).send( 'Not found' ); 53 | } 54 | 55 | // console.log( '\nserver > renderProps: \n', require('util').inspect( renderProps, false, 1, true) ) 56 | // console.log( '\nserver > renderProps: \n', require('util').inspect( renderProps.components, false, 3, true) ) 57 | 58 | // this is where universal rendering happens, 59 | // fetchComponentData() will trigger actions listed in static "needs" props in each container component 60 | // and wait for all of them to complete before continuing rendering the page, 61 | // hence ensuring all data needed was fetched before proceeding 62 | // 63 | // renderProps: contains all necessary data, e.g: routes, router, history, components... 64 | fetchComponentData( store.dispatch, renderProps.components, renderProps.params) 65 | 66 | .then( () => { 67 | 68 | const initView = renderToString(( 69 | 70 | 71 | 72 | )) 73 | 74 | // console.log('\ninitView:\n', initView); 75 | 76 | let state = JSON.stringify( store.getState() ); 77 | // console.log( '\nstate: ', state ) 78 | 79 | let page = renderFullPage( initView, state ) 80 | // console.log( '\npage:\n', page ); 81 | 82 | return page; 83 | 84 | }) 85 | 86 | .then( page => res.status(200).send(page) ) 87 | 88 | .catch( err => res.end(err.message) ); 89 | }) 90 | }) 91 | 92 | 93 | function renderFullPage(html, initialState) { 94 | return ` 95 | 96 | 97 | 98 | Universal Redux Example 99 | 100 | 101 | 102 | 103 |
${html}
104 | 105 | 106 | 107 | 108 | ` 109 | } 110 | 111 | // example of handling 404 pages 112 | app.get('*', function(req, res) { 113 | res.status(404).send('Server.js > 404 - Page Not Found'); 114 | }) 115 | 116 | // global error catcher, need four arguments 117 | app.use((err, req, res, next) => { 118 | console.error("Error on request %s %s", req.method, req.url); 119 | console.error(err.stack); 120 | res.status(500).send("Server error"); 121 | }); 122 | 123 | process.on('uncaughtException', evt => { 124 | console.log( 'uncaughtException: ', evt ); 125 | }) 126 | 127 | app.listen(3000, function(){ 128 | console.log('Listening on port 3000'); 129 | }); 130 | 131 | 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Universal Example with React and Redux 3 | ====================================== 4 | 5 | ## Introduction 6 | 7 | This is an isomorphic/universal example built with react and [`Redux`](https://github.com/gaearon/redux) which utilizes the same codebase on server and browser to correctly handle multiple requests at the same time. 8 | 9 | This example was previously built with [alt](https://github.com/coodoo/react-alt-isomorphic-example) and now fully migrated to `redux`, you might as well compare the two and see the differences. 10 | 11 | Feel free to [ask questions](https://github.com/coodoo/react-redux-isomorphic-example/issues) or send over pull requests. 12 | 13 | ## How to run 14 | 15 | ``` 16 | $ npm i 17 | $ npm start 18 | ``` 19 | 20 | Then visit `localhost:3000`. 21 | 22 | ### Upgrade Notes 23 | 24 | - It is highly recommented to always __remove__ `node_modules` and reinstall everything to avoid any possible issues 25 | 26 | ## The case for redux 27 | 28 | We love react and flux for that it provides a smooth workflow and much simpler mindset to develop applications. 29 | 30 | On top of that, redux makes things like __10x__ easier by transforming `flux store` into pure functions, with no state stored inside, which solved a lot of `singleton` and `waiting` issues in other implementations, it also encourages a lot of best practices like `container component`, easy extension points and minimal API surface area. 31 | 32 | Under the hood, redux is heavily based on various `functional programming` ideas to make those nice things happen, but don't let the functional aspect of redux scares you off, __`as a lib user, you don't really need to know about those inner workings to get started`__. 33 | 34 | That being said, it's quite an enjoyable and amazing experience to read through the whole codebase line by line and fully comprehend how things were designed to work in a very different manner, it's truly an eye opener. 35 | 36 | In short, if you could only pick up one new skill this summer (2015), `redux` should be on top of your list (and a bit of functional programming knowledge won't hurt too :) 37 | 38 | 39 | ## Feature Highlights 40 | 41 | - React, Redux, universal/isomorphic, ES6/7, webpack, babel and _all those usual suspects!_ 42 | 43 | 44 | - Uses `Promise middleware` for redux to showcase async data fetching along with optimistic updating for each REST API calls 45 | 46 | - Uses `createReducer` to greatly simplify `reducers` code by using `constant mapping` 47 | 48 | - Showcase async data fetching solutions with `react-router` before displaying each view (hence no blank screen while waiting for data to be downloaded) 49 | 50 | - Showcase how to easily implement `universal` application with `redux` and `react-router` on the server (and dehydrate the data on the client too) 51 | 52 | ## Goals of the example 53 | 54 | - Reuse the same codebase on both the browser and server without any modification 55 | 56 | - Must be able to handle multiple requests on the server at the same time without polluting each user's data 57 | 58 | - The approach must be easily understandable and simple to develop and maintain so it's strongly fool-proof in a team environment 59 | 60 | - The application can be used in either `universal` (a.k.a server-rendering) or `pure client` mode 61 | 62 | ## Why Universal App? 63 | 64 | - Shorter time-to-first-page means better user experience 65 | 66 | - Deep linking into the application for better usability (ie: bookmarks) 67 | 68 | - SEO (of course!) 69 | 70 | ## Technical highlights 71 | 72 | #### 1. Learn from mistakes 73 | 74 | An early implementation of this example handles the data model wrong by mutating the state passed into each reducer hence causing a lot of strange issues like making `redux-devtools` acting funny. 75 | 76 | [@gaearon](https://github.com/gaearon), author of both `redux` and `redux-devtools`, was kindly enough to [pointed out those mistakes](https://github.com/coodoo/react-redux-isomorphic-example/issues/9) and provided detailed instructions on how to make it right. 77 | 78 | Later the example was migrated to use `immutablejs` but the `immutability` spirit stays the same. 79 | 80 | #### 2. Key things to note about universal implementations 81 | 82 | - We want to reuse all routing rules on both the client and server, no dupe works needed 83 | - We need to make sure data for each view is fully fetched before rendering to string, this is tricky because most REST API calls are async 84 | - With redux and react-router in place, all these can be done easily and gracefully with `Router.run`, checkout `server.js`. 85 | 86 | #### 3. How immutable data model works 87 | 88 | [`Immutable.js`](https://github.com/facebook/immutable-js/) was picked for the job for two main reasons: 89 | 90 | 1. Immutable source of truth (data model) is easy to reason about, it makes dirty diff fast and the overal app state less error-prone. 91 | 92 | 2. The data structure itself is fully fool-proof, meaning no one can accidentally modify the data model and causing bugs that are extremely hard to trace and fix, especially in a large team. 93 | 94 | Pay special attention to the use of `Immutable.Record` data structure and `update()` method used to manipulate data. 95 | 96 | #### 4. How container component works 97 | 98 | In most redux applications `` is the root container component that holds all child components, it's responsible for fetching application state from outside, stores it, monitor it's `change` event and triggers redraw. 99 | 100 | There's a nice `select` function in `` that you could use to cherry pick and monitor only those `reducers` you are interested, by doing so you avoided unnecessary redraw calls hence making the app more performant. 101 | 102 | 103 | ## Redux-devtools included 104 | 105 | With [redux-devtools](https://github.com/gaearon/redux-devtools/) in place, you can undo/redo changes anytime, just like travelling back and forth in time, it's super neat being able to debug the application at different status. 106 | 107 | Pro hint, it can be toggled off by setting `window.$REDUX_DEVTOOL = false;` in `boot.js`. 108 | 109 | ![redux-devtools inaction](https://raw.githubusercontent.com/coodoo/react-redux-isomorphic-example/master/client/assets/images/cap.png) 110 | 111 | ## Special thanks 112 | 113 | Thanks [@gaearon](https://github.com/gaearon) for code review and pointing out misc. issues. [Lee Byron](https://github.com/leebyron) for bringing `immutable.js` to the world, it makes everything easier, you guys rock! 114 | 115 | ## License 116 | 117 | MIT 118 | -------------------------------------------------------------------------------- /client/assets/css/uikit.almost-flat.min.css: -------------------------------------------------------------------------------- 1 | /*! UIkit 2.16.2 | http://www.getuikit.com | (c) 2014 YOOtheme | MIT License */ 2 | html{font:400 14px / 20px "Helvetica Neue",Helvetica,Arial,sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;background:#fff;color:#444}body{margin:0}a{background:0 0}a:active,a:hover{outline:0}.uk-link,a{color:#07d;text-decoration:none;cursor:pointer}.uk-link:hover,a:hover{color:#059;text-decoration:underline}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}:not(pre)>code,:not(pre)>kbd,:not(pre)>samp{font-size:12px;font-family:Consolas,monospace,serif;color:#d05;white-space:nowrap;padding:0 4px;border:1px solid #ddd;border-radius:3px;background:#fafafa}em{color:#d05}ins{background:#ffa;color:#444;text-decoration:none}mark{background:#ffa;color:#444}q{font-style:italic}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{max-width:100%;height:auto;-moz-box-sizing:border-box;box-sizing:border-box;border:0;vertical-align:middle}.uk-img-preserve,.uk-img-preserve img{max-width:none}svg:not(:root){overflow:hidden}address,blockquote,dl,fieldset,figure,ol,p,pre,ul{margin:0 0 15px}*+address,*+blockquote,*+dl,*+fieldset,*+figure,*+ol,*+p,*+pre,*+ul{margin-top:15px}h1,h2,h3,h4,h5,h6{margin:0 0 15px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:400;color:#444;text-transform:none}*+h1,*+h2,*+h3,*+h4,*+h5,*+h6{margin-top:25px}.uk-h1,h1{font-size:36px;line-height:42px}.uk-h2,h2{font-size:24px;line-height:30px}.uk-h3,h3{font-size:18px;line-height:24px}.uk-h4,h4{font-size:16px;line-height:22px}.uk-h5,h5{font-size:14px;line-height:20px}.uk-h6,h6{font-size:12px;line-height:18px}ol,ul{padding-left:30px}ol>li>ol,ol>li>ul,ul>li>ol,ul>li>ul{margin:0}dt{font-weight:700}dd{margin-left:0}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0;margin:15px 0;border:0;border-top:1px solid #ddd}address{font-style:normal}blockquote{padding-left:15px;border-left:5px solid #ddd;font-size:16px;line-height:22px;font-style:italic}pre{padding:10px;background:#fafafa;font:12px / 18px Consolas,monospace,serif;color:#444;-moz-tab-size:4;tab-size:4;overflow:auto;border:1px solid #ddd;border-radius:3px}::-moz-selection{background:#39f;color:#fff;text-shadow:none}::selection{background:#39f;color:#fff;text-shadow:none}article,aside,details,figcaption,figure,footer,header,main,nav,section,summary{display:block}progress{vertical-align:baseline}[hidden],audio:not([controls]),template{display:none}iframe{border:0}@media screen and (max-width:400px){@-ms-viewport{width:device-width}}.uk-grid{display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-wrap:wrap;-webkit-flex-wrap:wrap;flex-wrap:wrap;margin:0 0 0 -25px;padding:0;list-style:none}.uk-grid:after,.uk-grid:before{content:"";display:block;overflow:hidden}.uk-grid:after{clear:both}.uk-grid>*{-ms-flex:none;-webkit-flex:none;flex:none;margin:0;padding-left:25px;float:left}.uk-grid>*>:last-child{margin-bottom:0}.uk-grid+.uk-grid,.uk-grid>*>.uk-panel+.uk-panel,.uk-grid>.uk-grid-margin{margin-top:25px}@media (min-width:1220px){.uk-grid:not(.uk-grid-preserve){margin-left:-35px}.uk-grid:not(.uk-grid-preserve)>*{padding-left:35px}.uk-grid:not(.uk-grid-preserve)+.uk-grid,.uk-grid:not(.uk-grid-preserve)>*>.uk-panel+.uk-panel,.uk-grid:not(.uk-grid-preserve)>.uk-grid-margin{margin-top:35px}}.uk-grid.uk-grid-small{margin-left:-10px}.uk-grid.uk-grid-small>*{padding-left:10px}.uk-grid.uk-grid-small+.uk-grid-small,.uk-grid.uk-grid-small>*>.uk-panel+.uk-panel,.uk-grid.uk-grid-small>.uk-grid-margin{margin-top:10px}.uk-grid-divider:not(:empty){margin-left:-25px;margin-right:-25px}.uk-grid-divider>*{padding-left:25px;padding-right:25px}.uk-grid-divider>[class*=uk-width-1-]:not(.uk-width-1-1):nth-child(n+2),.uk-grid-divider>[class*=uk-width-2-]:nth-child(n+2),.uk-grid-divider>[class*=uk-width-3-]:nth-child(n+2),.uk-grid-divider>[class*=uk-width-4-]:nth-child(n+2),.uk-grid-divider>[class*=uk-width-5-]:nth-child(n+2),.uk-grid-divider>[class*=uk-width-6-]:nth-child(n+2),.uk-grid-divider>[class*=uk-width-7-]:nth-child(n+2),.uk-grid-divider>[class*=uk-width-8-]:nth-child(n+2),.uk-grid-divider>[class*=uk-width-9-]:nth-child(n+2){border-left:1px solid #ddd}@media (min-width:768px){.uk-grid-divider>[class*=uk-width-medium-]:not(.uk-width-medium-1-1):nth-child(n+2){border-left:1px solid #ddd}}@media (min-width:960px){.uk-grid-divider>[class*=uk-width-large-]:not(.uk-width-large-1-1):nth-child(n+2){border-left:1px solid #ddd}}@media (min-width:1220px){.uk-grid-divider:not(.uk-grid-preserve):not(:empty){margin-left:-35px;margin-right:-35px}.uk-grid-divider:not(.uk-grid-preserve)>*{padding-left:35px;padding-right:35px}.uk-grid-divider:not(.uk-grid-preserve):empty{margin-top:35px;margin-bottom:35px}}.uk-grid-divider:empty{margin-top:25px;margin-bottom:25px;border-top:1px solid #ddd}.uk-grid-match>*{display:-ms-flexbox;display:-webkit-flex;display:flex}.uk-grid-match>*>*{-ms-flex:none;-webkit-flex:none;flex:none;-moz-box-sizing:border-box;box-sizing:border-box;width:100%}[class*=uk-grid-width]>*{-moz-box-sizing:border-box;box-sizing:border-box;width:100%}.uk-grid-width-1-2>*{width:50%}.uk-grid-width-1-3>*{width:33.333%}.uk-grid-width-1-4>*{width:25%}.uk-grid-width-1-5>*{width:20%}.uk-grid-width-1-6>*{width:16.666%}.uk-grid-width-1-10>*{width:10%}@media (min-width:480px){.uk-grid-width-small-1-2>*{width:50%}.uk-grid-width-small-1-3>*{width:33.333%}.uk-grid-width-small-1-4>*{width:25%}.uk-grid-width-small-1-5>*{width:20%}.uk-grid-width-small-1-6>*{width:16.666%}.uk-grid-width-small-1-10>*{width:10%}}@media (min-width:768px){.uk-grid-width-medium-1-2>*{width:50%}.uk-grid-width-medium-1-3>*{width:33.333%}.uk-grid-width-medium-1-4>*{width:25%}.uk-grid-width-medium-1-5>*{width:20%}.uk-grid-width-medium-1-6>*{width:16.666%}.uk-grid-width-medium-1-10>*{width:10%}}@media (min-width:960px){.uk-grid-width-large-1-2>*{width:50%}.uk-grid-width-large-1-3>*{width:33.333%}.uk-grid-width-large-1-4>*{width:25%}.uk-grid-width-large-1-5>*{width:20%}.uk-grid-width-large-1-6>*{width:16.666%}.uk-grid-width-large-1-10>*{width:10%}}@media (min-width:1220px){.uk-grid-width-xlarge-1-2>*{width:50%}.uk-grid-width-xlarge-1-3>*{width:33.333%}.uk-grid-width-xlarge-1-4>*{width:25%}.uk-grid-width-xlarge-1-5>*{width:20%}.uk-grid-width-xlarge-1-6>*{width:16.666%}.uk-grid-width-xlarge-1-10>*{width:10%}}[class*=uk-width]{-moz-box-sizing:border-box;box-sizing:border-box;width:100%}.uk-width-1-1{width:100%}.uk-width-1-2,.uk-width-2-4,.uk-width-3-6,.uk-width-5-10{width:50%}.uk-width-1-3,.uk-width-2-6{width:33.333%}.uk-width-2-3,.uk-width-4-6{width:66.666%}.uk-width-1-4{width:25%}.uk-width-3-4{width:75%}.uk-width-1-5,.uk-width-2-10{width:20%}.uk-width-2-5,.uk-width-4-10{width:40%}.uk-width-3-5,.uk-width-6-10{width:60%}.uk-width-4-5,.uk-width-8-10{width:80%}.uk-width-1-6{width:16.666%}.uk-width-5-6{width:83.333%}.uk-width-1-10{width:10%}.uk-width-3-10{width:30%}.uk-width-7-10{width:70%}.uk-width-9-10{width:90%}@media (min-width:480px){.uk-width-small-1-1{width:100%}.uk-width-small-1-2,.uk-width-small-2-4,.uk-width-small-3-6,.uk-width-small-5-10{width:50%}.uk-width-small-1-3,.uk-width-small-2-6{width:33.333%}.uk-width-small-2-3,.uk-width-small-4-6{width:66.666%}.uk-width-small-1-4{width:25%}.uk-width-small-3-4{width:75%}.uk-width-small-1-5,.uk-width-small-2-10{width:20%}.uk-width-small-2-5,.uk-width-small-4-10{width:40%}.uk-width-small-3-5,.uk-width-small-6-10{width:60%}.uk-width-small-4-5,.uk-width-small-8-10{width:80%}.uk-width-small-1-6{width:16.666%}.uk-width-small-5-6{width:83.333%}.uk-width-small-1-10{width:10%}.uk-width-small-3-10{width:30%}.uk-width-small-7-10{width:70%}.uk-width-small-9-10{width:90%}}@media (min-width:768px){.uk-width-medium-1-1{width:100%}.uk-width-medium-1-2,.uk-width-medium-2-4,.uk-width-medium-3-6,.uk-width-medium-5-10{width:50%}.uk-width-medium-1-3,.uk-width-medium-2-6{width:33.333%}.uk-width-medium-2-3,.uk-width-medium-4-6{width:66.666%}.uk-width-medium-1-4{width:25%}.uk-width-medium-3-4{width:75%}.uk-width-medium-1-5,.uk-width-medium-2-10{width:20%}.uk-width-medium-2-5,.uk-width-medium-4-10{width:40%}.uk-width-medium-3-5,.uk-width-medium-6-10{width:60%}.uk-width-medium-4-5,.uk-width-medium-8-10{width:80%}.uk-width-medium-1-6{width:16.666%}.uk-width-medium-5-6{width:83.333%}.uk-width-medium-1-10{width:10%}.uk-width-medium-3-10{width:30%}.uk-width-medium-7-10{width:70%}.uk-width-medium-9-10{width:90%}}@media (min-width:960px){.uk-width-large-1-1{width:100%}.uk-width-large-1-2,.uk-width-large-2-4,.uk-width-large-3-6,.uk-width-large-5-10{width:50%}.uk-width-large-1-3,.uk-width-large-2-6{width:33.333%}.uk-width-large-2-3,.uk-width-large-4-6{width:66.666%}.uk-width-large-1-4{width:25%}.uk-width-large-3-4{width:75%}.uk-width-large-1-5,.uk-width-large-2-10{width:20%}.uk-width-large-2-5,.uk-width-large-4-10{width:40%}.uk-width-large-3-5,.uk-width-large-6-10{width:60%}.uk-width-large-4-5,.uk-width-large-8-10{width:80%}.uk-width-large-1-6{width:16.666%}.uk-width-large-5-6{width:83.333%}.uk-width-large-1-10{width:10%}.uk-width-large-3-10{width:30%}.uk-width-large-7-10{width:70%}.uk-width-large-9-10{width:90%}}@media (min-width:768px){[class*=uk-pull-],[class*=uk-push-]{position:relative}.uk-push-1-2,.uk-push-2-4,.uk-push-3-6,.uk-push-5-10{left:50%}.uk-push-1-3,.uk-push-2-6{left:33.333%}.uk-push-2-3,.uk-push-4-6{left:66.666%}.uk-push-1-4{left:25%}.uk-push-3-4{left:75%}.uk-push-1-5,.uk-push-2-10{left:20%}.uk-push-2-5,.uk-push-4-10{left:40%}.uk-push-3-5,.uk-push-6-10{left:60%}.uk-push-4-5,.uk-push-8-10{left:80%}.uk-push-1-6{left:16.666%}.uk-push-5-6{left:83.333%}.uk-push-1-10{left:10%}.uk-push-3-10{left:30%}.uk-push-7-10{left:70%}.uk-push-9-10{left:90%}.uk-pull-1-2,.uk-pull-2-4,.uk-pull-3-6,.uk-pull-5-10{left:-50%}.uk-pull-1-3,.uk-pull-2-6{left:-33.333%}.uk-pull-2-3,.uk-pull-4-6{left:-66.666%}.uk-pull-1-4{left:-25%}.uk-pull-3-4{left:-75%}.uk-pull-1-5,.uk-pull-2-10{left:-20%}.uk-pull-2-5,.uk-pull-4-10{left:-40%}.uk-pull-3-5,.uk-pull-6-10{left:-60%}.uk-pull-4-5,.uk-pull-8-10{left:-80%}.uk-pull-1-6{left:-16.666%}.uk-pull-5-6{left:-83.333%}.uk-pull-1-10{left:-10%}.uk-pull-3-10{left:-30%}.uk-pull-7-10{left:-70%}.uk-pull-9-10{left:-90%}}.uk-panel{display:block;position:relative}.uk-panel,a.uk-panel:hover{color:inherit;text-decoration:none}.uk-panel:after,.uk-panel:before{content:"";display:table}.uk-panel:after{clear:both}.uk-panel>:not(.uk-panel-title):last-child{margin-bottom:0}.uk-panel-title{margin-top:0;margin-bottom:15px;font-size:18px;line-height:24px;font-weight:400;text-transform:none;color:#444}.uk-panel-badge{position:absolute;top:0;right:0;z-index:1}.uk-panel-box{padding:15px;background:#fafafa;color:#444;border:1px solid #ddd;border-radius:4px}.uk-panel-box .uk-panel-title,a.uk-panel-box:hover{color:#444}.uk-panel-box .uk-panel-badge{top:10px;right:10px}.uk-panel-box .uk-panel-teaser{margin:-16px -16px 15px}.uk-panel-box>.uk-nav-side{margin:0 -15px}.uk-panel-box-primary{background-color:#ebf7fd;color:#2d7091;border-color:rgba(45,112,145,.3)}.uk-panel-box-primary .uk-panel-title,a.uk-panel-box-primary:hover{color:#2d7091}.uk-panel-box-secondary{background-color:#fff;color:#444}.uk-panel-box-secondary .uk-panel-title,a.uk-panel-box-secondary:hover{color:#444}.uk-panel-hover{padding:15px;border:1px solid transparent;border-radius:4px}.uk-panel-hover:hover{background:#fafafa;color:#444;border-color:#ddd}.uk-panel-hover .uk-panel-badge{top:10px;right:10px}.uk-panel-hover .uk-panel-teaser{margin:-16px -16px 15px}.uk-panel-header .uk-panel-title{padding-bottom:10px;border-bottom:1px solid #ddd;color:#444}.uk-panel-space{padding:30px}.uk-panel-space .uk-panel-badge{top:30px;right:30px}.uk-panel+.uk-panel-divider{margin-top:50px!important}.uk-panel+.uk-panel-divider:before{content:"";display:block;position:absolute;top:-25px;left:0;right:0;border-top:1px solid #ddd}@media (min-width:1220px){.uk-panel+.uk-panel-divider{margin-top:70px!important}.uk-panel+.uk-panel-divider:before{top:-35px}}.uk-panel-box .uk-panel-teaser{border-top-left-radius:4px;border-top-right-radius:4px;overflow:hidden}.uk-article:after,.uk-article:before{content:"";display:table}.uk-article:after{clear:both}.uk-article>:last-child{margin-bottom:0}.uk-article+.uk-article{margin-top:25px}.uk-article-title{font-size:36px;line-height:42px;font-weight:400;text-transform:none}.uk-article-title a{color:inherit;text-decoration:none}.uk-article-meta{font-size:12px;line-height:18px;color:#999}.uk-article-lead{color:#444;font-size:18px;line-height:24px;font-weight:400}.uk-article-divider{margin-bottom:25px;border-color:#ddd}*+.uk-article-divider{margin-top:25px}.uk-article+.uk-article{padding-top:25px;border-top:1px solid #ddd}.uk-comment-header{margin-bottom:15px;padding:10px;border:1px solid #ddd;border-radius:4px;background:#fafafa}.uk-comment-header:after,.uk-comment-header:before{content:"";display:table}.uk-comment-header:after{clear:both}.uk-comment-avatar{margin-right:15px;float:left}.uk-comment-title{margin:5px 0 0;font-size:16px;line-height:22px}.uk-comment-meta{margin:2px 0 0;font-size:11px;line-height:16px;color:#999}.uk-comment-body{padding-left:10px;padding-right:10px}.uk-comment-body>:last-child{margin-bottom:0}.uk-comment-list{padding:0;list-style:none}.uk-comment-list .uk-comment+ul{margin:25px 0 0;list-style:none}.uk-comment-list .uk-comment+ul>li:nth-child(n+2),.uk-comment-list>li:nth-child(n+2){margin-top:25px}@media (min-width:768px){.uk-comment-list .uk-comment+ul{padding-left:100px}}.uk-comment-primary .uk-comment-header{border-color:rgba(45,112,145,.3);background-color:#ebf7fd;color:#2d7091;text-shadow:0 1px 0 #fff}.uk-nav,.uk-nav ul{margin:0;padding:0;list-style:none}.uk-nav li>a{display:block;text-decoration:none}.uk-nav>li>a{padding:5px 15px}.uk-nav ul{padding-left:15px}.uk-nav ul a{padding:2px 0}.uk-nav li>a>div{font-size:12px;line-height:18px}.uk-nav-header{padding:5px 15px;text-transform:uppercase;font-weight:700;font-size:12px}.uk-nav-header:not(:first-child){margin-top:15px}.uk-nav-divider{margin:9px 15px}ul.uk-nav-sub{padding:5px 0 5px 15px}.uk-nav-parent-icon>.uk-parent>a:after{content:"\f104";width:20px;margin-right:-10px;float:right;font-family:FontAwesome;text-align:center}.uk-nav-parent-icon>.uk-parent.uk-open>a:after{content:"\f107"}.uk-nav-side>li>a{color:#444}.uk-nav-side>li>a:focus,.uk-nav-side>li>a:hover{background:rgba(0,0,0,.03);color:#444;outline:0;box-shadow:inset 0 0 1px rgba(0,0,0,.06);text-shadow:0 -1px 0 #fff}.uk-nav-side>li.uk-active>a{background:#00a8e6;color:#fff;box-shadow:inset 0 0 5px rgba(0,0,0,.05);text-shadow:0 -1px 0 rgba(0,0,0,.1)}.uk-nav-side .uk-nav-header{color:#444}.uk-nav-side .uk-nav-divider{border-top:1px solid #ddd;box-shadow:0 1px 0 #fff}.uk-nav-side ul a{color:#07d}.uk-nav-side ul a:hover{color:#059}.uk-nav-dropdown>li>a{color:#444}.uk-nav-dropdown>li>a:focus,.uk-nav-dropdown>li>a:hover{background:#00a8e6;color:#fff;outline:0;box-shadow:inset 0 0 5px rgba(0,0,0,.05);text-shadow:0 -1px 0 rgba(0,0,0,.1)}.uk-nav-dropdown .uk-nav-header{color:#999}.uk-nav-dropdown .uk-nav-divider{border-top:1px solid #ddd}.uk-nav-dropdown ul a{color:#07d}.uk-nav-dropdown ul a:hover{color:#059}.uk-nav-navbar>li>a{color:#444}.uk-nav-navbar>li>a:focus,.uk-nav-navbar>li>a:hover{background:#00a8e6;color:#fff;outline:0;box-shadow:inset 0 0 5px rgba(0,0,0,.05);text-shadow:0 -1px 0 rgba(0,0,0,.1)}.uk-nav-navbar .uk-nav-header{color:#999}.uk-nav-navbar .uk-nav-divider{border-top:1px solid #ddd}.uk-nav-navbar ul a{color:#07d}.uk-nav-navbar ul a:hover{color:#059}.uk-nav-offcanvas>li>a{color:#ccc;padding:10px 15px;border-top:1px solid rgba(0,0,0,.3);box-shadow:inset 0 1px 0 rgba(255,255,255,.05);text-shadow:0 1px 0 rgba(0,0,0,.5)}.uk-nav-offcanvas>.uk-open>a,html:not(.uk-touch) .uk-nav-offcanvas>li>a:focus,html:not(.uk-touch) .uk-nav-offcanvas>li>a:hover{background:#404040;color:#fff;outline:0}html .uk-nav.uk-nav-offcanvas>li.uk-active>a{background:#1a1a1a;color:#fff;box-shadow:inset 0 1px 3px rgba(0,0,0,.3)}.uk-nav-offcanvas .uk-nav-header{color:#777;margin-top:0;border-top:1px solid rgba(0,0,0,.3);background:#404040;box-shadow:inset 0 1px 0 rgba(255,255,255,.05);text-shadow:0 1px 0 rgba(0,0,0,.5)}.uk-nav-offcanvas .uk-nav-divider{border-top:1px solid rgba(255,255,255,.01);margin:0;height:4px;background:rgba(0,0,0,.2);box-shadow:inset 0 1px 3px rgba(0,0,0,.3)}.uk-nav-offcanvas ul a{color:#ccc}html:not(.uk-touch) .uk-nav-offcanvas ul a:hover{color:#fff}.uk-nav-offcanvas{border-bottom:1px solid rgba(0,0,0,.3);box-shadow:0 1px 0 rgba(255,255,255,.05)}.uk-nav-offcanvas .uk-nav-sub{border-top:1px solid rgba(0,0,0,.3);box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}.uk-navbar{background:#f5f5f5;color:#444;border:1px solid rgba(0,0,0,.06);border-radius:4px}.uk-navbar:after,.uk-navbar:before{content:"";display:table}.uk-navbar:after{clear:both}.uk-navbar-nav{margin:0;padding:0;list-style:none;float:left}.uk-navbar-nav>li{float:left;position:relative}.uk-navbar-nav>li>a{display:block;-moz-box-sizing:border-box;box-sizing:border-box;text-decoration:none;height:41px;padding:0 15px;line-height:40px;color:#444;font-size:14px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:400;margin-top:-1px;margin-left:-1px;border:1px solid transparent;border-bottom-width:0;text-shadow:0 1px 0 #fff}.uk-navbar-nav>li>a[href='#']{cursor:text}.uk-navbar-nav>li.uk-open>a,.uk-navbar-nav>li:hover>a,.uk-navbar-nav>li>a:focus{background-color:#fafafa;color:#444;outline:0;position:relative;z-index:1;border-left-color:rgba(0,0,0,.1);border-right-color:rgba(0,0,0,.1);border-top-color:rgba(0,0,0,.1)}.uk-navbar-nav>li>a:active{background-color:#eee;color:#444;border-left-color:rgba(0,0,0,.1);border-right-color:rgba(0,0,0,.1);border-top-color:rgba(0,0,0,.2)}.uk-navbar-nav>li.uk-active>a{background-color:#fafafa;color:#444;border-left-color:rgba(0,0,0,.1);border-right-color:rgba(0,0,0,.1);border-top-color:rgba(0,0,0,.1)}.uk-navbar-nav .uk-navbar-nav-subtitle{line-height:28px}.uk-navbar-nav-subtitle>div{margin-top:-6px;font-size:10px;line-height:12px}.uk-navbar-brand,.uk-navbar-content,.uk-navbar-toggle{-moz-box-sizing:border-box;box-sizing:border-box;display:block;height:41px;padding:0 15px;float:left;margin-top:-1px;text-shadow:0 1px 0 #fff}.uk-navbar-brand:before,.uk-navbar-content:before,.uk-navbar-toggle:before{content:'';display:inline-block;height:100%;vertical-align:middle}.uk-navbar-content+.uk-navbar-content:not(.uk-navbar-center){padding-left:0}.uk-navbar-content>a:not([class]){color:#07d}.uk-navbar-content>a:not([class]):hover{color:#059}.uk-navbar-brand{font-size:18px;color:#444;text-decoration:none}.uk-navbar-brand:focus,.uk-navbar-brand:hover{color:#444;text-decoration:none;outline:0}.uk-navbar-toggle{font-size:18px;color:#444;text-decoration:none}.uk-navbar-toggle:focus,.uk-navbar-toggle:hover{color:#444;text-decoration:none;outline:0}.uk-navbar-toggle:after{content:"\f0c9";font-family:FontAwesome;vertical-align:middle}.uk-navbar-toggle-alt:after{content:"\f002"}.uk-navbar-center{float:none;text-align:center;max-width:50%;margin-left:auto;margin-right:auto}.uk-navbar-flip{float:right}.uk-navbar-nav:first-child>li:first-child>a{border-top-left-radius:4px;border-bottom-left-radius:4px}.uk-navbar-flip .uk-navbar-nav>li>a{margin-left:0;margin-right:-1px}.uk-navbar-flip .uk-navbar-nav:first-child>li:first-child>a{border-top-left-radius:0;border-bottom-left-radius:0}.uk-navbar-flip .uk-navbar-nav:last-child>li:last-child>a{border-top-right-radius:4px;border-bottom-right-radius:4px}.uk-navbar-attached{border-top-color:transparent;border-left-color:transparent;border-right-color:transparent;border-radius:0}.uk-navbar-attached .uk-navbar-nav>li>a{border-radius:0!important}.uk-subnav{padding:0;list-style:none;font-size:0}.uk-subnav>li{position:relative;font-size:1rem;vertical-align:top}.uk-subnav>li,.uk-subnav>li>a,.uk-subnav>li>span{display:inline-block}.uk-subnav>li:nth-child(n+2){margin-left:10px}.uk-subnav>li>a{color:#07d}.uk-subnav>li>a:hover{color:#059}.uk-subnav>li>span{color:#999}.uk-subnav-line>li:nth-child(n+2):before{content:"";display:inline-block;height:10px;margin-right:10px;border-left:1px solid #ddd}.uk-subnav-pill>li>a,.uk-subnav-pill>li>span{padding:3px 9px;text-decoration:none;border-radius:4px}.uk-subnav-pill>li>a:focus,.uk-subnav-pill>li>a:hover{background:#fafafa;color:#444;outline:0;box-shadow:0 0 0 1px rgba(0,0,0,.15)}.uk-subnav-pill>li.uk-active>a{background:#00a8e6;color:#fff;box-shadow:inset 0 0 5px rgba(0,0,0,.05)}.uk-breadcrumb{padding:0;list-style:none;font-size:0}.uk-breadcrumb>li{font-size:1rem;vertical-align:top}.uk-breadcrumb>li,.uk-breadcrumb>li>a,.uk-breadcrumb>li>span{display:inline-block}.uk-breadcrumb>li:nth-child(n+2):before{content:"/";display:inline-block;margin:0 8px}.uk-breadcrumb>li:not(.uk-active)>span{color:#999}.uk-pagination{padding:0;list-style:none;text-align:center;font-size:0}.uk-pagination:after,.uk-pagination:before{content:"";display:table}.uk-pagination:after{clear:both}.uk-pagination>li{display:inline-block;font-size:1rem;vertical-align:top}.uk-pagination>li:nth-child(n+2){margin-left:5px}.uk-pagination>li>a,.uk-pagination>li>span{display:inline-block;min-width:16px;padding:3px 5px;line-height:20px;text-decoration:none;-moz-box-sizing:content-box;box-sizing:content-box;text-align:center;border:1px solid rgba(0,0,0,.06);border-radius:4px}.uk-pagination>li>a{background:#f5f5f5;color:#444;text-shadow:0 1px 0 #fff}.uk-pagination>li>a:focus,.uk-pagination>li>a:hover{background-color:#fafafa;color:#444;outline:0;border-color:rgba(0,0,0,.16)}.uk-pagination>li>a:active{background-color:#eee;color:#444}.uk-pagination>.uk-active>span{background:#00a8e6;color:#fff;border-color:transparent;box-shadow:inset 0 0 5px rgba(0,0,0,.05);text-shadow:0 -1px 0 rgba(0,0,0,.1)}.uk-pagination>.uk-disabled>span{background-color:#fafafa;color:#999;border:1px solid rgba(0,0,0,.06);text-shadow:0 1px 0 #fff}.uk-pagination-previous{float:left}.uk-pagination-next{float:right}.uk-pagination-left{text-align:left}.uk-pagination-right{text-align:right}.uk-tab{margin:0;padding:0;list-style:none;border-bottom:1px solid #ddd}.uk-tab:after,.uk-tab:before{content:"";display:table}.uk-tab:after{clear:both}.uk-tab>li{margin-bottom:-1px;float:left;position:relative}.uk-tab>li>a{display:block;padding:8px 12px;border:1px solid transparent;border-bottom-width:0;color:#07d;text-decoration:none;border-radius:4px 4px 0 0;text-shadow:0 1px 0 #fff}.uk-tab>li:nth-child(n+2)>a{margin-left:5px}.uk-tab>li.uk-open>a,.uk-tab>li>a:focus,.uk-tab>li>a:hover{border-color:rgba(0,0,0,.06);background:#f5f5f5;color:#059;outline:0}.uk-tab>li.uk-open:not(.uk-active)>a,.uk-tab>li:not(.uk-active)>a:focus,.uk-tab>li:not(.uk-active)>a:hover{margin-bottom:1px;padding-bottom:7px}.uk-tab>li.uk-active>a{border-color:#ddd #ddd transparent;background:#fff;color:#444}.uk-tab>li.uk-disabled>a{color:#999;cursor:auto}.uk-tab>li.uk-disabled.uk-active>a,.uk-tab>li.uk-disabled>a:focus,.uk-tab>li.uk-disabled>a:hover{background:0 0;border-color:transparent}.uk-tab-flip>li{float:right}.uk-tab-flip>li:nth-child(n+2)>a{margin-left:0;margin-right:5px}.uk-tab>li.uk-tab-responsive>a{margin-left:0;margin-right:0}.uk-tab-responsive>a:before{content:"\f0c9\00a0";font-family:FontAwesome}.uk-tab-center{border-bottom:1px solid #ddd}.uk-tab-center-bottom{border-bottom:none;border-top:1px solid #ddd}.uk-tab-center:after,.uk-tab-center:before{content:"";display:table}.uk-tab-center:after{clear:both}.uk-tab-center .uk-tab{position:relative;right:50%;border:none;float:right}.uk-tab-center .uk-tab>li{position:relative;right:-50%}.uk-tab-center .uk-tab>li>a{text-align:center}.uk-tab-bottom{border-top:1px solid #ddd;border-bottom:none}.uk-tab-bottom>li{margin-top:-1px;margin-bottom:0}.uk-tab-bottom>li>a{padding-top:8px;padding-bottom:8px;border-bottom-width:1px;border-top-width:0}.uk-tab-bottom>li.uk-open:not(.uk-active)>a,.uk-tab-bottom>li:not(.uk-active)>a:focus,.uk-tab-bottom>li:not(.uk-active)>a:hover{margin-bottom:0;margin-top:1px;padding-bottom:8px;padding-top:7px}.uk-tab-bottom>li.uk-active>a{border-top-color:transparent;border-bottom-color:#ddd}.uk-tab-grid{margin-left:-5px;border-bottom:none;position:relative;z-index:0}.uk-tab-grid:before{display:block;position:absolute;left:5px;right:0;bottom:-1px;border-top:1px solid #ddd;z-index:-1}.uk-tab-grid>li:first-child>a{margin-left:5px}.uk-tab-grid>li>a{text-align:center}.uk-tab-grid.uk-tab-bottom{border-top:none}.uk-tab-grid.uk-tab-bottom:before{top:-1px;bottom:auto}@media (min-width:768px){.uk-tab-left,.uk-tab-right{border-bottom:none}.uk-tab-left>li,.uk-tab-right>li{margin-bottom:0;float:none}.uk-tab-left>li>a,.uk-tab-right>li>a{padding-top:8px;padding-bottom:8px}.uk-tab-left>li:nth-child(n+2)>a,.uk-tab-right>li:nth-child(n+2)>a{margin-left:0;margin-top:5px}.uk-tab-left>li.uk-active>a,.uk-tab-right>li.uk-active>a{border-color:#ddd}.uk-tab-left{border-right:1px solid #ddd}.uk-tab-left>li{margin-right:-1px}.uk-tab-left>li>a{border-bottom-width:1px;border-right-width:0}.uk-tab-left>li:not(.uk-active)>a:focus,.uk-tab-left>li:not(.uk-active)>a:hover{margin-bottom:0;margin-right:1px;padding-bottom:8px;padding-right:11px}.uk-tab-left>li.uk-active>a{border-right-color:transparent}.uk-tab-right{border-left:1px solid #ddd}.uk-tab-right>li{margin-left:-1px}.uk-tab-right>li>a{border-bottom-width:1px;border-left-width:0}.uk-tab-right>li:not(.uk-active)>a:focus,.uk-tab-right>li:not(.uk-active)>a:hover{margin-bottom:0;margin-left:1px;padding-bottom:8px;padding-left:11px}.uk-tab-right>li.uk-active>a{border-left-color:transparent}}.uk-tab-bottom>li>a{border-radius:0 0 4px 4px}@media (min-width:768px){.uk-tab-left>li>a{border-radius:4px 0 0 4px}.uk-tab-right>li>a{border-radius:0 4px 4px 0}}.uk-list{padding:0;list-style:none}.uk-list>li:after,.uk-list>li:before{content:"";display:table}.uk-list>li:after{clear:both}.uk-list>li>:last-child{margin-bottom:0}.uk-list ul{margin:0;padding-left:20px;list-style:none}.uk-list-line>li:nth-child(n+2){margin-top:5px;padding-top:5px;border-top:1px solid #ddd}.uk-list-striped>li{padding:5px;border-bottom:1px solid #ddd}.uk-list-striped>li:nth-of-type(odd){background:#fafafa}.uk-list-space>li:nth-child(n+2){margin-top:10px}.uk-list-striped>li:first-child{border-top:1px solid #ddd}@media (min-width:768px){.uk-description-list-horizontal{overflow:hidden}.uk-description-list-horizontal>dt{width:160px;float:left;clear:both;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.uk-description-list-horizontal>dd{margin-left:180px}}.uk-description-list-line>dt{font-weight:400}.uk-description-list-line>dt:nth-child(n+2){margin-top:5px;padding-top:5px;border-top:1px solid #ddd}.uk-description-list-line>dd{color:#999}.uk-table{border-collapse:collapse;border-spacing:0;width:100%;margin-bottom:15px}*+.uk-table{margin-top:15px}.uk-table td,.uk-table th{padding:8px;border-bottom:1px solid #ddd}.uk-table th{text-align:left}.uk-table td{vertical-align:top}.uk-table thead th{vertical-align:bottom}.uk-table caption,.uk-table tfoot{font-size:12px;font-style:italic}.uk-table caption{text-align:left;color:#999}.uk-table-middle,.uk-table-middle td{vertical-align:middle!important}.uk-table-striped tbody tr:nth-of-type(odd){background:#fafafa}.uk-table-condensed td{padding:4px 8px}.uk-table-hover tbody tr:hover{background:#f0f0f0}.uk-form input,.uk-form select,.uk-form textarea{-moz-box-sizing:border-box;box-sizing:border-box;margin:0;border-radius:0;font:inherit;color:inherit}.uk-form select{text-transform:none}.uk-form optgroup{font:inherit;font-weight:700}.uk-form input::-moz-focus-inner{border:0;padding:0}.uk-form input[type=checkbox],.uk-form input[type=radio]{padding:0}.uk-form input[type=checkbox]:not(:disabled),.uk-form input[type=radio]:not(:disabled){cursor:pointer}.uk-form input:not([type]),.uk-form input[type=datetime],.uk-form input[type=email],.uk-form input[type=number],.uk-form input[type=password],.uk-form input[type=search],.uk-form input[type=tel],.uk-form input[type=text],.uk-form input[type=url],.uk-form textarea{-webkit-appearance:none}.uk-form input[type=search]::-webkit-search-cancel-button,.uk-form input[type=search]::-webkit-search-decoration{-webkit-appearance:none}.uk-form input[type=number]::-webkit-inner-spin-button,.uk-form input[type=number]::-webkit-outer-spin-button{height:auto}.uk-form fieldset{border:none;margin:0;padding:0}.uk-form textarea{overflow:auto;vertical-align:top}.uk-form ::-moz-placeholder{opacity:1}.uk-form :invalid{box-shadow:none}.uk-form input:not([type=radio]):not([type=checkbox]),.uk-form select{vertical-align:middle}.uk-form>:last-child{margin-bottom:0}.uk-form input:not([type]),.uk-form input[type=color],.uk-form input[type=date],.uk-form input[type=datetime-local],.uk-form input[type=datetime],.uk-form input[type=email],.uk-form input[type=month],.uk-form input[type=number],.uk-form input[type=password],.uk-form input[type=search],.uk-form input[type=tel],.uk-form input[type=text],.uk-form input[type=time],.uk-form input[type=url],.uk-form input[type=week],.uk-form select,.uk-form textarea{height:30px;max-width:100%;padding:4px 6px;border:1px solid #ddd;background:#fff;color:#444;-webkit-transition:all linear .2s;transition:all linear .2s;border-radius:4px}.uk-form input:not([type]):focus,.uk-form input[type=color]:focus,.uk-form input[type=date]:focus,.uk-form input[type=datetime-local]:focus,.uk-form input[type=datetime]:focus,.uk-form input[type=email]:focus,.uk-form input[type=month]:focus,.uk-form input[type=number]:focus,.uk-form input[type=password]:focus,.uk-form input[type=search]:focus,.uk-form input[type=tel]:focus,.uk-form input[type=text]:focus,.uk-form input[type=time]:focus,.uk-form input[type=url]:focus,.uk-form input[type=week]:focus,.uk-form select:focus,.uk-form textarea:focus{border-color:#99baca;outline:0;background:#f5fbfe;color:#444}.uk-form input:not([type]):disabled,.uk-form input[type=color]:disabled,.uk-form input[type=date]:disabled,.uk-form input[type=datetime-local]:disabled,.uk-form input[type=datetime]:disabled,.uk-form input[type=email]:disabled,.uk-form input[type=month]:disabled,.uk-form input[type=number]:disabled,.uk-form input[type=password]:disabled,.uk-form input[type=search]:disabled,.uk-form input[type=tel]:disabled,.uk-form input[type=text]:disabled,.uk-form input[type=time]:disabled,.uk-form input[type=url]:disabled,.uk-form input[type=week]:disabled,.uk-form select:disabled,.uk-form textarea:disabled{border-color:#ddd;background-color:#fafafa;color:#999}.uk-form :-ms-input-placeholder{color:#999!important}.uk-form ::-moz-placeholder{color:#999}.uk-form ::-webkit-input-placeholder{color:#999}.uk-form :disabled:-ms-input-placeholder{color:#999!important}.uk-form :disabled::-moz-placeholder{color:#999}.uk-form :disabled::-webkit-input-placeholder{color:#999}.uk-form legend{width:100%;border:0;padding:0 0 15px;font-size:18px;line-height:30px}.uk-form legend:after{content:"";display:block;border-bottom:1px solid #ddd;width:100%}input:not([type]).uk-form-small,input[type].uk-form-small,select.uk-form-small,textarea.uk-form-small{height:25px;padding:3px;font-size:12px}input:not([type]).uk-form-large,input[type].uk-form-large,select.uk-form-large,textarea.uk-form-large{height:40px;padding:8px 6px;font-size:16px}.uk-form select[multiple],.uk-form select[size],.uk-form textarea{height:auto}.uk-form-danger{border-color:#dc8d99!important;background:#fff7f8!important;color:#d85030!important}.uk-form-success{border-color:#8ec73b!important;background:#fafff2!important;color:#659f13!important}.uk-form-blank{border-color:transparent!important;border-style:dashed!important;background:none!important}.uk-form-blank:focus{border-color:#ddd!important}input.uk-form-width-mini{width:40px}select.uk-form-width-mini{width:65px}.uk-form-width-small{width:130px}.uk-form-width-medium{width:200px}.uk-form-width-large{width:500px}.uk-form-row:after,.uk-form-row:before{content:"";display:table}.uk-form-row:after{clear:both}.uk-form-row+.uk-form-row{margin-top:15px}.uk-form-help-inline{display:inline-block;margin:0 0 0 10px}.uk-form-help-block{margin:5px 0 0}.uk-form-controls>:first-child{margin-top:0}.uk-form-controls>:last-child{margin-bottom:0}.uk-form-controls-condensed{margin:5px 0}.uk-form-stacked .uk-form-label{display:block;margin-bottom:5px;font-weight:700}@media (max-width:959px){.uk-form-horizontal .uk-form-label{display:block;margin-bottom:5px;font-weight:700}}@media (min-width:960px){.uk-form-horizontal .uk-form-label{width:200px;margin-top:5px;float:left}.uk-form-horizontal .uk-form-controls{margin-left:215px}.uk-form-horizontal .uk-form-controls-text{padding-top:5px}}.uk-form-icon{display:inline-block;position:relative;max-width:100%}.uk-form-icon>[class*=uk-icon-]{position:absolute;top:50%;width:30px;margin-top:-7px;font-size:14px;color:#999;text-align:center;pointer-events:none}.uk-form-icon:not(.uk-form-icon-flip)>input{padding-left:30px!important}.uk-form-icon-flip>[class*=uk-icon-]{right:0}.uk-form-icon-flip>input{padding-right:30px!important}.uk-button::-moz-focus-inner{border:0;padding:0}.uk-button{-webkit-appearance:none;margin:0;border:none;overflow:visible;font:inherit;color:#444;text-transform:none;display:inline-block;-moz-box-sizing:border-box;box-sizing:border-box;padding:0 12px;background:#f5f5f5;vertical-align:middle;line-height:28px;min-height:30px;font-size:1rem;text-decoration:none;text-align:center;border:1px solid rgba(0,0,0,.06);border-radius:4px;text-shadow:0 1px 0 #fff}.uk-button:not(:disabled){cursor:pointer}.uk-button:focus,.uk-button:hover{background-color:#fafafa;color:#444;outline:0;text-decoration:none;border-color:rgba(0,0,0,.16)}.uk-button.uk-active,.uk-button:active{background-color:#eee;color:#444}.uk-button-primary{background-color:#00a8e6;color:#fff}.uk-button-primary:focus,.uk-button-primary:hover{background-color:#35b3ee;color:#fff}.uk-button-primary.uk-active,.uk-button-primary:active{background-color:#0091ca;color:#fff}.uk-button-success{background-color:#8cc14c;color:#fff}.uk-button-success:focus,.uk-button-success:hover{background-color:#8ec73b;color:#fff}.uk-button-success.uk-active,.uk-button-success:active{background-color:#72ae41;color:#fff}.uk-button-danger{background-color:#da314b;color:#fff}.uk-button-danger:focus,.uk-button-danger:hover{background-color:#e4354f;color:#fff}.uk-button-danger.uk-active,.uk-button-danger:active{background-color:#c91032;color:#fff}.uk-button:disabled{background-color:#fafafa;color:#999;border-color:rgba(0,0,0,.06);box-shadow:none;text-shadow:0 1px 0 #fff}.uk-button-link,.uk-button-link.uk-active,.uk-button-link:active,.uk-button-link:disabled,.uk-button-link:focus,.uk-button-link:hover{border-color:transparent;background:0 0;box-shadow:none;text-shadow:none}.uk-button-link{color:#07d}.uk-button-link.uk-active,.uk-button-link:active,.uk-button-link:focus,.uk-button-link:hover{color:#059;text-decoration:underline}.uk-button-link:disabled{color:#999}.uk-button-link:focus{outline:dotted 1px}.uk-button-mini{min-height:20px;padding:0 6px;line-height:18px;font-size:11px}.uk-button-small{min-height:25px;padding:0 10px;line-height:23px;font-size:12px}.uk-button-large{min-height:40px;padding:0 15px;line-height:38px;font-size:16px;border-radius:5px}.uk-button-group{display:inline-block;vertical-align:middle;position:relative;font-size:0;white-space:nowrap}.uk-button-group>*{display:inline-block}.uk-button-group .uk-button{vertical-align:top}.uk-button-dropdown{display:inline-block;vertical-align:middle;position:relative}.uk-button-danger,.uk-button-primary,.uk-button-success{box-shadow:inset 0 0 5px rgba(0,0,0,.05);text-shadow:0 -1px 0 rgba(0,0,0,.1)}.uk-button-danger:focus,.uk-button-danger:hover,.uk-button-primary:focus,.uk-button-primary:hover,.uk-button-success:focus,.uk-button-success:hover{border-color:rgba(0,0,0,.21)}.uk-button-group>.uk-button:not(:first-child):not(:last-child),.uk-button-group>div:not(:first-child):not(:last-child) .uk-button{border-left-color:rgba(0,0,0,.1);border-right-color:rgba(0,0,0,.1);border-radius:0}.uk-button-group>.uk-button:first-child,.uk-button-group>div:first-child .uk-button{border-right-color:rgba(0,0,0,.1);border-top-right-radius:0;border-bottom-right-radius:0}.uk-button-group>.uk-button:last-child,.uk-button-group>div:last-child .uk-button{border-left-color:rgba(0,0,0,.1);border-top-left-radius:0;border-bottom-left-radius:0}.uk-button-group>.uk-button:nth-child(n+2),.uk-button-group>div:nth-child(n+2) .uk-button{margin-left:-1px}.uk-button-group .uk-button:active,.uk-button-group .uk-button:hover{position:relative}@font-face{font-family:FontAwesome;src:url(../fonts/fontawesome-webfont.eot);src:url(../fonts/fontawesome-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/fontawesome-webfont.woff) format("woff"),url(../fonts/fontawesome-webfont.ttf) format("truetype");font-weight:400;font-style:normal}[class*=uk-icon-]{font-family:FontAwesome;display:inline-block;font-weight:400;font-style:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.uk-icon-small:before{font-size:150%;vertical-align:-10%}.uk-icon-medium:before{font-size:200%;vertical-align:-16%}.uk-icon-large:before{font-size:250%;vertical-align:-22%}.uk-icon-spin{display:inline-block;-webkit-animation:uk-rotate 2s infinite linear;animation:uk-rotate 2s infinite linear}.uk-icon-button{-moz-box-sizing:border-box;box-sizing:border-box;display:inline-block;width:35px;height:35px;border-radius:100%;background:#f5f5f5;line-height:35px;color:#444;font-size:18px;text-align:center;border:1px solid #e7e7e7;text-shadow:0 1px 0 #fff}.uk-icon-button:focus,.uk-icon-button:hover{background-color:#fafafa;color:#444;text-decoration:none;outline:0;border-color:#d3d3d3}.uk-icon-button:active{background-color:#eee;color:#444}.uk-icon-glass:before{content:"\f000"}.uk-icon-music:before{content:"\f001"}.uk-icon-search:before{content:"\f002"}.uk-icon-envelope-o:before{content:"\f003"}.uk-icon-heart:before{content:"\f004"}.uk-icon-star:before{content:"\f005"}.uk-icon-star-o:before{content:"\f006"}.uk-icon-user:before{content:"\f007"}.uk-icon-film:before{content:"\f008"}.uk-icon-th-large:before{content:"\f009"}.uk-icon-th:before{content:"\f00a"}.uk-icon-th-list:before{content:"\f00b"}.uk-icon-check:before{content:"\f00c"}.uk-icon-close:before,.uk-icon-remove:before,.uk-icon-times:before{content:"\f00d"}.uk-icon-search-plus:before{content:"\f00e"}.uk-icon-search-minus:before{content:"\f010"}.uk-icon-power-off:before{content:"\f011"}.uk-icon-signal:before{content:"\f012"}.uk-icon-cog:before,.uk-icon-gear:before{content:"\f013"}.uk-icon-trash-o:before{content:"\f014"}.uk-icon-home:before{content:"\f015"}.uk-icon-file-o:before{content:"\f016"}.uk-icon-clock-o:before{content:"\f017"}.uk-icon-road:before{content:"\f018"}.uk-icon-download:before{content:"\f019"}.uk-icon-arrow-circle-o-down:before{content:"\f01a"}.uk-icon-arrow-circle-o-up:before{content:"\f01b"}.uk-icon-inbox:before{content:"\f01c"}.uk-icon-play-circle-o:before{content:"\f01d"}.uk-icon-repeat:before,.uk-icon-rotate-right:before{content:"\f01e"}.uk-icon-refresh:before{content:"\f021"}.uk-icon-list-alt:before{content:"\f022"}.uk-icon-lock:before{content:"\f023"}.uk-icon-flag:before{content:"\f024"}.uk-icon-headphones:before{content:"\f025"}.uk-icon-volume-off:before{content:"\f026"}.uk-icon-volume-down:before{content:"\f027"}.uk-icon-volume-up:before{content:"\f028"}.uk-icon-qrcode:before{content:"\f029"}.uk-icon-barcode:before{content:"\f02a"}.uk-icon-tag:before{content:"\f02b"}.uk-icon-tags:before{content:"\f02c"}.uk-icon-book:before{content:"\f02d"}.uk-icon-bookmark:before{content:"\f02e"}.uk-icon-print:before{content:"\f02f"}.uk-icon-camera:before{content:"\f030"}.uk-icon-font:before{content:"\f031"}.uk-icon-bold:before{content:"\f032"}.uk-icon-italic:before{content:"\f033"}.uk-icon-text-height:before{content:"\f034"}.uk-icon-text-width:before{content:"\f035"}.uk-icon-align-left:before{content:"\f036"}.uk-icon-align-center:before{content:"\f037"}.uk-icon-align-right:before{content:"\f038"}.uk-icon-align-justify:before{content:"\f039"}.uk-icon-list:before{content:"\f03a"}.uk-icon-dedent:before,.uk-icon-outdent:before{content:"\f03b"}.uk-icon-indent:before{content:"\f03c"}.uk-icon-video-camera:before{content:"\f03d"}.uk-icon-image:before,.uk-icon-photo:before,.uk-icon-picture-o:before{content:"\f03e"}.uk-icon-pencil:before{content:"\f040"}.uk-icon-map-marker:before{content:"\f041"}.uk-icon-adjust:before{content:"\f042"}.uk-icon-tint:before{content:"\f043"}.uk-icon-edit:before,.uk-icon-pencil-square-o:before{content:"\f044"}.uk-icon-share-square-o:before{content:"\f045"}.uk-icon-check-square-o:before{content:"\f046"}.uk-icon-arrows:before{content:"\f047"}.uk-icon-step-backward:before{content:"\f048"}.uk-icon-fast-backward:before{content:"\f049"}.uk-icon-backward:before{content:"\f04a"}.uk-icon-play:before{content:"\f04b"}.uk-icon-pause:before{content:"\f04c"}.uk-icon-stop:before{content:"\f04d"}.uk-icon-forward:before{content:"\f04e"}.uk-icon-fast-forward:before{content:"\f050"}.uk-icon-step-forward:before{content:"\f051"}.uk-icon-eject:before{content:"\f052"}.uk-icon-chevron-left:before{content:"\f053"}.uk-icon-chevron-right:before{content:"\f054"}.uk-icon-plus-circle:before{content:"\f055"}.uk-icon-minus-circle:before{content:"\f056"}.uk-icon-times-circle:before{content:"\f057"}.uk-icon-check-circle:before{content:"\f058"}.uk-icon-question-circle:before{content:"\f059"}.uk-icon-info-circle:before{content:"\f05a"}.uk-icon-crosshairs:before{content:"\f05b"}.uk-icon-times-circle-o:before{content:"\f05c"}.uk-icon-check-circle-o:before{content:"\f05d"}.uk-icon-ban:before{content:"\f05e"}.uk-icon-arrow-left:before{content:"\f060"}.uk-icon-arrow-right:before{content:"\f061"}.uk-icon-arrow-up:before{content:"\f062"}.uk-icon-arrow-down:before{content:"\f063"}.uk-icon-mail-forward:before,.uk-icon-share:before{content:"\f064"}.uk-icon-expand:before{content:"\f065"}.uk-icon-compress:before{content:"\f066"}.uk-icon-plus:before{content:"\f067"}.uk-icon-minus:before{content:"\f068"}.uk-icon-asterisk:before{content:"\f069"}.uk-icon-exclamation-circle:before{content:"\f06a"}.uk-icon-gift:before{content:"\f06b"}.uk-icon-leaf:before{content:"\f06c"}.uk-icon-fire:before{content:"\f06d"}.uk-icon-eye:before{content:"\f06e"}.uk-icon-eye-slash:before{content:"\f070"}.uk-icon-exclamation-triangle:before,.uk-icon-warning:before{content:"\f071"}.uk-icon-plane:before{content:"\f072"}.uk-icon-calendar:before{content:"\f073"}.uk-icon-random:before{content:"\f074"}.uk-icon-comment:before{content:"\f075"}.uk-icon-magnet:before{content:"\f076"}.uk-icon-chevron-up:before{content:"\f077"}.uk-icon-chevron-down:before{content:"\f078"}.uk-icon-retweet:before{content:"\f079"}.uk-icon-shopping-cart:before{content:"\f07a"}.uk-icon-folder:before{content:"\f07b"}.uk-icon-folder-open:before{content:"\f07c"}.uk-icon-arrows-v:before{content:"\f07d"}.uk-icon-arrows-h:before{content:"\f07e"}.uk-icon-bar-chart-o:before,.uk-icon-bar-chart:before{content:"\f080"}.uk-icon-twitter-square:before{content:"\f081"}.uk-icon-facebook-square:before{content:"\f082"}.uk-icon-camera-retro:before{content:"\f083"}.uk-icon-key:before{content:"\f084"}.uk-icon-cogs:before,.uk-icon-gears:before{content:"\f085"}.uk-icon-comments:before{content:"\f086"}.uk-icon-thumbs-o-up:before{content:"\f087"}.uk-icon-thumbs-o-down:before{content:"\f088"}.uk-icon-star-half:before{content:"\f089"}.uk-icon-heart-o:before{content:"\f08a"}.uk-icon-sign-out:before{content:"\f08b"}.uk-icon-linkedin-square:before{content:"\f08c"}.uk-icon-thumb-tack:before{content:"\f08d"}.uk-icon-external-link:before{content:"\f08e"}.uk-icon-sign-in:before{content:"\f090"}.uk-icon-trophy:before{content:"\f091"}.uk-icon-github-square:before{content:"\f092"}.uk-icon-upload:before{content:"\f093"}.uk-icon-lemon-o:before{content:"\f094"}.uk-icon-phone:before{content:"\f095"}.uk-icon-square-o:before{content:"\f096"}.uk-icon-bookmark-o:before{content:"\f097"}.uk-icon-phone-square:before{content:"\f098"}.uk-icon-twitter:before{content:"\f099"}.uk-icon-facebook:before{content:"\f09a"}.uk-icon-github:before{content:"\f09b"}.uk-icon-unlock:before{content:"\f09c"}.uk-icon-credit-card:before{content:"\f09d"}.uk-icon-rss:before{content:"\f09e"}.uk-icon-hdd-o:before{content:"\f0a0"}.uk-icon-bullhorn:before{content:"\f0a1"}.uk-icon-bell:before{content:"\f0f3"}.uk-icon-certificate:before{content:"\f0a3"}.uk-icon-hand-o-right:before{content:"\f0a4"}.uk-icon-hand-o-left:before{content:"\f0a5"}.uk-icon-hand-o-up:before{content:"\f0a6"}.uk-icon-hand-o-down:before{content:"\f0a7"}.uk-icon-arrow-circle-left:before{content:"\f0a8"}.uk-icon-arrow-circle-right:before{content:"\f0a9"}.uk-icon-arrow-circle-up:before{content:"\f0aa"}.uk-icon-arrow-circle-down:before{content:"\f0ab"}.uk-icon-globe:before{content:"\f0ac"}.uk-icon-wrench:before{content:"\f0ad"}.uk-icon-tasks:before{content:"\f0ae"}.uk-icon-filter:before{content:"\f0b0"}.uk-icon-briefcase:before{content:"\f0b1"}.uk-icon-arrows-alt:before{content:"\f0b2"}.uk-icon-group:before,.uk-icon-users:before{content:"\f0c0"}.uk-icon-chain:before,.uk-icon-link:before{content:"\f0c1"}.uk-icon-cloud:before{content:"\f0c2"}.uk-icon-flask:before{content:"\f0c3"}.uk-icon-cut:before,.uk-icon-scissors:before{content:"\f0c4"}.uk-icon-copy:before,.uk-icon-files-o:before{content:"\f0c5"}.uk-icon-paperclip:before{content:"\f0c6"}.uk-icon-floppy-o:before,.uk-icon-save:before{content:"\f0c7"}.uk-icon-square:before{content:"\f0c8"}.uk-icon-bars:before,.uk-icon-navicon:before,.uk-icon-reorder:before{content:"\f0c9"}.uk-icon-list-ul:before{content:"\f0ca"}.uk-icon-list-ol:before{content:"\f0cb"}.uk-icon-strikethrough:before{content:"\f0cc"}.uk-icon-underline:before{content:"\f0cd"}.uk-icon-table:before{content:"\f0ce"}.uk-icon-magic:before{content:"\f0d0"}.uk-icon-truck:before{content:"\f0d1"}.uk-icon-pinterest:before{content:"\f0d2"}.uk-icon-pinterest-square:before{content:"\f0d3"}.uk-icon-google-plus-square:before{content:"\f0d4"}.uk-icon-google-plus:before{content:"\f0d5"}.uk-icon-money:before{content:"\f0d6"}.uk-icon-caret-down:before{content:"\f0d7"}.uk-icon-caret-up:before{content:"\f0d8"}.uk-icon-caret-left:before{content:"\f0d9"}.uk-icon-caret-right:before{content:"\f0da"}.uk-icon-columns:before{content:"\f0db"}.uk-icon-sort:before,.uk-icon-unsorted:before{content:"\f0dc"}.uk-icon-sort-desc:before,.uk-icon-sort-down:before{content:"\f0dd"}.uk-icon-sort-asc:before,.uk-icon-sort-up:before{content:"\f0de"}.uk-icon-envelope:before{content:"\f0e0"}.uk-icon-linkedin:before{content:"\f0e1"}.uk-icon-rotate-left:before,.uk-icon-undo:before{content:"\f0e2"}.uk-icon-gavel:before,.uk-icon-legal:before{content:"\f0e3"}.uk-icon-dashboard:before,.uk-icon-tachometer:before{content:"\f0e4"}.uk-icon-comment-o:before{content:"\f0e5"}.uk-icon-comments-o:before{content:"\f0e6"}.uk-icon-bolt:before,.uk-icon-flash:before{content:"\f0e7"}.uk-icon-sitemap:before{content:"\f0e8"}.uk-icon-umbrella:before{content:"\f0e9"}.uk-icon-clipboard:before,.uk-icon-paste:before{content:"\f0ea"}.uk-icon-lightbulb-o:before{content:"\f0eb"}.uk-icon-exchange:before{content:"\f0ec"}.uk-icon-cloud-download:before{content:"\f0ed"}.uk-icon-cloud-upload:before{content:"\f0ee"}.uk-icon-user-md:before{content:"\f0f0"}.uk-icon-stethoscope:before{content:"\f0f1"}.uk-icon-suitcase:before{content:"\f0f2"}.uk-icon-bell-o:before{content:"\f0a2"}.uk-icon-coffee:before{content:"\f0f4"}.uk-icon-cutlery:before{content:"\f0f5"}.uk-icon-file-text-o:before{content:"\f0f6"}.uk-icon-building-o:before{content:"\f0f7"}.uk-icon-hospital-o:before{content:"\f0f8"}.uk-icon-ambulance:before{content:"\f0f9"}.uk-icon-medkit:before{content:"\f0fa"}.uk-icon-fighter-jet:before{content:"\f0fb"}.uk-icon-beer:before{content:"\f0fc"}.uk-icon-h-square:before{content:"\f0fd"}.uk-icon-plus-square:before{content:"\f0fe"}.uk-icon-angle-double-left:before{content:"\f100"}.uk-icon-angle-double-right:before{content:"\f101"}.uk-icon-angle-double-up:before{content:"\f102"}.uk-icon-angle-double-down:before{content:"\f103"}.uk-icon-angle-left:before{content:"\f104"}.uk-icon-angle-right:before{content:"\f105"}.uk-icon-angle-up:before{content:"\f106"}.uk-icon-angle-down:before{content:"\f107"}.uk-icon-desktop:before{content:"\f108"}.uk-icon-laptop:before{content:"\f109"}.uk-icon-tablet:before{content:"\f10a"}.uk-icon-mobile-phone:before,.uk-icon-mobile:before{content:"\f10b"}.uk-icon-circle-o:before{content:"\f10c"}.uk-icon-quote-left:before{content:"\f10d"}.uk-icon-quote-right:before{content:"\f10e"}.uk-icon-spinner:before{content:"\f110"}.uk-icon-circle:before{content:"\f111"}.uk-icon-mail-reply:before,.uk-icon-reply:before{content:"\f112"}.uk-icon-github-alt:before{content:"\f113"}.uk-icon-folder-o:before{content:"\f114"}.uk-icon-folder-open-o:before{content:"\f115"}.uk-icon-smile-o:before{content:"\f118"}.uk-icon-frown-o:before{content:"\f119"}.uk-icon-meh-o:before{content:"\f11a"}.uk-icon-gamepad:before{content:"\f11b"}.uk-icon-keyboard-o:before{content:"\f11c"}.uk-icon-flag-o:before{content:"\f11d"}.uk-icon-flag-checkered:before{content:"\f11e"}.uk-icon-terminal:before{content:"\f120"}.uk-icon-code:before{content:"\f121"}.uk-icon-mail-reply-all:before,.uk-icon-reply-all:before{content:"\f122"}.uk-icon-star-half-empty:before,.uk-icon-star-half-full:before,.uk-icon-star-half-o:before{content:"\f123"}.uk-icon-location-arrow:before{content:"\f124"}.uk-icon-crop:before{content:"\f125"}.uk-icon-code-fork:before{content:"\f126"}.uk-icon-chain-broken:before,.uk-icon-unlink:before{content:"\f127"}.uk-icon-question:before{content:"\f128"}.uk-icon-info:before{content:"\f129"}.uk-icon-exclamation:before{content:"\f12a"}.uk-icon-superscript:before{content:"\f12b"}.uk-icon-subscript:before{content:"\f12c"}.uk-icon-eraser:before{content:"\f12d"}.uk-icon-puzzle-piece:before{content:"\f12e"}.uk-icon-microphone:before{content:"\f130"}.uk-icon-microphone-slash:before{content:"\f131"}.uk-icon-shield:before{content:"\f132"}.uk-icon-calendar-o:before{content:"\f133"}.uk-icon-fire-extinguisher:before{content:"\f134"}.uk-icon-rocket:before{content:"\f135"}.uk-icon-maxcdn:before{content:"\f136"}.uk-icon-chevron-circle-left:before{content:"\f137"}.uk-icon-chevron-circle-right:before{content:"\f138"}.uk-icon-chevron-circle-up:before{content:"\f139"}.uk-icon-chevron-circle-down:before{content:"\f13a"}.uk-icon-html5:before{content:"\f13b"}.uk-icon-css3:before{content:"\f13c"}.uk-icon-anchor:before{content:"\f13d"}.uk-icon-unlock-alt:before{content:"\f13e"}.uk-icon-bullseye:before{content:"\f140"}.uk-icon-ellipsis-h:before{content:"\f141"}.uk-icon-ellipsis-v:before{content:"\f142"}.uk-icon-rss-square:before{content:"\f143"}.uk-icon-play-circle:before{content:"\f144"}.uk-icon-ticket:before{content:"\f145"}.uk-icon-minus-square:before{content:"\f146"}.uk-icon-minus-square-o:before{content:"\f147"}.uk-icon-level-up:before{content:"\f148"}.uk-icon-level-down:before{content:"\f149"}.uk-icon-check-square:before{content:"\f14a"}.uk-icon-pencil-square:before{content:"\f14b"}.uk-icon-external-link-square:before{content:"\f14c"}.uk-icon-share-square:before{content:"\f14d"}.uk-icon-compass:before{content:"\f14e"}.uk-icon-caret-square-o-down:before,.uk-icon-toggle-down:before{content:"\f150"}.uk-icon-caret-square-o-up:before,.uk-icon-toggle-up:before{content:"\f151"}.uk-icon-caret-square-o-right:before,.uk-icon-toggle-right:before{content:"\f152"}.uk-icon-eur:before,.uk-icon-euro:before{content:"\f153"}.uk-icon-gbp:before{content:"\f154"}.uk-icon-dollar:before,.uk-icon-usd:before{content:"\f155"}.uk-icon-inr:before,.uk-icon-rupee:before{content:"\f156"}.uk-icon-cny:before,.uk-icon-jpy:before,.uk-icon-rmb:before,.uk-icon-yen:before{content:"\f157"}.uk-icon-rouble:before,.uk-icon-rub:before,.uk-icon-ruble:before{content:"\f158"}.uk-icon-krw:before,.uk-icon-won:before{content:"\f159"}.uk-icon-bitcoin:before,.uk-icon-btc:before{content:"\f15a"}.uk-icon-file:before{content:"\f15b"}.uk-icon-file-text:before{content:"\f15c"}.uk-icon-sort-alpha-asc:before{content:"\f15d"}.uk-icon-sort-alpha-desc:before{content:"\f15e"}.uk-icon-sort-amount-asc:before{content:"\f160"}.uk-icon-sort-amount-desc:before{content:"\f161"}.uk-icon-sort-numeric-asc:before{content:"\f162"}.uk-icon-sort-numeric-desc:before{content:"\f163"}.uk-icon-thumbs-up:before{content:"\f164"}.uk-icon-thumbs-down:before{content:"\f165"}.uk-icon-youtube-square:before{content:"\f166"}.uk-icon-youtube:before{content:"\f167"}.uk-icon-xing:before{content:"\f168"}.uk-icon-xing-square:before{content:"\f169"}.uk-icon-youtube-play:before{content:"\f16a"}.uk-icon-dropbox:before{content:"\f16b"}.uk-icon-stack-overflow:before{content:"\f16c"}.uk-icon-instagram:before{content:"\f16d"}.uk-icon-flickr:before{content:"\f16e"}.uk-icon-adn:before{content:"\f170"}.uk-icon-bitbucket:before{content:"\f171"}.uk-icon-bitbucket-square:before{content:"\f172"}.uk-icon-tumblr:before{content:"\f173"}.uk-icon-tumblr-square:before{content:"\f174"}.uk-icon-long-arrow-down:before{content:"\f175"}.uk-icon-long-arrow-up:before{content:"\f176"}.uk-icon-long-arrow-left:before{content:"\f177"}.uk-icon-long-arrow-right:before{content:"\f178"}.uk-icon-apple:before{content:"\f179"}.uk-icon-windows:before{content:"\f17a"}.uk-icon-android:before{content:"\f17b"}.uk-icon-linux:before{content:"\f17c"}.uk-icon-dribbble:before{content:"\f17d"}.uk-icon-skype:before{content:"\f17e"}.uk-icon-foursquare:before{content:"\f180"}.uk-icon-trello:before{content:"\f181"}.uk-icon-female:before{content:"\f182"}.uk-icon-male:before{content:"\f183"}.uk-icon-gittip:before{content:"\f184"}.uk-icon-sun-o:before{content:"\f185"}.uk-icon-moon-o:before{content:"\f186"}.uk-icon-archive:before{content:"\f187"}.uk-icon-bug:before{content:"\f188"}.uk-icon-vk:before{content:"\f189"}.uk-icon-weibo:before{content:"\f18a"}.uk-icon-renren:before{content:"\f18b"}.uk-icon-pagelines:before{content:"\f18c"}.uk-icon-stack-exchange:before{content:"\f18d"}.uk-icon-arrow-circle-o-right:before{content:"\f18e"}.uk-icon-arrow-circle-o-left:before{content:"\f190"}.uk-icon-caret-square-o-left:before,.uk-icon-toggle-left:before{content:"\f191"}.uk-icon-dot-circle-o:before{content:"\f192"}.uk-icon-wheelchair:before{content:"\f193"}.uk-icon-vimeo-square:before{content:"\f194"}.uk-icon-try:before,.uk-icon-turkish-lira:before{content:"\f195"}.uk-icon-plus-square-o:before{content:"\f196"}.uk-icon-space-shuttle:before{content:"\f197"}.uk-icon-slack:before{content:"\f198"}.uk-icon-envelope-square:before{content:"\f199"}.uk-icon-wordpress:before{content:"\f19a"}.uk-icon-openid:before{content:"\f19b"}.uk-icon-bank:before,.uk-icon-institution:before,.uk-icon-university:before{content:"\f19c"}.uk-icon-graduation-cap:before,.uk-icon-mortar-board:before{content:"\f19d"}.uk-icon-yahoo:before{content:"\f19e"}.uk-icon-google:before{content:"\f1a0"}.uk-icon-reddit:before{content:"\f1a1"}.uk-icon-reddit-square:before{content:"\f1a2"}.uk-icon-stumbleupon-circle:before{content:"\f1a3"}.uk-icon-stumbleupon:before{content:"\f1a4"}.uk-icon-delicious:before{content:"\f1a5"}.uk-icon-digg:before{content:"\f1a6"}.uk-icon-pied-piper:before{content:"\f1a7"}.uk-icon-pied-piper-alt:before{content:"\f1a8"}.uk-icon-drupal:before{content:"\f1a9"}.uk-icon-joomla:before{content:"\f1aa"}.uk-icon-language:before{content:"\f1ab"}.uk-icon-fax:before{content:"\f1ac"}.uk-icon-building:before{content:"\f1ad"}.uk-icon-child:before{content:"\f1ae"}.uk-icon-paw:before{content:"\f1b0"}.uk-icon-spoon:before{content:"\f1b1"}.uk-icon-cube:before{content:"\f1b2"}.uk-icon-cubes:before{content:"\f1b3"}.uk-icon-behance:before{content:"\f1b4"}.uk-icon-behance-square:before{content:"\f1b5"}.uk-icon-steam:before{content:"\f1b6"}.uk-icon-steam-square:before{content:"\f1b7"}.uk-icon-recycle:before{content:"\f1b8"}.uk-icon-automobile:before,.uk-icon-car:before{content:"\f1b9"}.uk-icon-cab:before,.uk-icon-taxi:before{content:"\f1ba"}.uk-icon-tree:before{content:"\f1bb"}.uk-icon-spotify:before{content:"\f1bc"}.uk-icon-deviantart:before{content:"\f1bd"}.uk-icon-soundcloud:before{content:"\f1be"}.uk-icon-database:before{content:"\f1c0"}.uk-icon-file-pdf-o:before{content:"\f1c1"}.uk-icon-file-word-o:before{content:"\f1c2"}.uk-icon-file-excel-o:before{content:"\f1c3"}.uk-icon-file-powerpoint-o:before{content:"\f1c4"}.uk-icon-file-image-o:before,.uk-icon-file-photo-o:before,.uk-icon-file-picture-o:before{content:"\f1c5"}.uk-icon-file-archive-o:before,.uk-icon-file-zip-o:before{content:"\f1c6"}.uk-icon-file-audio-o:before,.uk-icon-file-sound-o:before{content:"\f1c7"}.uk-icon-file-movie-o:before,.uk-icon-file-video-o:before{content:"\f1c8"}.uk-icon-file-code-o:before{content:"\f1c9"}.uk-icon-vine:before{content:"\f1ca"}.uk-icon-codepen:before{content:"\f1cb"}.uk-icon-jsfiddle:before{content:"\f1cc"}.uk-icon-life-bouy:before,.uk-icon-life-buoy:before,.uk-icon-life-ring:before,.uk-icon-life-saver:before,.uk-icon-support:before{content:"\f1cd"}.uk-icon-circle-o-notch:before{content:"\f1ce"}.uk-icon-ra:before,.uk-icon-rebel:before{content:"\f1d0"}.uk-icon-empire:before,.uk-icon-ge:before{content:"\f1d1"}.uk-icon-git-square:before{content:"\f1d2"}.uk-icon-git:before{content:"\f1d3"}.uk-icon-hacker-news:before{content:"\f1d4"}.uk-icon-tencent-weibo:before{content:"\f1d5"}.uk-icon-qq:before{content:"\f1d6"}.uk-icon-wechat:before,.uk-icon-weixin:before{content:"\f1d7"}.uk-icon-paper-plane:before,.uk-icon-send:before{content:"\f1d8"}.uk-icon-paper-plane-o:before,.uk-icon-send-o:before{content:"\f1d9"}.uk-icon-history:before{content:"\f1da"}.uk-icon-circle-thin:before{content:"\f1db"}.uk-icon-header:before{content:"\f1dc"}.uk-icon-paragraph:before{content:"\f1dd"}.uk-icon-sliders:before{content:"\f1de"}.uk-icon-share-alt:before{content:"\f1e0"}.uk-icon-share-alt-square:before{content:"\f1e1"}.uk-icon-bomb:before{content:"\f1e2"}.uk-icon-futbol-o:before,.uk-icon-soccer-ball-o:before{content:"\f1e3"}.uk-icon-tty:before{content:"\f1e4"}.uk-icon-binoculars:before{content:"\f1e5"}.uk-icon-plug:before{content:"\f1e6"}.uk-icon-slideshare:before{content:"\f1e7"}.uk-icon-twitch:before{content:"\f1e8"}.uk-icon-yelp:before{content:"\f1e9"}.uk-icon-newspaper-o:before{content:"\f1ea"}.uk-icon-wifi:before{content:"\f1eb"}.uk-icon-calculator:before{content:"\f1ec"}.uk-icon-paypal:before{content:"\f1ed"}.uk-icon-google-wallet:before{content:"\f1ee"}.uk-icon-cc-visa:before{content:"\f1f0"}.uk-icon-cc-mastercard:before{content:"\f1f1"}.uk-icon-cc-discover:before{content:"\f1f2"}.uk-icon-cc-amex:before{content:"\f1f3"}.uk-icon-cc-paypal:before{content:"\f1f4"}.uk-icon-cc-stripe:before{content:"\f1f5"}.uk-icon-bell-slash:before{content:"\f1f6"}.uk-icon-bell-slash-o:before{content:"\f1f7"}.uk-icon-trash:before{content:"\f1f8"}.uk-icon-copyright:before{content:"\f1f9"}.uk-icon-at:before{content:"\f1fa"}.uk-icon-eyedropper:before{content:"\f1fb"}.uk-icon-paint-brush:before{content:"\f1fc"}.uk-icon-birthday-cake:before{content:"\f1fd"}.uk-icon-area-chart:before{content:"\f1fe"}.uk-icon-pie-chart:before{content:"\f200"}.uk-icon-line-chart:before{content:"\f201"}.uk-icon-lastfm:before{content:"\f202"}.uk-icon-lastfm-square:before{content:"\f203"}.uk-icon-toggle-off:before{content:"\f204"}.uk-icon-toggle-on:before{content:"\f205"}.uk-icon-bicycle:before{content:"\f206"}.uk-icon-bus:before{content:"\f207"}.uk-icon-ioxhost:before{content:"\f208"}.uk-icon-angellist:before{content:"\f209"}.uk-icon-cc:before{content:"\f20a"}.uk-icon-ils:before,.uk-icon-shekel:before,.uk-icon-sheqel:before{content:"\f20b"}.uk-icon-meanpath:before{content:"\f20c"}.uk-close::-moz-focus-inner{border:0;padding:0}.uk-close{-webkit-appearance:none;margin:0;border:none;overflow:visible;font:inherit;color:inherit;text-transform:none;padding:0;background:0 0;display:inline-block;-moz-box-sizing:content-box;box-sizing:content-box;width:20px;line-height:20px;text-align:center;vertical-align:middle;opacity:.3}.uk-close:after{display:block;content:"\f00d";font-family:FontAwesome}.uk-close:focus,.uk-close:hover{opacity:.5;outline:0;color:inherit;text-decoration:none;cursor:pointer}.uk-close-alt{padding:2px;border-radius:50%;background:#fff;opacity:1;box-shadow:0 0 0 1px rgba(0,0,0,.1),0 0 6px rgba(0,0,0,.3)}.uk-close-alt:focus,.uk-close-alt:hover{opacity:1}.uk-close-alt:after{opacity:.5}.uk-close-alt:focus:after,.uk-close-alt:hover:after{opacity:.8}.uk-badge{display:inline-block;padding:0 5px;background:#00a8e6;font-size:10px;font-weight:700;line-height:14px;color:#fff;text-align:center;vertical-align:middle;text-transform:none;border:1px solid rgba(0,0,0,.06);border-radius:2px;text-shadow:0 1px 0 rgba(0,0,0,.1)}a.uk-badge:hover{color:#fff}.uk-badge-notification{-moz-box-sizing:border-box;box-sizing:border-box;min-width:18px;border-radius:500px;font-size:12px;line-height:18px}.uk-badge-success{background-color:#8cc14c}.uk-badge-warning{background-color:#faa732}.uk-badge-danger{background-color:#da314b}.uk-alert{margin-bottom:15px;padding:10px;background:#ebf7fd;color:#2d7091;border:1px solid rgba(45,112,145,.3);border-radius:4px;text-shadow:0 1px 0 #fff}*+.uk-alert{margin-top:15px}.uk-alert>:last-child{margin-bottom:0}.uk-alert h1,.uk-alert h2,.uk-alert h3,.uk-alert h4,.uk-alert h5,.uk-alert h6{color:inherit}.uk-alert>.uk-close:first-child{float:right}.uk-alert>.uk-close:first-child+*{margin-top:0}.uk-alert-success{background:#f2fae3;color:#659f13;border-color:rgba(101,159,19,.3)}.uk-alert-warning{background:#fffceb;color:#e28327;border-color:rgba(226,131,39,.3)}.uk-alert-danger{background:#fff1f0;color:#d85030;border-color:rgba(216,80,48,.3)}.uk-alert-large{padding:20px}.uk-alert-large>.uk-close:first-child{margin:-10px -10px 0 0}.uk-thumbnail{display:inline-block;max-width:100%;-moz-box-sizing:border-box;box-sizing:border-box;margin:0;padding:4px;border:1px solid #ddd;background:#fff;border-radius:4px}a.uk-thumbnail:focus,a.uk-thumbnail:hover{border-color:#aaa;background-color:#fff;text-decoration:none;outline:0}.uk-thumbnail-caption{padding-top:4px;text-align:center;color:#444}.uk-thumbnail-mini{width:150px}.uk-thumbnail-small{width:200px}.uk-thumbnail-medium{width:300px}.uk-thumbnail-large{width:400px}.uk-thumbnail-expand,.uk-thumbnail-expand>img{width:100%}.uk-overlay{display:inline-block;position:relative;max-width:100%;vertical-align:middle;overflow:hidden}.uk-overlay>:first-child{margin-bottom:0}.uk-overlay-area{position:absolute;top:0;bottom:0;left:0;right:0;background:rgba(0,0,0,.3);opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear;-webkit-transform:translate3d(0,0,0)}.uk-overlay-toggle.uk-hover .uk-overlay-area,.uk-overlay-toggle:hover .uk-overlay-area,.uk-overlay.uk-hover .uk-overlay-area,.uk-overlay:hover .uk-overlay-area{opacity:1}.uk-overlay-area:empty:before{content:"\f002";position:absolute;top:50%;left:50%;width:50px;height:50px;margin-top:-25px;margin-left:-25px;font-size:50px;line-height:1;font-family:FontAwesome;text-align:center;color:#fff}.uk-overlay-area:not(:empty){font-size:0}.uk-overlay-area:not(:empty):before{content:'';display:inline-block;height:100%;vertical-align:middle}.uk-overlay-area-content{display:inline-block;-moz-box-sizing:border-box;box-sizing:border-box;width:100%;vertical-align:middle;font-size:1rem;text-align:center;padding:0 15px;color:#fff}.uk-overlay-area-content>:last-child{margin-bottom:0}.uk-overlay-area-content a:not([class]),.uk-overlay-area-content a:not([class]):hover{color:inherit}.uk-overlay-caption{position:absolute;bottom:0;left:0;right:0;padding:15px;background:rgba(0,0,0,.5);color:#fff;opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear;-webkit-transform:translate3d(0,0,0)}.uk-overlay-toggle.uk-hover .uk-overlay-caption,.uk-overlay-toggle:hover .uk-overlay-caption,.uk-overlay.uk-hover .uk-overlay-caption,.uk-overlay:hover .uk-overlay-caption{opacity:1}.uk-progress{-moz-box-sizing:border-box;box-sizing:border-box;height:20px;margin-bottom:15px;background:#f5f5f5;overflow:hidden;line-height:20px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.06);border-radius:4px}*+.uk-progress{margin-top:15px}.uk-progress-bar{width:0;height:100%;background:#00a8e6;float:left;-webkit-transition:width .6s ease;transition:width .6s ease;font-size:12px;color:#fff;text-align:center;box-shadow:inset 0 0 5px rgba(0,0,0,.05);text-shadow:0 -1px 0 rgba(0,0,0,.1)}.uk-progress-mini{height:6px}.uk-progress-small{height:12px}.uk-progress-success .uk-progress-bar{background-color:#8cc14c}.uk-progress-warning .uk-progress-bar{background-color:#faa732}.uk-progress-danger .uk-progress-bar{background-color:#da314b}.uk-progress-striped .uk-progress-bar{background-image:-webkit-linear-gradient(-45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(-45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:30px 30px}.uk-progress-striped.uk-active .uk-progress-bar{-webkit-animation:uk-progress-bar-stripes 2s linear infinite;animation:uk-progress-bar-stripes 2s linear infinite}@-webkit-keyframes uk-progress-bar-stripes{0%{background-position:0 0}100%{background-position:30px 0}}@keyframes uk-progress-bar-stripes{0%{background-position:0 0}100%{background-position:30px 0}}.uk-progress-mini,.uk-progress-small{border-radius:500px}[class*=uk-animation-]{-webkit-animation-duration:.5s;animation-duration:.5s;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out;-webkit-animation-fill-mode:both;animation-fill-mode:both}@media screen{[data-uk-scrollspy*=uk-animation-]{opacity:0}}.uk-animation-fade{-webkit-animation-name:uk-fade;animation-name:uk-fade;-webkit-animation-duration:.8s;animation-duration:.8s;-webkit-animation-timing-function:linear!important;animation-timing-function:linear!important}.uk-animation-scale-up{-webkit-animation-name:uk-fade-scale-02;animation-name:uk-fade-scale-02}.uk-animation-scale-down{-webkit-animation-name:uk-fade-scale-18;animation-name:uk-fade-scale-18}.uk-animation-slide-top{-webkit-animation-name:uk-fade-top;animation-name:uk-fade-top}.uk-animation-slide-bottom{-webkit-animation-name:uk-fade-bottom;animation-name:uk-fade-bottom}.uk-animation-slide-left{-webkit-animation-name:uk-fade-left;animation-name:uk-fade-left}.uk-animation-slide-right{-webkit-animation-name:uk-fade-right;animation-name:uk-fade-right}.uk-animation-scale{-webkit-animation-name:uk-scale-12;animation-name:uk-scale-12}.uk-animation-shake{-webkit-animation-name:uk-shake;animation-name:uk-shake}.uk-animation-reverse{-webkit-animation-direction:reverse;animation-direction:reverse;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}.uk-animation-15{-webkit-animation-duration:15s;animation-duration:15s}.uk-animation-top-left{-webkit-transform-origin:0 0;transform-origin:0 0}.uk-animation-top-center{-webkit-transform-origin:50% 0;transform-origin:50% 0}.uk-animation-top-right{-webkit-transform-origin:100% 0;transform-origin:100% 0}.uk-animation-middle-left{-webkit-transform-origin:0 50%;transform-origin:0 50%}.uk-animation-middle-right{-webkit-transform-origin:100% 50%;transform-origin:100% 50%}.uk-animation-bottom-left{-webkit-transform-origin:0 100%;transform-origin:0 100%}.uk-animation-bottom-center{-webkit-transform-origin:50% 100%;transform-origin:50% 100%}.uk-animation-bottom-right{-webkit-transform-origin:100% 100%;transform-origin:100% 100%}.uk-animation-hover:not(:hover),.uk-animation-hover:not(:hover) [class*=uk-animation-],.uk-touch .uk-animation-hover:not(.uk-hover),.uk-touch .uk-animation-hover:not(.uk-hover) [class*=uk-animation-]{-webkit-animation-name:none;animation-name:none}@-webkit-keyframes uk-fade{0%{opacity:0}100%{opacity:1}}@keyframes uk-fade{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes uk-fade-top{0%{opacity:0;-webkit-transform:translateY(-100%)}100%{opacity:1;-webkit-transform:translateY(0)}}@keyframes uk-fade-top{0%{opacity:0;transform:translateY(-100%)}100%{opacity:1;transform:translateY(0)}}@-webkit-keyframes uk-fade-bottom{0%{opacity:0;-webkit-transform:translateY(100%)}100%{opacity:1;-webkit-transform:translateY(0)}}@keyframes uk-fade-bottom{0%{opacity:0;transform:translateY(100%)}100%{opacity:1;transform:translateY(0)}}@-webkit-keyframes uk-fade-left{0%{opacity:0;-webkit-transform:translateX(-100%)}100%{opacity:1;-webkit-transform:translateX(0)}}@keyframes uk-fade-left{0%{opacity:0;transform:translateX(-100%)}100%{opacity:1;transform:translateX(0)}}@-webkit-keyframes uk-fade-right{0%{opacity:0;-webkit-transform:translateX(100%)}100%{opacity:1;-webkit-transform:translateX(0)}}@keyframes uk-fade-right{0%{opacity:0;transform:translateX(100%)}100%{opacity:1;transform:translateX(0)}}@-webkit-keyframes uk-fade-scale-02{0%{opacity:0;-webkit-transform:scale(0.2)}100%{opacity:1;-webkit-transform:scale(1)}}@keyframes uk-fade-scale-02{0%{opacity:0;transform:scale(0.2)}100%{opacity:1;transform:scale(1)}}@-webkit-keyframes uk-fade-scale-15{0%{opacity:0;-webkit-transform:scale(1.5)}100%{opacity:1;-webkit-transform:scale(1)}}@keyframes uk-fade-scale-15{0%{opacity:0;transform:scale(1.5)}100%{opacity:1;transform:scale(1)}}@-webkit-keyframes uk-fade-scale-18{0%{opacity:0;-webkit-transform:scale(1.8)}100%{opacity:1;-webkit-transform:scale(1)}}@keyframes uk-fade-scale-18{0%{opacity:0;transform:scale(1.8)}100%{opacity:1;transform:scale(1)}}@-webkit-keyframes uk-slide-left{0%{-webkit-transform:translateX(-100%)}100%{-webkit-transform:translateX(0)}}@keyframes uk-slide-left{0%{transform:translateX(-100%)}100%{transform:translateX(0)}}@-webkit-keyframes uk-slide-right{0%{-webkit-transform:translateX(100%)}100%{-webkit-transform:translateX(0)}}@keyframes uk-slide-right{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@-webkit-keyframes uk-slide-left-33{0%{-webkit-transform:translateX(33%)}100%{-webkit-transform:translateX(0)}}@keyframes uk-slide-left-33{0%{transform:translateX(33%)}100%{transform:translateX(0)}}@-webkit-keyframes uk-slide-right-33{0%{-webkit-transform:translateX(-33%)}100%{-webkit-transform:translateX(0)}}@keyframes uk-slide-right-33{0%{transform:translateX(-33%)}100%{transform:translateX(0)}}@-webkit-keyframes uk-scale-12{0%{-webkit-transform:scale(1.2)}100%{-webkit-transform:scale(1)}}@keyframes uk-scale-12{0%{transform:scale(1.2)}100%{transform:scale(1)}}@-webkit-keyframes uk-rotate{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@keyframes uk-rotate{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}@-webkit-keyframes uk-shake{0%,100%{-webkit-transform:translateX(0)}10%{-webkit-transform:translateX(-9px)}20%{-webkit-transform:translateX(8px)}30%{-webkit-transform:translateX(-7px)}40%{-webkit-transform:translateX(6px)}50%{-webkit-transform:translateX(-5px)}60%{-webkit-transform:translateX(4px)}70%{-webkit-transform:translateX(-3px)}80%{-webkit-transform:translateX(2px)}90%{-webkit-transform:translateX(-1px)}}@keyframes uk-shake{0%,100%{transform:translateX(0)}10%{transform:translateX(-9px)}20%{transform:translateX(8px)}30%{transform:translateX(-7px)}40%{transform:translateX(6px)}50%{transform:translateX(-5px)}60%{transform:translateX(4px)}70%{transform:translateX(-3px)}80%{transform:translateX(2px)}90%{transform:translateX(-1px)}}@-webkit-keyframes uk-slide-top-fixed{0%{opacity:0;-webkit-transform:translateY(-10px)}100%{opacity:1;-webkit-transform:translateY(0)}}@keyframes uk-slide-top-fixed{0%{opacity:0;transform:translateY(-10px)}100%{opacity:1;transform:translateY(0)}}@-webkit-keyframes uk-slide-bottom-fixed{0%{opacity:0;-webkit-transform:translateY(10px)}100%{opacity:1;-webkit-transform:translateY(0)}}@keyframes uk-slide-bottom-fixed{0%{opacity:0;transform:translateY(10px)}100%{opacity:1;transform:translateY(0)}}.uk-dropdown{display:none;position:absolute;top:100%;left:0;z-index:1020;-moz-box-sizing:border-box;box-sizing:border-box;width:200px;margin-top:5px;padding:15px;background:#fff;color:#444;font-size:1rem;vertical-align:top;border:1px solid #ddd;border-radius:4px}.uk-open>.uk-dropdown{display:block;-webkit-animation:uk-fade .2s ease-in-out;animation:uk-fade .2s ease-in-out;-webkit-transform-origin:0 0;transform-origin:0 0}.uk-dropdown-flip{left:auto;right:0}.uk-dropdown-up{top:auto;bottom:100%;margin-top:auto;margin-bottom:5px}.uk-dropdown .uk-nav{margin:0 -15px}.uk-dropdown-grid>[class*=uk-width-]>.uk-panel+.uk-panel,.uk-grid .uk-dropdown-grid+.uk-dropdown-grid{margin-top:15px}@media (min-width:768px){.uk-dropdown:not(.uk-dropdown-stack)>.uk-dropdown-grid{margin-left:-15px;margin-right:-15px}.uk-dropdown:not(.uk-dropdown-stack)>.uk-dropdown-grid>[class*=uk-width-]{padding-left:15px;padding-right:15px}.uk-dropdown:not(.uk-dropdown-stack)>.uk-dropdown-grid>[class*=uk-width-]:nth-child(n+2){border-left:1px solid #ddd}.uk-dropdown-width-2:not(.uk-dropdown-stack){width:400px}.uk-dropdown-width-3:not(.uk-dropdown-stack){width:600px}.uk-dropdown-width-4:not(.uk-dropdown-stack){width:800px}.uk-dropdown-width-5:not(.uk-dropdown-stack){width:1000px}}@media (max-width:767px){.uk-dropdown-grid>[class*=uk-width-]{width:100%}.uk-dropdown-grid>[class*=uk-width-]:nth-child(n+2){margin-top:15px}}.uk-dropdown-stack>.uk-dropdown-grid>[class*=uk-width-]{width:100%}.uk-dropdown-stack>.uk-dropdown-grid>[class*=uk-width-]:nth-child(n+2){margin-top:15px}.uk-dropdown-small{min-width:150px;width:auto;padding:5px;white-space:nowrap}.uk-dropdown-small .uk-nav{margin:0 -5px}.uk-dropdown-navbar{margin-top:6px;background:#fff;color:#444;left:-1px;border:1px solid #ddd;border-radius:4px}.uk-open>.uk-dropdown-navbar{-webkit-animation:uk-slide-top-fixed .2s ease-in-out;animation:uk-slide-top-fixed .2s ease-in-out}.uk-dropdown-scrollable{overflow-y:auto;max-height:200px}.uk-dropdown-navbar.uk-dropdown-flip{left:auto}.uk-modal{display:none;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1010;overflow-y:auto;-webkit-overflow-scrolling:touch;background:rgba(0,0,0,.6);opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.uk-modal.uk-open{opacity:1}.uk-modal-page,.uk-modal-page body{overflow:hidden}.uk-modal-dialog{position:relative;-moz-box-sizing:border-box;box-sizing:border-box;margin:50px auto;padding:20px;width:600px;max-width:100%;max-width:calc(100% - 20px);min-height:200px;background:#fff;opacity:0;-webkit-transform:translateY(-100px);transform:translateY(-100px);-webkit-transition:opacity .3s linear,-webkit-transform .3s ease-out;transition:opacity .3s linear,transform .3s ease-out;border-radius:4px;box-shadow:0 0 10px rgba(0,0,0,.3)}@media (max-width:767px){.uk-modal-dialog{width:auto;margin:10px auto}}.uk-open .uk-modal-dialog{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}.uk-modal-dialog>:not([class*=uk-modal-]):last-child{margin-bottom:0}.uk-modal-dialog>.uk-close:first-child{margin:-10px -10px 0 0;float:right}.uk-modal-dialog>.uk-close:first-child+:not([class*=uk-modal-]){margin-top:0}.uk-modal-dialog-lightbox{margin:15px auto;padding:0;max-width:95%;max-width:calc(100% - 30px);border-radius:0}.uk-modal-dialog-lightbox>.uk-close:first-child{position:absolute;top:-12px;right:-12px;margin:0;float:none}@media (max-width:767px){.uk-modal-dialog-lightbox>.uk-close:first-child{top:-7px;right:-7px}}@media (min-width:768px){.uk-modal-dialog-large{width:930px}}@media (min-width:1220px){.uk-modal-dialog-large{width:1130px}}.uk-modal-header{margin:-20px -20px 15px;padding:20px;border-bottom:1px solid #ddd;border-radius:4px 4px 0 0;background:#fafafa}.uk-modal-footer{margin:15px -20px -20px;padding:20px;border-top:1px solid #ddd;border-radius:0 0 4px 4px;background:#fafafa}.uk-modal-footer>:last-child,.uk-modal-header>:last-child{margin-bottom:0}.uk-modal-caption{position:absolute;left:0;right:0;bottom:-20px;margin-bottom:-10px;color:#fff;text-align:center}.uk-modal-spinner{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);font-size:25px;color:#ddd}.uk-modal-spinner:after{content:"\f110";font-family:FontAwesome;-webkit-animation:uk-rotate 2s infinite linear;animation:uk-rotate 2s infinite linear}.uk-offcanvas{display:none;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;touch-action:none;background:rgba(0,0,0,.1)}.uk-offcanvas.uk-active{display:block}.uk-offcanvas-page{position:fixed;-webkit-transition:margin-left .3s ease-in-out;transition:margin-left .3s ease-in-out}.uk-offcanvas-bar{position:fixed;top:0;bottom:0;left:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);z-index:1001;width:270px;max-width:100%;background:#333;overflow-y:auto;-webkit-overflow-scrolling:touch;-webkit-transition:-webkit-transform .3s ease-in-out;transition:transform .3s ease-in-out;-ms-scroll-chaining:none}.uk-offcanvas.uk-active .uk-offcanvas-bar.uk-offcanvas-bar-show{-webkit-transform:translateX(0%);transform:translateX(0%)}.uk-offcanvas-bar-flip{left:auto;right:0;-webkit-transform:translateX(100%);transform:translateX(100%)}.uk-offcanvas .uk-panel{margin:20px 15px;color:#777;text-shadow:0 1px 0 rgba(0,0,0,.5)}.uk-offcanvas .uk-panel a:not([class]),.uk-offcanvas .uk-panel-title{color:#ccc}.uk-offcanvas .uk-panel a:not([class]):hover{color:#fff}.uk-offcanvas-bar:after{content:"";display:block;position:absolute;top:0;bottom:0;right:0;width:1px;background:rgba(0,0,0,.6);box-shadow:0 0 5px 2px rgba(0,0,0,.6)}.uk-offcanvas-bar-flip:after{right:auto;left:0;width:1px;background:rgba(0,0,0,.6);box-shadow:0 0 5px 2px rgba(0,0,0,.6)}.uk-switcher{margin:0;padding:0;list-style:none}.uk-switcher>:not(.uk-active){display:none}.uk-tooltip{display:none;position:absolute;z-index:1030;-moz-box-sizing:border-box;box-sizing:border-box;max-width:200px;padding:5px 8px;background:#333;color:rgba(255,255,255,.7);font-size:12px;line-height:18px;text-align:center;border-radius:3px;text-shadow:0 1px 0 rgba(0,0,0,.5)}.uk-tooltip:after{content:"";display:block;position:absolute;width:0;height:0;border:5px dashed #333}.uk-tooltip-top-left:after,.uk-tooltip-top-right:after,.uk-tooltip-top:after{bottom:-5px;border-top-style:solid;border-bottom:none;border-left-color:transparent;border-right-color:transparent;border-top-color:#333}.uk-tooltip-bottom-left:after,.uk-tooltip-bottom-right:after,.uk-tooltip-bottom:after{top:-5px;border-bottom-style:solid;border-top:none;border-left-color:transparent;border-right-color:transparent;border-bottom-color:#333}.uk-tooltip-bottom:after,.uk-tooltip-top:after{left:50%;margin-left:-5px}.uk-tooltip-bottom-left:after,.uk-tooltip-top-left:after{left:10px}.uk-tooltip-bottom-right:after,.uk-tooltip-top-right:after{right:10px}.uk-tooltip-left:after{right:-5px;top:50%;margin-top:-5px;border-left-style:solid;border-right:none;border-top-color:transparent;border-bottom-color:transparent;border-left-color:#333}.uk-tooltip-right:after{left:-5px;top:50%;margin-top:-5px;border-right-style:solid;border-left:none;border-top-color:transparent;border-bottom-color:transparent;border-right-color:#333}.uk-text-small{font-size:11px;line-height:16px}.uk-text-large{font-size:18px;line-height:24px;font-weight:400}.uk-text-bold{font-weight:700}.uk-text-muted{color:#999!important}.uk-text-primary{color:#2d7091!important}.uk-text-success{color:#659f13!important}.uk-text-warning{color:#e28327!important}.uk-text-danger{color:#d85030!important}.uk-text-contrast{color:#fff!important}.uk-text-left{text-align:left!important}.uk-text-right{text-align:right!important}.uk-text-center{text-align:center!important}.uk-text-justify{text-align:justify!important}.uk-text-top{vertical-align:top!important}.uk-text-middle{vertical-align:middle!important}.uk-text-bottom{vertical-align:bottom!important}@media (max-width:959px){.uk-text-center-medium{text-align:center!important}.uk-text-left-medium{text-align:left!important}}@media (max-width:767px){.uk-text-center-small{text-align:center!important}.uk-text-left-small{text-align:left!important}}.uk-text-nowrap{white-space:nowrap}.uk-text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.uk-text-break{word-wrap:break-word;-webkit-hyphens:auto;-ms-hyphens:auto;-moz-hyphens:auto;hyphens:auto}.uk-container{-moz-box-sizing:border-box;box-sizing:border-box;max-width:980px;padding:0 25px}@media (min-width:1220px){.uk-container{max-width:1200px;padding:0 35px}}.uk-container:after,.uk-container:before{content:"";display:table}.uk-container:after{clear:both}.uk-container-center{margin-left:auto;margin-right:auto}.uk-clearfix:before{content:"";display:table-cell}.uk-clearfix:after{content:"";display:table;clear:both}.uk-nbfc{overflow:hidden}.uk-nbfc-alt{display:table-cell;width:10000px}.uk-float-left{float:left}.uk-float-right{float:right}[class*=uk-float-]{max-width:100%}[class*=uk-align-]{display:block;margin-bottom:15px}.uk-align-left{margin-right:15px;float:left}.uk-align-right{margin-left:15px;float:right}@media (min-width:768px){.uk-align-medium-left{margin-right:15px;margin-bottom:15px;float:left}.uk-align-medium-right{margin-left:15px;margin-bottom:15px;float:right}}.uk-align-center{margin-left:auto;margin-right:auto}.uk-vertical-align{font-size:0}.uk-vertical-align:before{content:'';display:inline-block;height:100%;vertical-align:middle}.uk-vertical-align-bottom,.uk-vertical-align-middle{display:inline-block;max-width:100%;font-size:1rem}.uk-vertical-align-middle{vertical-align:middle}.uk-vertical-align-bottom{vertical-align:bottom}[class*=uk-height]{-moz-box-sizing:border-box;box-sizing:border-box}.uk-height-1-1{height:100%}.uk-height-viewport{height:100vh;min-height:600px}.uk-responsive-height,.uk-responsive-width{-moz-box-sizing:border-box;box-sizing:border-box}.uk-responsive-width{max-width:100%!important;height:auto}.uk-responsive-height{max-height:100%;width:auto}.uk-margin{margin-bottom:15px}*+.uk-margin{margin-top:15px}.uk-margin-top{margin-top:15px!important}.uk-margin-bottom{margin-bottom:15px!important}.uk-margin-left{margin-left:15px!important}.uk-margin-right{margin-right:15px!important}.uk-margin-large{margin-bottom:50px}*+.uk-margin-large{margin-top:50px}.uk-margin-large-top{margin-top:50px!important}.uk-margin-large-bottom{margin-bottom:50px!important}.uk-margin-large-left{margin-left:50px!important}.uk-margin-large-right{margin-right:50px!important}.uk-margin-small{margin-bottom:5px}*+.uk-margin-small{margin-top:5px}.uk-margin-small-top{margin-top:5px!important}.uk-margin-small-bottom{margin-bottom:5px!important}.uk-margin-small-left{margin-left:5px!important}.uk-margin-small-right{margin-right:5px!important}.uk-margin-remove{margin:0!important}.uk-margin-top-remove{margin-top:0!important}.uk-margin-bottom-remove{margin-bottom:0!important}.uk-border-circle{border-radius:50%}.uk-border-rounded{border-radius:5px}@media (min-width:768px){.uk-heading-large{font-size:52px;line-height:64px}}.uk-link-muted,.uk-link-muted a,.uk-link-muted a:hover,.uk-link-muted:hover{color:#444}.uk-link-reset,.uk-link-reset a,.uk-link-reset a:hover,.uk-link-reset:hover{color:inherit;text-decoration:none}.uk-scrollable-text{height:300px;overflow-y:scroll;-webkit-overflow-scrolling:touch;resize:both}.uk-scrollable-box{-moz-box-sizing:border-box;box-sizing:border-box;height:170px;padding:10px;border:1px solid #ddd;overflow:auto;-webkit-overflow-scrolling:touch;resize:both;border-radius:3px}.uk-scrollable-box>:last-child{margin-bottom:0}.uk-overflow-container{overflow:auto;-webkit-overflow-scrolling:touch}.uk-overflow-container>:last-child{margin-bottom:0}.uk-position-bottom,.uk-position-top{position:absolute!important;width:100%}.uk-position-top{top:0}.uk-position-bottom{bottom:0}.uk-position-cover{position:absolute;top:0;bottom:0;left:0;right:0}.uk-position-relative{position:relative!important}.uk-display-block{display:block!important}.uk-display-inline{display:inline!important}.uk-display-inline-block{display:inline-block!important}@media (min-width:960px){.uk-hidden-large,.uk-visible-medium,.uk-visible-small{display:none!important}}@media (min-width:768px) and (max-width:959px){.uk-hidden-medium,.uk-visible-large,.uk-visible-small{display:none!important}}@media (max-width:767px){.uk-hidden-small,.uk-visible-large,.uk-visible-medium{display:none!important}}.uk-hidden{display:none!important;visibility:hidden!important}.uk-invisible{visibility:hidden!important}.uk-visible-hover:hover .uk-hidden,.uk-visible-hover:hover .uk-invisible{display:block!important;visibility:visible!important}.uk-visible-hover-inline:hover .uk-hidden,.uk-visible-hover-inline:hover .uk-invisible{display:inline-block!important;visibility:visible!important}.uk-notouch .uk-hidden-notouch,.uk-touch .uk-hidden-touch{display:none!important}@media print{*{background:0 0!important;color:#000!important;box-shadow:none!important;text-shadow:none!important}a,a:visited{text-decoration:underline}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}} --------------------------------------------------------------------------------