├── .eslintrc
├── .gitignore
├── README.md
├── client
└── app
│ ├── .babelrc
│ ├── components
│ ├── Home.js
│ ├── Login.js
│ ├── Logout.js
│ ├── Navbar.js
│ ├── Register.js
│ └── Settings.js
│ ├── config
│ └── routes.js
│ ├── containers
│ └── AppContainer.js
│ ├── index.html
│ ├── index.js
│ ├── services
│ ├── authentication.js
│ ├── event_manager.js
│ └── greeting.js
│ └── styles
│ └── bulma.min.css
├── knexfile.js
├── migrations
└── 20150927195358-create-user-and-phrases.js
├── package.json
├── server.js
├── server
├── config
│ ├── app.js
│ ├── bookshelf.js
│ ├── db_config.js
│ └── routes.js
├── controllers
│ ├── greeting_controller.js
│ ├── phrases_controller.js
│ ├── sessions_controller.js
│ └── users_controller.js
├── models
│ ├── phrase.js
│ └── user.js
└── test
│ └── requests
│ └── api_test.js
└── webpack.config.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser" : "babel-eslint",
3 | "extends" : [
4 | "standard",
5 | "standard-react",
6 | "airbnb"
7 | ],
8 | "plugins" : [
9 | "flow-vars"
10 | ],
11 | "env" : {
12 | "browser" : true
13 | },
14 | "globals" : {
15 | "Action" : false,
16 | "__DEV__" : false,
17 | "__PROD__" : false,
18 | "__DEBUG__" : false,
19 | "__DEBUG_NEW_WINDOW__" : false,
20 | "__BASENAME__" : false
21 | },
22 | "rules": {
23 | "semi" : [2, "never"],
24 | "max-len": [2, 120, 2],
25 | "flow-vars/define-flow-type": 1,
26 | "flow-vars/use-flow-type": 1
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | node_modules/*
3 | *.log
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # lehrer-node
2 |
3 | ##Please look at [https://github.com/rocky-jaiswal/hapi-knex-starter](https://github.com/rocky-jaiswal/hapi-knex-starter) for a better backend setup. For frontend I recommend [http://www.reactboilerplate.com/](http://www.reactboilerplate.com/)
4 |
5 | Hapi.js + ReactJS project
6 |
7 | To get started in development mode -
8 |
9 | npm install
10 | npm run serve
11 | npm run webpack
12 |
--------------------------------------------------------------------------------
/client/app/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "react",
4 | "babel-preset-es2015",
5 | "babel-preset-stage-3"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/client/app/components/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import greeting from '../services/greeting'
3 |
4 | const Home = React.createClass({
5 | getInitialState() {
6 | return {
7 | message: ''
8 | }
9 | },
10 |
11 | componentDidMount() {
12 | const promise = greeting.fetch()
13 | promise.then(response => { this.setState({ message: response.data.greeting }) })
14 | .catch(error => { this.setState({ message: 'An error occured!' }) })
15 | },
16 |
17 | render() {
18 | return (
19 |
20 |
21 |
{this.state.message}
22 |
23 |
24 | )
25 | },
26 | })
27 |
28 | export default Home
29 |
--------------------------------------------------------------------------------
/client/app/components/Login.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import authentication from '../services/authentication'
3 |
4 | const styles = {
5 | error: {
6 | color: '#FF0000',
7 | marginTop: '15px',
8 | },
9 | }
10 |
11 | const Login = React.createClass({
12 | contextTypes: {
13 | router: React.PropTypes.object.isRequired,
14 | },
15 |
16 | getInitialState() {
17 | return { error: false }
18 | },
19 |
20 | onSubmitLogin(event) {
21 | event.preventDefault()
22 |
23 | const email = this.refs.email.value
24 | const pass = this.refs.password.value
25 |
26 | authentication.login(email, pass, (loggedIn) => {
27 | if (loggedIn) {
28 | this.context.router.push({ pathname: '/' })
29 | } else {
30 | return this.setState({ error: true })
31 | }
32 | })
33 | },
34 |
35 | render() {
36 | return (
37 |
38 | Login
39 |
52 | {this.state.error && (
53 | Bad login information
54 | )}
55 |
56 | )
57 | },
58 | })
59 |
60 | export default Login
61 |
--------------------------------------------------------------------------------
/client/app/components/Logout.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router'
3 | import authentication from '../services/authentication'
4 |
5 | const Logout = React.createClass({
6 | contextTypes: {
7 | router: React.PropTypes.object.isRequired,
8 | },
9 |
10 | componentDidMount() {
11 | authentication.logout()
12 | this.context.router.replace('/')
13 | },
14 |
15 | render() {
16 | return (
17 |
18 | You are now logged out. Click here to log back in.
19 |
20 | )
21 | },
22 | })
23 |
24 | export default Logout
25 |
--------------------------------------------------------------------------------
/client/app/components/Navbar.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import { Link } from 'react-router'
3 |
4 | var styles = {
5 | head: {
6 | backgroundColor: "#ecf0f1",
7 | height: "75px",
8 | padding: "10px"
9 | },
10 | heading: {
11 | color: "#34495e"
12 |
13 | }
14 | };
15 |
16 | const Navbar = React.createClass({
17 |
18 | notLoggedIn () {
19 | return(
20 |
21 |
22 | Login
23 |
24 |
25 | Register
26 |
27 |
28 | );
29 | },
30 |
31 | loggedIn () {
32 | return(
33 |
34 |
35 | Home
36 |
37 |
38 | Settings
39 |
40 |
41 | Logout
42 |
43 |
44 | );
45 | },
46 |
47 | render () {
48 | return (
49 |
55 | );
56 | }
57 |
58 | })
59 |
60 | export default Navbar
61 |
--------------------------------------------------------------------------------
/client/app/components/Register.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 | import authentication from '../services/authentication'
3 |
4 | var styles = {
5 | error: {
6 | color: '#FF0000',
7 | marginTop: '15px'
8 | }
9 | }
10 |
11 | const Register = React.createClass({
12 | contextTypes: {
13 | router: React.PropTypes.object.isRequired
14 | },
15 |
16 | getInitialState () {
17 | return {
18 | error: false,
19 | errorMsg: '',
20 | }
21 | },
22 |
23 | onSubmitRegister (event) {
24 | event.preventDefault();
25 |
26 | const email = this.refs.email.value;
27 | const pass = this.refs.password.value;
28 | const confirmPassword = this.refs.confirmPassword.value;
29 |
30 | if(pass.length < 5 || pass !== confirmPassword) {
31 | this.setState({
32 | error: true,
33 | errorMsg: 'Password is too short or does not match'
34 | })
35 | } else {
36 | authentication.register(email, pass, confirmPassword, (loggedIn) => {
37 | if (loggedIn)
38 | this.context.router.push({pathname: '/'});
39 | else
40 | return this.setState({ error: true });
41 | });
42 | }
43 | },
44 |
45 | render () {
46 | return (
47 |
48 | Register
49 |
65 | {this.state.error && (
66 | {this.state.errorMsg}
67 | )}
68 |
69 | )
70 | }
71 | });
72 |
73 | export default Register
74 |
--------------------------------------------------------------------------------
/client/app/components/Settings.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react'
2 |
3 | const styles = {
4 | }
5 |
6 | const Settings = React.createClass({
7 | render () {
8 | return (
9 |
10 |
11 |
Settings
12 |
13 |
14 | )
15 | }
16 | });
17 |
18 | export default Settings
19 |
--------------------------------------------------------------------------------
/client/app/config/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Router, Route, hashHistory, IndexRoute } from 'react-router'
3 |
4 | import AppContainer from '../containers/AppContainer'
5 | import Login from '../components/Login'
6 | import Register from '../components/Register'
7 | import Logout from '../components/Logout'
8 | import Home from '../components/Home'
9 | import Settings from '../components/Settings'
10 |
11 | import authentication from '../services/authentication'
12 | import eventManager from '../services/event_manager'
13 |
14 | function checkAuth(nextState, replace, cb) {
15 | const promise = authentication.isAuthenticated();
16 | promise.then(function(resp) {
17 | eventManager.getEmitter().emit(eventManager.authChannel, true);
18 | cb();
19 | }).catch(function(err) {
20 | eventManager.getEmitter().emit(eventManager.authChannel, false);
21 | replace({
22 | pathname: '/login',
23 | state: { nextPathname: nextState.location.pathname }
24 | });
25 | cb();
26 | });
27 | }
28 |
29 | const routes = (
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | );
40 |
41 | export default routes
42 |
--------------------------------------------------------------------------------
/client/app/containers/AppContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Navbar from '../components/Navbar'
3 | import authentication from '../services/authentication'
4 | import eventManager from '../services/event_manager'
5 |
6 | const styles = {
7 | }
8 |
9 | const AppContainer = React.createClass({
10 | contextTypes: {
11 | router: React.PropTypes.object.isRequired
12 | },
13 |
14 | getInitialState () {
15 | return {
16 | loggedIn: false
17 | }
18 | },
19 |
20 | updateAuth (loggedIn) {
21 | this.setState({
22 | loggedIn: loggedIn
23 | })
24 | },
25 |
26 | componentDidMount () {
27 | this.subscription = eventManager.getEmitter().addListener(eventManager.authChannel, this.updateAuth);
28 | const promise = authentication.isAuthenticated();
29 | promise.then(resp => {this.setState({loggedIn: true})})
30 | .catch(err => {this.setState({loggedIn: false})});
31 | },
32 |
33 | componentWillUnmount () {
34 | this.subscription.remove();
35 | },
36 |
37 | render () {
38 | return (
39 |
40 |
41 |
42 | {React.cloneElement(this.props.children, { loggedIn: this.state.loggedIn })}
43 |
44 |
45 | )
46 | }
47 | });
48 |
49 | export default AppContainer
50 |
--------------------------------------------------------------------------------
/client/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ::Lehrer
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/client/app/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import routes from './config/routes'
4 |
5 | ReactDOM.render(routes, document.getElementById('app'));
6 |
--------------------------------------------------------------------------------
/client/app/services/authentication.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | const authentication = {
4 |
5 | isAuthenticated () {
6 | const token = localStorage.getItem('token');
7 | if(token) {
8 | return axios.get("http://localhost:3000/api/session", {headers: {"Authorization": token}});
9 | } else {
10 | return new Promise(function(resolve, reject){ reject(); });
11 | }
12 | },
13 |
14 | login (email, password, cb) {
15 | const promise = axios.post("http://localhost:3000/api/session", {email: email,
16 | password: password});
17 | this.handleAuth(promise, cb);
18 | },
19 |
20 | register (email, password, passwordConfirmation, cb) {
21 | const promise = axios.post("http://localhost:3000/api/users", {email: email,
22 | password: password,
23 | passwordConfirmation: passwordConfirmation});
24 | this.handleAuth(promise, cb);
25 | },
26 |
27 | logout () {
28 | const token = localStorage.getItem('token');
29 | localStorage.removeItem('token');
30 | axios.delete("http://localhost:3000/api/session", {headers: {"Authorization": token}});
31 | return true;
32 | },
33 |
34 | handleAuth (promise, cb) {
35 | promise.then((resp) => {
36 | if (resp.data.token) {
37 | localStorage.setItem('token', resp.data.token);
38 | cb(true);
39 | }
40 | }).catch((error) => cb(false));
41 | }
42 |
43 | }
44 |
45 | export default authentication
46 |
--------------------------------------------------------------------------------
/client/app/services/event_manager.js:
--------------------------------------------------------------------------------
1 | import {EventEmitter} from 'fbemitter';
2 |
3 | const EventManager = function () {
4 | this.emitter = this.emitter || new EventEmitter();
5 |
6 | this.authChannel = 'authState';
7 |
8 | this.getEmitter = function() {
9 | return this.emitter;
10 | }
11 | }
12 |
13 | export default new EventManager();
14 |
--------------------------------------------------------------------------------
/client/app/services/greeting.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | const greeting = {
4 |
5 | fetch () {
6 | const token = localStorage.getItem('token');
7 | if(token) {
8 | return axios.get("http://localhost:3000/api/greeting", {headers: {"Authorization": token}});
9 | } else {
10 | return new Promise(function(resolve, reject){ reject(); });
11 | }
12 | }
13 |
14 | }
15 |
16 | export default greeting
17 |
--------------------------------------------------------------------------------
/client/app/styles/bulma.min.css:
--------------------------------------------------------------------------------
1 | html,body,body div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,figure,footer,header,menu,nav,section,time,mark,audio,video,details,summary{margin:0;padding:0;border:0;font-size:100%;font-weight:normal;vertical-align:baseline;background:transparent}article,aside,figure,footer,header,nav,section,details,summary{display:block}html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}img,object,embed{max-width:100%}html{overflow-y:scroll}ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:transparent}del{text-decoration:line-through}abbr[title],dfn[title]{border-bottom:1px dotted #000;cursor:help}table{border-collapse:collapse;border-spacing:0}th{font-weight:bold;vertical-align:bottom}td{font-weight:normal;vertical-align:top}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}input,select{vertical-align:middle}pre{white-space:pre;white-space:pre-wrap;white-space:pre-line;word-wrap:break-word}input[type="radio"]{vertical-align:text-bottom}input[type="checkbox"]{vertical-align:bottom}select,input,textarea{font:99% sans-serif}table{font-size:inherit;font:100%}small{font-size:85%}strong{font-weight:bold}td,td img{vertical-align:top}sub,sup{font-size:75%;line-height:0;position:relative}sup{top:-0.5em}sub{bottom:-0.25em}pre,code,kbd,samp{font-family:monospace, sans-serif}label,input[type=button],input[type=submit],input[type=file],button{cursor:pointer}button,input,select,textarea{margin:0}button,input[type=button]{width:auto;overflow:visible}@-webkit-keyframes spin-around{from{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spin-around{from{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}html{background:#f5f7fa;font-size:14px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:hidden;overflow-y:scroll;text-rendering:optimizeLegibility}html.has-modal-open{overflow:hidden}body,button,input,select,textarea{font-family:"Helvetica Neue","Helvetica","Arial",sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:monospace;line-height:1.25}body{color:#69707a;font-size:1rem;line-height:1.428571428571429}a{color:#1fc8db;cursor:pointer;text-decoration:none;-webkit-transition:none 86ms ease-out;transition:none 86ms ease-out}a:hover{color:#222324}code{background:#f5f7fa;color:#ed6c63;font-size:12px;font-weight:normal;padding:1px 2px 2px}hr{border-top-color:#d3d6db;margin:20px 0}img{max-width:100%}input[type="checkbox"],input[type="radio"]{vertical-align:baseline}small{font-size:11px}strong{color:#222324}article,aside,figure,footer,header,hgroup,section{display:block}pre{background:#f5f7fa;color:#69707a;white-space:pre;word-wrap:normal}pre code{background:#f5f7fa;color:#69707a;display:block;overflow-x:auto;padding:16px 20px}table{width:100%}table th,table td{text-align:left;vertical-align:top}table th{color:#222324}.block:not(:last-child),.content:not(:last-child),.title:not(:last-child),.subtitle:not(:last-child),.message:not(:last-child),.notification:not(:last-child),.highlight:not(:last-child),.navbar:not(:last-child),.tabs:not(:last-child){margin-bottom:20px}.container{position:relative}@media screen and (min-width: 980px){.container{margin:0 auto;max-width:960px}.container.is-fluid{margin:0 20px;max-width:none}}.fa{font-size:21px;text-align:center;vertical-align:top}.content.is-medium{font-size:18px}.content.is-medium code{font-size:14px}.content.is-large{font-size:24px}.content.is-large code{font-size:18px}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#222324;font-weight:300;line-height:1.125;margin-bottom:20px}.content h1:not(:first-child),.content h2:not(:first-child),.content h3:not(:first-child){margin-top:40px}.content h1{font-size:2em}.content h2{font-size:1.75em}.content h3{font-size:1.5em}.content h4{font-size:1.25em}.content h5{font-size:1.125em}.content h6{font-size:1em}.content p:not(:last-child){margin-bottom:1em}.content li+li{margin-top:0.25em}.content ol{list-style:decimal outside;margin:1em 2em}.content ul{list-style:disc outside;margin:1em 2em}.content ul ul{list-style-type:circle;margin-top:0.5em}.content ul ul ul{list-style-type:square}.content blockquote{background:#f5f7fa;border-left:5px solid #d3d6db;padding:1.5em}.content blockquote:not(:last-child){margin-bottom:1em}.highlight{background-color:#fdf6e3;color:#586e75}.highlight .c{color:#93a1a1}.highlight .err,.highlight .g{color:#586e75}.highlight .k{color:#859900}.highlight .l,.highlight .n{color:#586e75}.highlight .o{color:#859900}.highlight .x{color:#cb4b16}.highlight .p{color:#586e75}.highlight .cm{color:#93a1a1}.highlight .cp{color:#859900}.highlight .c1{color:#93a1a1}.highlight .cs{color:#859900}.highlight .gd{color:#2aa198}.highlight .ge{color:#586e75;font-style:italic}.highlight .gr{color:#dc322f}.highlight .gh{color:#cb4b16}.highlight .gi{color:#859900}.highlight .go,.highlight .gp{color:#586e75}.highlight .gs{color:#586e75;font-weight:bold}.highlight .gu{color:#cb4b16}.highlight .gt{color:#586e75}.highlight .kc{color:#cb4b16}.highlight .kd{color:#268bd2}.highlight .kn,.highlight .kp{color:#859900}.highlight .kr{color:#268bd2}.highlight .kt{color:#dc322f}.highlight .ld{color:#586e75}.highlight .m,.highlight .s{color:#2aa198}.highlight .na{color:#B58900}.highlight .nb{color:#586e75}.highlight .nc{color:#268bd2}.highlight .no{color:#cb4b16}.highlight .nd{color:#268bd2}.highlight .ni,.highlight .ne{color:#cb4b16}.highlight .nf{color:#268bd2}.highlight .nl,.highlight .nn,.highlight .nx,.highlight .py{color:#586e75}.highlight .nt,.highlight .nv{color:#268bd2}.highlight .ow{color:#859900}.highlight .w{color:#586e75}.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:#2aa198}.highlight .sb{color:#93a1a1}.highlight .sc{color:#2aa198}.highlight .sd{color:#586e75}.highlight .s2{color:#2aa198}.highlight .se{color:#cb4b16}.highlight .sh{color:#586e75}.highlight .si,.highlight .sx{color:#2aa198}.highlight .sr{color:#dc322f}.highlight .s1,.highlight .ss{color:#2aa198}.highlight .bp,.highlight .vc,.highlight .vg,.highlight .vi{color:#268bd2}.highlight .il{color:#2aa198}.is-block{display:block}.is-inline{display:inline}.is-flex{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.is-clearfix:after{clear:both;content:" ";display:table}.is-pulled-left{float:left}.is-pulled-right{float:right}.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}.is-fullwidth{width:100%}.is-text-centered{text-align:center}.is-text-left{text-align:left}.is-text-right{text-align:right}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px){.is-hidden-tablet{display:none !important}}@media screen and (max-width: 979px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 980px){.is-hidden-desktop{display:none !important}}.is-disabled{pointer-events:none}.is-marginless{margin:0 !important}.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.input,.textarea{-moz-appearance:none;-webkit-appearance:none;background:#fff;border:1px solid #d3d6db;border-radius:3px;color:#222324;display:inline-block;font-size:14px;height:32px;line-height:24px;padding:3px 8px;position:relative;vertical-align:top;box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);display:block;max-width:100%;width:100%}.input:hover,.textarea:hover{border-color:#aeb1b5}.input:active,.textarea:active,.input:focus,.textarea:focus{border-color:#1fc8db;outline:none}.input[disabled],[disabled].textarea,.input[disabled]:hover,[disabled].textarea:hover{background:#f5f7fa;border-color:#d3d6db}.input[disabled]::-moz-placeholder,[disabled].textarea::-moz-placeholder,.input[disabled]:hover::-moz-placeholder,[disabled].textarea:hover::-moz-placeholder{color:rgba(34,35,36,0.3)}.input[disabled]::-webkit-input-placeholder,[disabled].textarea::-webkit-input-placeholder,.input[disabled]:hover::-webkit-input-placeholder,[disabled].textarea:hover::-webkit-input-placeholder{color:rgba(34,35,36,0.3)}.input[disabled]:-moz-placeholder,[disabled].textarea:-moz-placeholder,.input[disabled]:hover:-moz-placeholder,[disabled].textarea:hover:-moz-placeholder{color:rgba(34,35,36,0.3)}.input[disabled]:-ms-input-placeholder,[disabled].textarea:-ms-input-placeholder,.input[disabled]:hover:-ms-input-placeholder,[disabled].textarea:hover:-ms-input-placeholder{color:rgba(34,35,36,0.3)}.input.is-dark,.is-dark.textarea{border-color:#222324;color:#222324}.input.is-primary,.is-primary.textarea{border-color:#1fc8db;color:#1fc8db}.input.is-info,.is-info.textarea{border-color:#42afe3;color:#42afe3}.input.is-success,.is-success.textarea{border-color:#97cd76;color:#97cd76}.input.is-warning,.is-warning.textarea{border-color:#fce473;color:#fce473}.input.is-danger,.is-danger.textarea{border-color:#ed6c63;color:#ed6c63}.input[type="search"],[type="search"].textarea{border-radius:290486px}.input.is-flat,.is-flat.textarea{border:none;box-shadow:none;padding:4px 8px}.input.is-small,.is-small.textarea{border-radius:2px;font-size:11px;height:24px;line-height:16px;padding:3px 6px}.input.is-small.is-flat,.is-small.is-flat.textarea{padding:4px 6px}.input.is-medium,.is-medium.textarea{font-size:18px;height:40px;line-height:32px;padding:3px 10px}.input.is-medium.is-flat,.is-medium.is-flat.textarea{padding:4px 10px}.input.is-large,.is-large.textarea{font-size:24px;height:48px;line-height:40px;padding:3px 12px}.input.is-large.is-flat,.is-large.is-flat.textarea{padding:4px 12px}.input.is-fullwidth,.is-fullwidth.textarea{display:block;width:100%}.input.is-inline,.is-inline.textarea{display:inline;width:auto}.textarea{line-height:1.2;max-height:600px;max-width:100%;min-height:120px;min-width:100%;padding:10px;resize:vertical}.checkbox,.menu-checkbox,.radio{cursor:pointer;display:inline-block;line-height:16px;padding-left:18px;position:relative;vertical-align:top}.checkbox input,.menu-checkbox input,.radio input{-moz-appearance:none;-webkit-appearance:none;background:#fff;border:1px solid #d3d6db;border-radius:3px;color:#222324;display:inline-block;font-size:14px;height:32px;line-height:24px;padding:3px 8px;position:relative;vertical-align:top;border-radius:1px;box-shadow:inset 0 1px 1px rgba(0,0,0,0.1);cursor:pointer;float:left;height:14px;left:0;outline:none;padding:0;position:absolute;top:1px;width:14px}.checkbox input:hover,.menu-checkbox input:hover,.radio input:hover{border-color:#aeb1b5}.checkbox input:active,.menu-checkbox input:active,.radio input:active,.checkbox input:focus,.menu-checkbox input:focus,.radio input:focus{border-color:#1fc8db;outline:none}.checkbox input[disabled],.menu-checkbox input[disabled],.radio input[disabled],.checkbox input[disabled]:hover,.menu-checkbox input[disabled]:hover,.radio input[disabled]:hover{background:#f5f7fa;border-color:#d3d6db}.checkbox input[disabled]::-moz-placeholder,.menu-checkbox input[disabled]::-moz-placeholder,.radio input[disabled]::-moz-placeholder,.checkbox input[disabled]:hover::-moz-placeholder,.menu-checkbox input[disabled]:hover::-moz-placeholder,.radio input[disabled]:hover::-moz-placeholder{color:rgba(34,35,36,0.3)}.checkbox input[disabled]::-webkit-input-placeholder,.menu-checkbox input[disabled]::-webkit-input-placeholder,.radio input[disabled]::-webkit-input-placeholder,.checkbox input[disabled]:hover::-webkit-input-placeholder,.menu-checkbox input[disabled]:hover::-webkit-input-placeholder,.radio input[disabled]:hover::-webkit-input-placeholder{color:rgba(34,35,36,0.3)}.checkbox input[disabled]:-moz-placeholder,.menu-checkbox input[disabled]:-moz-placeholder,.radio input[disabled]:-moz-placeholder,.checkbox input[disabled]:hover:-moz-placeholder,.menu-checkbox input[disabled]:hover:-moz-placeholder,.radio input[disabled]:hover:-moz-placeholder{color:rgba(34,35,36,0.3)}.checkbox input[disabled]:-ms-input-placeholder,.menu-checkbox input[disabled]:-ms-input-placeholder,.radio input[disabled]:-ms-input-placeholder,.checkbox input[disabled]:hover:-ms-input-placeholder,.menu-checkbox input[disabled]:hover:-ms-input-placeholder,.radio input[disabled]:hover:-ms-input-placeholder{color:rgba(34,35,36,0.3)}.checkbox input.is-dark,.menu-checkbox input.is-dark,.radio input.is-dark{border-color:#222324;color:#222324}.checkbox input.is-primary,.menu-checkbox input.is-primary,.radio input.is-primary{border-color:#1fc8db;color:#1fc8db}.checkbox input.is-info,.menu-checkbox input.is-info,.radio input.is-info{border-color:#42afe3;color:#42afe3}.checkbox input.is-success,.menu-checkbox input.is-success,.radio input.is-success{border-color:#97cd76;color:#97cd76}.checkbox input.is-warning,.menu-checkbox input.is-warning,.radio input.is-warning{border-color:#fce473;color:#fce473}.checkbox input.is-danger,.menu-checkbox input.is-danger,.radio input.is-danger{border-color:#ed6c63;color:#ed6c63}.checkbox input:after,.menu-checkbox input:after,.radio input:after{border:1px solid #fff;border-right:0;border-top:0;content:" ";display:block;height:7px;pointer-events:none;position:absolute;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);width:7px;height:4px;left:3px;opacity:0;position:absolute;top:3px;-webkit-transform:rotate(-45deg) scale(1);transform:rotate(-45deg) scale(1)}.checkbox input:checked,.menu-checkbox input:checked,.radio input:checked{background:#1fc8db;border-color:#1fc8db;box-shadow:none}.checkbox input:checked:after,.menu-checkbox input:checked:after,.radio input:checked:after{opacity:1}.checkbox:hover,.menu-checkbox:hover,.radio:hover{color:#222324}.checkbox:hover input,.menu-checkbox:hover input,.radio:hover input{border-color:#aeb1b5}.checkbox:hover input:checked,.menu-checkbox:hover input:checked,.radio:hover input:checked{border-color:#1fc8db}.is-disabled.checkbox,.is-disabled.menu-checkbox,.is-disabled.radio,.is-disabled.checkbox:hover,.is-disabled.menu-checkbox:hover,.is-disabled.radio:hover{color:#aeb1b5}.radio+.radio{margin-left:10px}.radio input{border-radius:8px}.radio input:after{background:#fff;border:0;border-radius:2px;left:4px;top:4px;-webkit-transform:none;transform:none;width:4px}.select{display:inline-block;height:32px;position:relative;vertical-align:top}.select select{-moz-appearance:none;-webkit-appearance:none;background:#fff;border:1px solid #d3d6db;border-radius:3px;color:#222324;display:inline-block;font-size:14px;height:32px;line-height:24px;padding:3px 8px;position:relative;vertical-align:top;cursor:pointer;display:block;outline:none;padding-right:36px}.select select:hover{border-color:#aeb1b5}.select select:active,.select select:focus{border-color:#1fc8db;outline:none}.select select[disabled],.select select[disabled]:hover{background:#f5f7fa;border-color:#d3d6db}.select select[disabled]::-moz-placeholder,.select select[disabled]:hover::-moz-placeholder{color:rgba(34,35,36,0.3)}.select select[disabled]::-webkit-input-placeholder,.select select[disabled]:hover::-webkit-input-placeholder{color:rgba(34,35,36,0.3)}.select select[disabled]:-moz-placeholder,.select select[disabled]:hover:-moz-placeholder{color:rgba(34,35,36,0.3)}.select select[disabled]:-ms-input-placeholder,.select select[disabled]:hover:-ms-input-placeholder{color:rgba(34,35,36,0.3)}.select select.is-dark{border-color:#222324;color:#222324}.select select.is-primary{border-color:#1fc8db;color:#1fc8db}.select select.is-info{border-color:#42afe3;color:#42afe3}.select select.is-success{border-color:#97cd76;color:#97cd76}.select select.is-warning{border-color:#fce473;color:#fce473}.select select.is-danger{border-color:#ed6c63;color:#ed6c63}.select select:hover{border-color:#aeb1b5}.select select::ms-expand{display:none}.select:after{border:1px solid #1fc8db;border-right:0;border-top:0;content:" ";display:block;height:7px;pointer-events:none;position:absolute;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);width:7px;margin-top:-6px;right:16px;top:50%}.select:hover:after{border-color:#222324}.label{color:#222324;display:block}.label:not(:last-child){margin-bottom:5px}.control{position:relative;text-align:left}.control.is-loading:after{position:absolute !important;right:8px;top:8px}.control:not(:last-child){margin-bottom:10px}.control.has-icon>.fa{display:inline-block;font-size:14px;height:20px;line-height:20px;text-align:center;vertical-align:top;width:20px;color:#aeb1b5;left:6px;pointer-events:none;position:absolute;top:6px;z-index:4}.control.has-icon .input,.control.has-icon .textarea{padding-left:32px}.control.has-icon .input:focus+.fa,.control.has-icon .textarea:focus+.fa{color:#1fc8db}.control.is-horizontal{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.control.is-horizontal>.button:not(:last-child),.control.is-horizontal>.input:not(:last-child),.control.is-horizontal>.textarea:not(:last-child),.control.is-horizontal>.select:not(:last-child){margin-right:10px}.control.is-horizontal>.input,.control.is-horizontal>.textarea{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.control.is-grouped{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.control.is-grouped .input,.control.is-grouped .textarea,.control.is-grouped .button,.control.is-grouped .select{border-radius:0;margin-right:-1px}.control.is-grouped .input:hover,.control.is-grouped .textarea:hover,.control.is-grouped .button:hover,.control.is-grouped .select:hover{z-index:2}.control.is-grouped .input:active,.control.is-grouped .textarea:active,.control.is-grouped .input:focus,.control.is-grouped .textarea:focus,.control.is-grouped .button:active,.control.is-grouped .button:focus,.control.is-grouped .select:active,.control.is-grouped .select:focus{z-index:3}.control.is-grouped .input:first-child,.control.is-grouped .textarea:first-child,.control.is-grouped .button:first-child,.control.is-grouped .select:first-child{border-radius:3px 0 0 3px}.control.is-grouped .input:first-child select,.control.is-grouped .textarea:first-child select,.control.is-grouped .button:first-child select,.control.is-grouped .select:first-child select{border-radius:3px 0 0 3px}.control.is-grouped .input:last-child,.control.is-grouped .textarea:last-child,.control.is-grouped .button:last-child,.control.is-grouped .select:last-child{border-radius:0 3px 3px 0}.control.is-grouped.is-centered{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.button{-moz-appearance:none;-webkit-appearance:none;background:#fff;border:1px solid #d3d6db;border-radius:3px;color:#222324;display:inline-block;font-size:14px;height:32px;line-height:24px;padding:3px 8px;position:relative;vertical-align:top;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding:3px 10px;text-align:center;white-space:nowrap}.button:hover{border-color:#aeb1b5}.button:active,.button:focus{border-color:#1fc8db;outline:none}.button[disabled],.button[disabled]:hover{background:#f5f7fa;border-color:#d3d6db}.button[disabled]::-moz-placeholder,.button[disabled]:hover::-moz-placeholder{color:rgba(34,35,36,0.3)}.button[disabled]::-webkit-input-placeholder,.button[disabled]:hover::-webkit-input-placeholder{color:rgba(34,35,36,0.3)}.button[disabled]:-moz-placeholder,.button[disabled]:hover:-moz-placeholder{color:rgba(34,35,36,0.3)}.button[disabled]:-ms-input-placeholder,.button[disabled]:hover:-ms-input-placeholder{color:rgba(34,35,36,0.3)}.button strong{color:inherit}.button small{display:block;font-size:11px;line-height:1;margin-top:5px}.button .fa{line-height:24px;margin:0 -2px;width:24px}.button:hover{color:#222324}.button:active{box-shadow:inset 0 1px 2px rgba(0,0,0,0.2)}.button.is-dark{background:#222324;border-color:transparent;color:#fff}.button.is-dark:hover,.button.is-dark:focus{background:#090a0a;border-color:transparent;color:#fff}.button.is-dark:active{border-color:transparent}.button.is-dark.is-outlined{background:transparent;border-color:#222324;color:#222324}.button.is-dark.is-outlined:hover,.button.is-dark.is-outlined:focus{border-color:#090a0a;color:#090a0a}.button.is-dark.is-inverted{background:#fff;color:#222324}.button.is-dark.is-inverted:hover{background:#f2f2f2}.button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-dark.is-inverted.is-outlined:hover{background:rgba(0,0,0,0.05)}.button.is-dark.is-loading:after{border-color:transparent transparent #fff #fff !important}.button.is-primary{background:#1fc8db;border-color:transparent;color:#fff}.button.is-primary:hover,.button.is-primary:focus{background:#199fae;border-color:transparent;color:#fff}.button.is-primary:active{border-color:transparent}.button.is-primary.is-outlined{background:transparent;border-color:#1fc8db;color:#1fc8db}.button.is-primary.is-outlined:hover,.button.is-primary.is-outlined:focus{border-color:#199fae;color:#199fae}.button.is-primary.is-inverted{background:#fff;color:#1fc8db}.button.is-primary.is-inverted:hover{background:#f2f2f2}.button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined:hover{background:rgba(0,0,0,0.05)}.button.is-primary.is-loading:after{border-color:transparent transparent #fff #fff !important}.button.is-info{background:#42afe3;border-color:transparent;color:#fff}.button.is-info:hover,.button.is-info:focus{background:#1f99d3;border-color:transparent;color:#fff}.button.is-info:active{border-color:transparent}.button.is-info.is-outlined{background:transparent;border-color:#42afe3;color:#42afe3}.button.is-info.is-outlined:hover,.button.is-info.is-outlined:focus{border-color:#1f99d3;color:#1f99d3}.button.is-info.is-inverted{background:#fff;color:#42afe3}.button.is-info.is-inverted:hover{background:#f2f2f2}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined:hover{background:rgba(0,0,0,0.05)}.button.is-info.is-loading:after{border-color:transparent transparent #fff #fff !important}.button.is-success{background:#97cd76;border-color:transparent;color:#fff}.button.is-success:hover,.button.is-success:focus{background:#7bbf51;border-color:transparent;color:#fff}.button.is-success:active{border-color:transparent}.button.is-success.is-outlined{background:transparent;border-color:#97cd76;color:#97cd76}.button.is-success.is-outlined:hover,.button.is-success.is-outlined:focus{border-color:#7bbf51;color:#7bbf51}.button.is-success.is-inverted{background:#fff;color:#97cd76}.button.is-success.is-inverted:hover{background:#f2f2f2}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-success.is-inverted.is-outlined:hover{background:rgba(0,0,0,0.05)}.button.is-success.is-loading:after{border-color:transparent transparent #fff #fff !important}.button.is-warning{background:#fce473;border-color:transparent;color:rgba(0,0,0,0.5)}.button.is-warning:hover,.button.is-warning:focus{background:#fbda41;border-color:transparent;color:rgba(0,0,0,0.5)}.button.is-warning:active{border-color:transparent}.button.is-warning.is-outlined{background:transparent;border-color:#fce473;color:#fce473}.button.is-warning.is-outlined:hover,.button.is-warning.is-outlined:focus{border-color:#fbda41;color:#fbda41}.button.is-warning.is-inverted{background:rgba(0,0,0,0.5);color:#fce473}.button.is-warning.is-inverted:hover{background:rgba(0,0,0,0.5)}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.5);color:rgba(0,0,0,0.5)}.button.is-warning.is-inverted.is-outlined:hover{background:rgba(0,0,0,0.05)}.button.is-warning.is-loading:after{border-color:transparent transparent rgba(0,0,0,0.5) rgba(0,0,0,0.5) !important}.button.is-danger{background:#ed6c63;border-color:transparent;color:#fff}.button.is-danger:hover,.button.is-danger:focus{background:#e84135;border-color:transparent;color:#fff}.button.is-danger:active{border-color:transparent}.button.is-danger.is-outlined{background:transparent;border-color:#ed6c63;color:#ed6c63}.button.is-danger.is-outlined:hover,.button.is-danger.is-outlined:focus{border-color:#e84135;color:#e84135}.button.is-danger.is-inverted{background:#fff;color:#ed6c63}.button.is-danger.is-inverted:hover{background:#f2f2f2}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined:hover{background:rgba(0,0,0,0.05)}.button.is-danger.is-loading:after{border-color:transparent transparent #fff #fff !important}.button.is-small{border-radius:2px;font-size:11px;height:24px;line-height:16px;padding:3px 6px}.button.is-medium{font-size:18px;height:40px;padding:7px 14px}.button.is-large{font-size:22px;height:48px;padding:11px 20px}.button.is-fullwidth{display:block;width:100%}.button.is-flexible{height:auto}.button.is-loading{color:transparent;pointer-events:none}.button.is-loading:after{left:50%;margin-left:-8px;margin-top:-8px;position:absolute;top:50%;position:absolute !important}.button.is-disabled,.button[disabled]{opacity:0.5;pointer-events:none}@media screen and (min-width: 769px){.button small{color:#69707a;left:0;margin-top:10px;position:absolute;top:100%;width:100%}}.title,.subtitle{font-weight:300}.title em,.subtitle em{font-weight:300}.title a:hover,.subtitle a:hover{border-bottom:1px solid}.title .tag,.subtitle .tag{vertical-align:bottom}.title{color:#222324;font-size:28px;line-height:1}.title strong{color:inherit}.title code{display:inline-block;font-size:28px}.title+.subtitle{margin-top:-10px}.title+.highlight{margin-top:-10px}.title.is-normal{font-weight:400}.title.is-normal strong{font-weight:700}.title.is-1{font-size:48px}.title.is-1 code{font-size:40px}.title.is-2{font-size:40px}.title.is-2 code{font-size:28px}.title.is-3{font-size:28px}.title.is-3 code{font-size:24px}.title.is-4{font-size:24px}.title.is-4 code{font-size:18px}.title.is-5{font-size:18px}.title.is-5 code{font-size:14px}.title.is-6{font-size:14px}.title.is-6 code{font-size:14px}@media screen and (min-width: 769px){.title+.subtitle{margin-top:-15px}}.subtitle{font-size:18px;line-height:1.125}.subtitle+.title{margin-top:-20px}.subtitle strong{color:#222324;font-weight:400}.subtitle code{border-radius:3px;display:inline-block;font-size:14px;padding:2px 3px;vertical-align:top}.subtitle+.text{margin-top:20px}.subtitle.is-normal{font-weight:400}.subtitle.is-normal strong{font-weight:700}.subtitle.is-1{font-size:48px}.subtitle.is-1 code{font-size:40px}.subtitle.is-2{font-size:40px}.subtitle.is-2 code{font-size:28px}.subtitle.is-3{font-size:28px}.subtitle.is-3 code{font-size:24px}.subtitle.is-4{font-size:24px}.subtitle.is-4 code{font-size:18px}.subtitle.is-5{font-size:18px}.subtitle.is-5 code{font-size:14px}.subtitle.is-6{font-size:14px}.subtitle.is-6 code{font-size:14px}.image{display:block;position:relative}.image img{display:block}.image.is-square img,.image.is-1by1 img,.image.is-4by3 img,.image.is-3by2 img,.image.is-16by9 img,.image.is-2by1 img{bottom:0;left:0;position:absolute;right:0;top:0;height:100%;width:100%}.image.is-square,.image.is-1by1{padding-top:100%}.image.is-4by3{padding-top:75%}.image.is-3by2{padding-top:66.6666%}.image.is-16by9{padding-top:56.25%}.image.is-2by1{padding-top:50%}.image.is-16x16{height:16px;width:16px}.image.is-24x24{height:24px;width:24px}.image.is-32x32{height:32px;width:32px}.image.is-48x48{height:48px;width:48px}.image.is-64x64{height:64px;width:64px}.image.is-96x96{height:96px;width:96px}.image.is-128x128{height:128px;width:128px}.message-body{border:1px solid #d3d6db;border-radius:3px;padding:12px 15px}.message-body strong{color:inherit}.message-header{background:#69707a;border-radius:3px 3px 0 0;color:#fff;font-size:10px;font-weight:bold;letter-spacing:1px;padding:3px 8px;text-transform:uppercase}.message-header+.message-body{border-radius:0 0 3px 3px;border-top:none}.message{background:#f5f7fa;border-radius:3px}.message.is-dark{background:#f5f5f5}.message.is-dark .message-header{background:#222324;color:#fff}.message.is-dark .message-body{border-color:#222324;color:gray}.message.is-primary{background:#edfbfc}.message.is-primary .message-header{background:#1fc8db;color:#fff}.message.is-primary .message-body{border-color:#1fc8db;color:gray}.message.is-info{background:#edf7fc}.message.is-info .message-header{background:#42afe3;color:#fff}.message.is-info .message-body{border-color:#42afe3;color:gray}.message.is-success{background:#f4faf0}.message.is-success .message-header{background:#97cd76;color:#fff}.message.is-success .message-body{border-color:#97cd76;color:gray}.message.is-warning{background:#fffbeb}.message.is-warning .message-header{background:#fce473;color:rgba(0,0,0,0.5)}.message.is-warning .message-body{border-color:#fce473;color:#666}.message.is-danger{background:#fdeeed}.message.is-danger .message-header{background:#ed6c63;color:#fff}.message.is-danger .message-body{border-color:#ed6c63;color:gray}.notification{background:#f5f7fa;border-radius:3px;padding:16px 20px;position:relative}.notification:after{clear:both;content:" ";display:table}.notification .title{color:inherit}.notification.is-dark{background:#222324;color:#fff}.notification.is-primary{background:#1fc8db;color:#fff}.notification.is-info{background:#42afe3;color:#fff}.notification.is-success{background:#97cd76;color:#fff}.notification.is-warning{background:#fce473;color:rgba(0,0,0,0.5)}.notification.is-danger{background:#ed6c63;color:#fff}.notification .delete,.notification .modal-close{background:rgba(0,0,0,0.2);border-radius:0 3px;float:right;margin:-16px -20px 0 20px}.notification .delete:hover,.notification .modal-close:hover{background:rgba(0,0,0,0.5)}.box{background:white;border-radius:5px;box-shadow:0 2px 3px rgba(0,0,0,0.1),0 0 0 1px rgba(0,0,0,0.1);padding:20px}.delete,.modal-close{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background:rgba(0,0,0,0.2);border:none;border-radius:290486px;cursor:pointer;display:inline-block;height:24px;position:relative;vertical-align:top;width:24px}.delete:before,.modal-close:before,.delete:after,.modal-close:after{background:white;content:"";display:block;height:2px;left:50%;margin-left:-25%;margin-top:-1px;position:absolute;top:50%;width:50%}.delete:before,.modal-close:before{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.delete:after,.modal-close:after{-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.delete:hover,.modal-close:hover{background:#ed6c63}.delete.is-small,.tag:not(.is-large) .delete,.tag:not(.is-large) .modal-close,.is-small.modal-close{height:16px;width:16px}.delete.is-medium,.is-medium.modal-close{height:32px;width:32px}.delete.is-large,.is-large.modal-close{height:40px;width:40px}.icon{display:inline-block;font-size:21px;height:24px;line-height:24px;text-align:center;vertical-align:top;width:24px}.icon .fa{font-size:inherit;line-height:inherit}.icon.is-small{display:inline-block;font-size:14px;height:20px;line-height:20px;text-align:center;vertical-align:top;width:20px}.icon.is-medium{display:inline-block;font-size:28px;height:32px;line-height:32px;text-align:center;vertical-align:top;width:32px}.icon.is-large{display:inline-block;font-size:42px;height:48px;line-height:48px;text-align:center;vertical-align:top;width:48px}.hamburger,.header-toggle{cursor:pointer;display:block;height:50px;position:relative;width:50px}.hamburger span,.header-toggle span{background:#69707a;display:block;height:1px;left:50%;margin-left:-7px;position:absolute;top:50%;-webkit-transition:none 86ms ease-out;transition:none 86ms ease-out;-webkit-transition-property:background, left, opacity, -webkit-transform;transition-property:background, left, opacity, -webkit-transform;transition-property:background, left, opacity, transform;transition-property:background, left, opacity, transform, -webkit-transform;width:15px}.hamburger span:nth-child(1),.header-toggle span:nth-child(1){margin-top:-6px}.hamburger span:nth-child(2),.header-toggle span:nth-child(2){margin-top:-1px}.hamburger span:nth-child(3),.header-toggle span:nth-child(3){margin-top:4px}.hamburger:hover,.header-toggle:hover{background:#f5f7fa}.hamburger.is-active span,.is-active.header-toggle span{background:#1fc8db}.hamburger.is-active span:nth-child(1),.is-active.header-toggle span:nth-child(1){margin-left:-5px;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-transform-origin:left top;transform-origin:left top}.hamburger.is-active span:nth-child(2),.is-active.header-toggle span:nth-child(2){opacity:0}.hamburger.is-active span:nth-child(3),.is-active.header-toggle span:nth-child(3){margin-left:-5px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-transform-origin:left bottom;transform-origin:left bottom}@media screen and (min-width: 769px){.hamburger,.header-toggle{height:50px;width:50px}}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.highlight{font-size:12px;font-weight:normal;max-width:100%;overflow:hidden;padding:0}.highlight pre{overflow:auto;max-width:100%}.image{display:block;position:relative;vertical-align:top}.image img{bottom:0;left:0;position:absolute;right:0;top:0;display:block;width:100%}.image.is-3x2{padding-top:66.6666%}.loader,.control.is-loading:after,.button.is-loading:after{-webkit-animation:spin-around 500ms infinite linear;animation:spin-around 500ms infinite linear;border:2px solid #d3d6db;border-radius:290486px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:16px;position:relative;width:16px}.number{background:#f5f7fa;border-radius:290486px;display:inline-block;font-size:18px;vertical-align:top}.tag{background:#f5f7fa;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.1);color:#69707a;display:inline-block;font-size:12px;height:24px;line-height:16px;padding:4px 10px;vertical-align:top;white-space:nowrap}.tag.is-dark{background:#69707a;color:#fff}.tag.is-rounded{border-radius:290486px}.tag.is-medium{box-shadow:inset 0 -2px 0 rgba(0,0,0,0.1);font-size:14px;height:32px;padding:7px 14px 9px}.tag:not(.is-large) .delete,.tag:not(.is-large) .modal-close{margin-left:4px;margin-right:-6px}.tag.is-large{box-shadow:inset 0 -2px 0 rgba(0,0,0,0.1);font-size:18px;height:40px;line-height:24px;padding:7px 18px 9px}.tag.is-large .delete,.tag.is-large .modal-close{margin-left:4px;margin-right:-8px}.tag.is-dark{background:#222324;color:#fff}.tag.is-primary{background:#1fc8db;color:#fff}.tag.is-info{background:#42afe3;color:#fff}.tag.is-success{background:#97cd76;color:#fff}.tag.is-warning{background:#fce473;color:rgba(0,0,0,0.5)}.tag.is-danger{background:#ed6c63;color:#fff}.column{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;padding:10px}.columns.is-mobile>.column.is-half{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:50%}.columns.is-mobile>.column.is-third{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:33.3333%}.columns.is-mobile>.column.is-quarter{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:25%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-quarter{margin-left:25%}.columns.is-mobile>.column.is-1{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:8.33333%}.columns.is-mobile>.column.is-offset-1{margin-left:8.33333%}.columns.is-mobile>.column.is-2{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:16.66667%}.columns.is-mobile>.column.is-offset-2{margin-left:16.66667%}.columns.is-mobile>.column.is-3{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:33.33333%}.columns.is-mobile>.column.is-offset-4{margin-left:33.33333%}.columns.is-mobile>.column.is-5{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:41.66667%}.columns.is-mobile>.column.is-offset-5{margin-left:41.66667%}.columns.is-mobile>.column.is-6{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:58.33333%}.columns.is-mobile>.column.is-offset-7{margin-left:58.33333%}.columns.is-mobile>.column.is-8{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:66.66667%}.columns.is-mobile>.column.is-offset-8{margin-left:66.66667%}.columns.is-mobile>.column.is-9{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:83.33333%}.columns.is-mobile>.column.is-offset-10{margin-left:83.33333%}.columns.is-mobile>.column.is-11{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:91.66667%}.columns.is-mobile>.column.is-offset-11{margin-left:91.66667%}@media screen and (max-width: 768px){.column.is-half-mobile{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:50%}.column.is-third-mobile{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:33.3333%}.column.is-quarter-mobile{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:25%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-third-mobile{margin-left:33.3333%}.column.is-offset-quarter-mobile{margin-left:25%}.column.is-1-mobile{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:8.33333%}.column.is-offset-1-mobile{margin-left:8.33333%}.column.is-2-mobile{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:16.66667%}.column.is-offset-2-mobile{margin-left:16.66667%}.column.is-3-mobile{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:33.33333%}.column.is-offset-4-mobile{margin-left:33.33333%}.column.is-5-mobile{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:41.66667%}.column.is-offset-5-mobile{margin-left:41.66667%}.column.is-6-mobile{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:58.33333%}.column.is-offset-7-mobile{margin-left:58.33333%}.column.is-8-mobile{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:66.66667%}.column.is-offset-8-mobile{margin-left:66.66667%}.column.is-9-mobile{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:83.33333%}.column.is-offset-10-mobile{margin-left:83.33333%}.column.is-11-mobile{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:91.66667%}.column.is-offset-11-mobile{margin-left:91.66667%}}@media screen and (min-width: 769px){.column.is-half,.column.is-half-tablet{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:50%}.column.is-third,.column.is-third-tablet{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:33.3333%}.column.is-quarter,.column.is-quarter-tablet{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:25%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-third,.column.is-offset-third-tablet{margin-left:33.3333%}.column.is-offset-quarter,.column.is-offset-quarter-tablet{margin-left:25%}.column.is-1,.column.is-1-tablet{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:8.33333%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.33333%}.column.is-2,.column.is-2-tablet{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:16.66667%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.66667%}.column.is-3,.column.is-3-tablet{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:33.33333%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.33333%}.column.is-5,.column.is-5-tablet{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:41.66667%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.66667%}.column.is-6,.column.is-6-tablet{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:58.33333%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.33333%}.column.is-8,.column.is-8-tablet{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:66.66667%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.66667%}.column.is-9,.column.is-9-tablet{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:83.33333%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.33333%}.column.is-11,.column.is-11-tablet{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:91.66667%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.66667%}}@media screen and (min-width: 980px){.column.is-half-desktop{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:50%}.column.is-third-desktop{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:33.3333%}.column.is-quarter-desktop{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:25%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-third-desktop{margin-left:33.3333%}.column.is-offset-quarter-desktop{margin-left:25%}.column.is-1-desktop{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:8.33333%}.column.is-offset-1-desktop{margin-left:8.33333%}.column.is-2-desktop{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:16.66667%}.column.is-offset-2-desktop{margin-left:16.66667%}.column.is-3-desktop{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:33.33333%}.column.is-offset-4-desktop{margin-left:33.33333%}.column.is-5-desktop{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:41.66667%}.column.is-offset-5-desktop{margin-left:41.66667%}.column.is-6-desktop{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:58.33333%}.column.is-offset-7-desktop{margin-left:58.33333%}.column.is-8-desktop{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:66.66667%}.column.is-offset-8-desktop{margin-left:66.66667%}.column.is-9-desktop{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:83.33333%}.column.is-offset-10-desktop{margin-left:83.33333%}.column.is-11-desktop{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;width:91.66667%}.column.is-offset-11-desktop{margin-left:91.66667%}}.columns{margin-left:-10px;margin-right:-10px;margin-top:-10px}.columns:last-child{margin-bottom:-10px}.columns:not(:last-child){margin-bottom:10px}.columns.is-centered{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.columns.is-mobile{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.columns.is-gapless{margin-left:0;margin-right:0}.columns.is-gapless:not(:last-child){margin-bottom:20px}.columns.is-gapless>.column{margin:0;padding:0}.columns.is-multiline{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.columns.is-vcentered{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center}@media screen and (min-width: 769px){.columns.is-grid{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.columns.is-grid>.column{-webkit-flex-basis:33.3333%;-ms-flex-preferred-size:33.3333%;flex-basis:33.3333%;max-width:33.3333%;padding:10px;width:33.3333%}.columns.is-grid>.column+.column{margin-left:0}}@media screen and (min-width: 769px){.columns:not(.is-desktop){display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}}@media screen and (min-width: 980px){.columns.is-desktop{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}}.navbar-item .title,.navbar-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){.navbar-item:not(:last-child){margin-bottom:10px}}.navbar code{border-radius:3px}.navbar img{display:inline-block;vertical-align:top}@media screen and (min-width: 769px){.navbar{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.navbar>.navbar-item:not(.is-narrow){-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}}.navbar-left .navbar-item.is-flexible,.navbar-right .navbar-item.is-flexible{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.navbar-left .navbar-item:not(:last-child),.navbar-right .navbar-item:not(:last-child){margin-right:10px}@media screen and (max-width: 768px){.navbar-left+.navbar-right{margin-top:20px}}@media screen and (min-width: 769px){.navbar-left{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}}@media screen and (min-width: 769px){.navbar-right{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}}.card-image{display:block;position:relative}.card-image img{display:block}.card-image.is-square img,.card-image.is-4x3 img,.card-image.is-3x2 img{bottom:0;left:0;position:absolute;right:0;top:0;height:100%;width:100%}.card-image.is-square{padding-top:100%}.card-image.is-4x3{padding-top:75%}.card-image.is-3x2{padding-top:66.6666%}.card-content{padding:20px}.card-content .title+.subtitle{margin-top:-20px}.card-footer{background:#f5f7fa;display:block;padding:10px}.card{background:white;box-shadow:0 2px 3px rgba(0,0,0,0.1),0 0 0 1px rgba(0,0,0,0.1);max-width:100%;position:relative;width:300px}.card .media:not(:last-child){margin-bottom:10px}.card.is-rounded{border-radius:5px}.table{background:white;color:#222324;margin-bottom:20px;width:100%}.table th,.table td{border:1px solid #d3d6db;border-width:0 0 1px;padding:8px 10px;vertical-align:top}.table th.table-narrow,.table td.table-narrow{white-space:nowrap;width:1%}.table th.table-link,.table td.table-link{padding:0}.table th.table-link>a,.table td.table-link>a{display:block;padding:8px 10px}.table th.table-link>a:hover,.table td.table-link>a:hover{background:#1fc8db;color:#fff}.table th.table-icon,.table td.table-icon{padding:5px;text-align:center;white-space:nowrap;width:1%}.table th.table-icon .fa,.table td.table-icon .fa{display:inline-block;font-size:21px;height:24px;line-height:24px;text-align:center;vertical-align:top;width:24px}.table th.table-icon.table-link,.table td.table-icon.table-link{padding:0}.table th.table-icon.table-link>a,.table td.table-icon.table-link>a{padding:5px}.table th{color:#222324;text-align:left}.table tr:hover{background:rgba(245,247,250,0.5);color:#222324}.table tr:last-child td{border-bottom-width:0}.table thead th,.table thead td{border-width:0 0 2px;color:#aeb1b5}.table tfoot th,.table tfoot td{border-width:2px 0 0;color:#aeb1b5}.table.is-bordered th,.table.is-bordered td{border-width:1px}.table.is-bordered tr:last-child td{border-bottom-width:1px}.table.is-narrow th,.table.is-narrow td{padding:5px 10px}.table.is-narrow th.table-link,.table.is-narrow td.table-link{padding:0}.table.is-narrow th.table-link>a,.table.is-narrow td.table-link>a{padding:5px 10px}.table.is-narrow th.table-icon,.table.is-narrow td.table-icon{padding:2px}.table.is-narrow th.table-icon.table-link,.table.is-narrow td.table-icon.table-link{padding:0}.table.is-narrow th.table-icon.table-link>a,.table.is-narrow td.table-icon.table-link>a{padding:2px}.table.is-striped tbody tr:nth-child(2n){background:rgba(245,247,250,0.5)}.table.is-striped tbody tr:nth-child(2n):hover{background:#f5f7fa}.tabs{line-height:24px;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs .fa{font-size:14px;line-height:20px;margin:2px -2px;width:20px}.tabs a{border-bottom:1px solid #d3d6db;color:#69707a;display:block;margin-bottom:-1px;padding:5px 0;vertical-align:top}.tabs a:hover{border-bottom-color:#222324;color:#222324}.tabs li{display:block;vertical-align:top}.tabs li+li{margin-left:20px}.tabs li.is-active a{border-bottom-color:#1fc8db;color:#1fc8db}.tabs ul{border-bottom:1px solid #d3d6db;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs.is-centered a{padding:5px 10px}.tabs.is-centered li+li{margin-left:0}.tabs.is-centered ul{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center}.tabs.is-right ul{-webkit-box-pack:end;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:3px 3px 0 0;padding:5px 15px}.tabs.is-boxed a:hover{background:#f5f7fa;border-bottom-color:#d3d6db}.tabs.is-boxed li+li{margin-left:5px}.tabs.is-boxed li.is-active a{background:white;border-color:#d3d6db;border-bottom-color:transparent}.tabs.is-boxed.is-centered li,.tabs.is-boxed.is-centered li+li{margin:0 2px}.tabs.is-toggle a{border:1px solid #d3d6db;margin-bottom:0;padding:5px 10px;position:relative}.tabs.is-toggle a:hover{background:#f5f7fa;border-color:#aeb1b5;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-radius:3px 0 0 3px}.tabs.is-toggle li:last-child a{border-radius:0 3px 3px 0}.tabs.is-toggle li.is-active a{background:#1fc8db;border-color:#1fc8db;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:none}@media screen and (min-width: 769px){.tabs.is-fullwidth li{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.tabs.is-fullwidth li+li{margin-left:0}.tabs.is-fullwidth ul{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center}}.media-number{background:#f5f7fa;border-radius:290486px;display:inline-block;font-size:18px;height:32px;line-height:24px;min-width:32px;padding:4px 8px;text-align:center;vertical-align:top}@media screen and (max-width: 768px){.media-number{margin-bottom:10px}}@media screen and (min-width: 769px){.media-number{margin-right:10px}}.media-left{margin-right:10px}.media-right{margin-left:10px}.media-content{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;text-align:left}.media{-webkit-box-align:start;-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;text-align:left}.media .content:not(:last-child){margin-bottom:10px}.media .media{border-top:1px solid rgba(211,214,219,0.5);display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;padding-top:10px}.media .media .textarea{border-radius:2px;font-size:11px;height:24px;line-height:16px;padding:3px 6px}.media .media .button{border-radius:2px;font-size:11px;height:24px;line-height:16px;padding:3px 6px}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:5px}.media .media .media{font-size:12px;padding-top:5px}.media .media .media+.media{margin-top:5px}.media+.media{border-top:1px solid rgba(211,214,219,0.5);margin-top:10px;padding-top:10px}.media.is-large+.media{margin-top:20px;padding-top:20px}@media screen and (min-width: 769px){.media.is-large .media-number{margin-right:20px}}.menu-icon{display:inline-block;font-size:14px;height:16px;line-height:16px;text-align:center;vertical-align:top;width:16px;color:#aeb1b5;float:left;margin:0 4px 0 -2px}.menu-icon .fa{font-size:inherit;line-height:inherit}.menu-heading{background:#f5f7fa;border-bottom:1px solid #d3d6db;border-radius:4px 4px 0 0;color:#222324;font-size:18px;font-weight:300;padding:10px}.menu-list a{color:#69707a}.menu-list a:hover{color:#1fc8db}.menu-tabs{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:11px;padding:5px 10px 0;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.menu-tabs:not(:last-child){border-bottom:1px solid #d3d6db}.menu-tabs a{border-bottom:1px solid #d3d6db;margin-bottom:-1px;padding:5px}.menu-tabs a.is-active{border-bottom-color:#222324;color:#222324}.menu-block{color:#222324;display:block;line-height:16px;padding:10px}.menu-block:not(:last-child){border-bottom:1px solid #d3d6db}.menu-block .checkbox,.menu-block .menu-checkbox{border:1px solid transparent;border-radius:3px;display:block;padding:8px;padding-left:32px}.menu-block .checkbox input,.menu-block .menu-checkbox input{left:9px;top:9px}.menu-block .checkbox:hover,.menu-block .menu-checkbox:hover{border-color:#1fc8db}a.menu-block:hover{background:#f5f7fa}.menu-checkbox{display:block;padding:9px 10px 9px 30px}.menu-checkbox:not(:last-child){border-bottom:1px solid #d3d6db}.menu-checkbox input{left:8px;top:10px}.menu{border:1px solid #d3d6db;border-radius:5px}.menu:not(:last-child){margin-bottom:20px}.modal-background{bottom:0;left:0;position:absolute;right:0;top:0;background:rgba(0,0,0,0.86)}.modal-content{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){.modal-content{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal{bottom:0;left:0;position:absolute;right:0;top:0;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center;display:none;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;overflow:hidden;position:fixed;z-index:1986}.modal.is-active{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.header{background:white;box-shadow:0 1px 2px rgba(0,0,0,0.1);display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;height:50px;line-height:24px;position:relative;text-align:center;z-index:2}.header:after{clear:both;content:" ";display:table}.header .container{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;box-shadow:0 1px 0 rgba(211,214,219,0.3);display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%}@media screen and (min-width: 769px){.header{height:50px}}@media screen and (min-width: 769px){.header-toggle{display:none}}.header-item{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;padding:10px}.header-item img{max-height:24px}.header-item a{color:#69707a}.header-item a:hover{color:#222324}.header-item a.is-active{color:#222324}.header-item .fa{font-size:21px;line-height:24px}.header-icon{display:inline-block;font-size:14px;height:24px;line-height:24px;text-align:center;vertical-align:top;width:24px;color:#69707a;margin:0 5px}.header-icon:hover{color:#222324}.header-tab{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center;border-bottom:1px solid transparent;color:#69707a;display:block;height:50px;line-height:24px;padding:13px 15px}.header-tab:hover{border-bottom:1px solid #1fc8db}.header-tab.is-active{border-bottom:3px solid #1fc8db;color:#1fc8db}.header-left{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;overflow:hidden;overflow-x:auto;white-space:nowrap}@media screen and (min-width: 980px){.header-left .header-item:first-child{padding-left:0}}.header-right{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;-ms-grid-row-align:stretch;align-items:stretch}@media screen and (min-width: 769px){.header-right{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}}@media screen and (min-width: 980px){.header-right .header-item:last-child{padding-right:0}}.header-full{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;width:100%}.header-full>.header-item{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;padding:0}.header-full>.header-item>a{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;width:100%}@media screen and (max-width: 768px){.header-menu{background:white;box-shadow:0 4px 7px rgba(0,0,0,0.1);display:none;min-width:120px;position:absolute;right:0;top:50px;z-index:100}.header-menu .header-item{border-top:1px solid rgba(211,214,219,0.5);padding:10px}.header-menu.is-active{display:block}}.header.is-centered{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.header.is-centered .header-left,.header.is-centered .header-right{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.header.is-small{background:#f5f7fa;box-shadow:none;height:40px;z-index:1}.header.is-small .container{height:40px}.header.is-small .header-tab{font-size:13px;height:40px;padding:8px 10px}.header.is-small .header-tab:hover,.header.is-small .header-tab.is-active{border-bottom-width:2px}.hero-video{bottom:0;left:0;position:absolute;right:0;top:0;overflow:hidden}.hero-video.is-transparent{opacity:0.3}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;-webkit-transform:translate3d(-50%, -50%, 0);transform:translate3d(-50%, -50%, 0)}@media screen and (max-width: 768px){.hero-video{display:none}}.hero-content{padding:40px 20px}@media screen and (min-width: 980px){.hero-content{padding:40px 0}}.hero-buttons{margin-top:20px}@media screen and (max-width: 768px){.hero-buttons .button{display:block}.hero-buttons .button:not(:last-child){margin-bottom:10px}}@media screen and (min-width: 769px){.hero-buttons{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:20px}}.hero{background:white;text-align:center}.hero .header{background:none;box-shadow:none}.hero .tabs a{border:none}.hero .tabs ul{border-bottom:none}.hero .tabs.is-boxed a{padding:8px 15px}.hero.is-alt{background:#f5f7fa;color:#aeb1b5}.hero.is-dark{background:#222324;color:#fff}.hero.is-dark .title{color:#fff}.hero.is-dark .title a,.hero.is-dark .title strong{color:inherit}.hero.is-dark .subtitle{color:rgba(255,255,255,0.7)}.hero.is-dark .subtitle strong{color:#fff}.hero.is-dark .header .container{box-shadow:0 1px 0 rgba(255,255,255,0.2)}.hero.is-dark .header-icon,.hero.is-dark .header-item>a:not(.button){color:#fff;opacity:0.5}.hero.is-dark .header-icon:hover,.hero.is-dark .header-icon.is-active,.hero.is-dark .header-item>a:not(.button):hover,.hero.is-dark .header-item>a:not(.button).is-active{opacity:1}.hero.is-dark .tabs a{color:#fff;opacity:0.5}.hero.is-dark .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a{opacity:1}.hero.is-dark .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a{color:#fff}.hero.is-dark .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover{background:rgba(0,0,0,0.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background:#fff;color:#222324}.hero.is-dark.is-bold{background-image:-webkit-linear-gradient(309deg, #080a0b 0%, #222324 71%, #2c2e34 100%);background-image:linear-gradient(141deg, #080a0b 0%, #222324 71%, #2c2e34 100%)}@media screen and (max-width: 768px){.hero.is-dark .header-toggle span{background:#fff}.hero.is-dark .header-toggle:hover{background:rgba(0,0,0,0.1)}.hero.is-dark .header-toggle.is-active span{background:#fff}.hero.is-dark .header-menu{background:#222324}.hero.is-dark .header-menu .header-item{border-top-color:rgba(255,255,255,0.2)}}.hero.is-primary{background:#1fc8db;color:#fff}.hero.is-primary .title{color:#fff}.hero.is-primary .title a,.hero.is-primary .title strong{color:inherit}.hero.is-primary .subtitle{color:rgba(255,255,255,0.7)}.hero.is-primary .subtitle strong{color:#fff}.hero.is-primary .header .container{box-shadow:0 1px 0 rgba(255,255,255,0.2)}.hero.is-primary .header-icon,.hero.is-primary .header-item>a:not(.button){color:#fff;opacity:0.5}.hero.is-primary .header-icon:hover,.hero.is-primary .header-icon.is-active,.hero.is-primary .header-item>a:not(.button):hover,.hero.is-primary .header-item>a:not(.button).is-active{opacity:1}.hero.is-primary .tabs a{color:#fff;opacity:0.5}.hero.is-primary .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a{opacity:1}.hero.is-primary .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover{background:rgba(0,0,0,0.1)}.hero.is-primary .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background:#fff;color:#1fc8db}.hero.is-primary.is-bold{background-image:-webkit-linear-gradient(309deg, #0fb8ad 0%, #1fc8db 71%, #2cb5e8 100%);background-image:linear-gradient(141deg, #0fb8ad 0%, #1fc8db 71%, #2cb5e8 100%)}@media screen and (max-width: 768px){.hero.is-primary .header-toggle span{background:#fff}.hero.is-primary .header-toggle:hover{background:rgba(0,0,0,0.1)}.hero.is-primary .header-toggle.is-active span{background:#fff}.hero.is-primary .header-menu{background:#1fc8db}.hero.is-primary .header-menu .header-item{border-top-color:rgba(255,255,255,0.2)}}.hero.is-info{background:#42afe3;color:#fff}.hero.is-info .title{color:#fff}.hero.is-info .title a,.hero.is-info .title strong{color:inherit}.hero.is-info .subtitle{color:rgba(255,255,255,0.7)}.hero.is-info .subtitle strong{color:#fff}.hero.is-info .header .container{box-shadow:0 1px 0 rgba(255,255,255,0.2)}.hero.is-info .header-icon,.hero.is-info .header-item>a:not(.button){color:#fff;opacity:0.5}.hero.is-info .header-icon:hover,.hero.is-info .header-icon.is-active,.hero.is-info .header-item>a:not(.button):hover,.hero.is-info .header-item>a:not(.button).is-active{opacity:1}.hero.is-info .tabs a{color:#fff;opacity:0.5}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background:rgba(0,0,0,0.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background:#fff;color:#42afe3}.hero.is-info.is-bold{background-image:-webkit-linear-gradient(309deg, #13bfdf 0%, #42afe3 71%, #53a1eb 100%);background-image:linear-gradient(141deg, #13bfdf 0%, #42afe3 71%, #53a1eb 100%)}@media screen and (max-width: 768px){.hero.is-info .header-toggle span{background:#fff}.hero.is-info .header-toggle:hover{background:rgba(0,0,0,0.1)}.hero.is-info .header-toggle.is-active span{background:#fff}.hero.is-info .header-menu{background:#42afe3}.hero.is-info .header-menu .header-item{border-top-color:rgba(255,255,255,0.2)}}.hero.is-success{background:#97cd76;color:#fff}.hero.is-success .title{color:#fff}.hero.is-success .title a,.hero.is-success .title strong{color:inherit}.hero.is-success .subtitle{color:rgba(255,255,255,0.7)}.hero.is-success .subtitle strong{color:#fff}.hero.is-success .header .container{box-shadow:0 1px 0 rgba(255,255,255,0.2)}.hero.is-success .header-icon,.hero.is-success .header-item>a:not(.button){color:#fff;opacity:0.5}.hero.is-success .header-icon:hover,.hero.is-success .header-icon.is-active,.hero.is-success .header-item>a:not(.button):hover,.hero.is-success .header-item>a:not(.button).is-active{opacity:1}.hero.is-success .tabs a{color:#fff;opacity:0.5}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:#fff}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background:rgba(0,0,0,0.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background:#fff;color:#97cd76}.hero.is-success.is-bold{background-image:-webkit-linear-gradient(309deg, #8ecb45 0%, #97cd76 71%, #96d885 100%);background-image:linear-gradient(141deg, #8ecb45 0%, #97cd76 71%, #96d885 100%)}@media screen and (max-width: 768px){.hero.is-success .header-toggle span{background:#fff}.hero.is-success .header-toggle:hover{background:rgba(0,0,0,0.1)}.hero.is-success .header-toggle.is-active span{background:#fff}.hero.is-success .header-menu{background:#97cd76}.hero.is-success .header-menu .header-item{border-top-color:rgba(255,255,255,0.2)}}.hero.is-warning{background:#fce473;color:rgba(0,0,0,0.5)}.hero.is-warning .title{color:rgba(0,0,0,0.5)}.hero.is-warning .title a,.hero.is-warning .title strong{color:inherit}.hero.is-warning .subtitle{color:rgba(0,0,0,0.7)}.hero.is-warning .subtitle strong{color:rgba(0,0,0,0.5)}.hero.is-warning .header .container{box-shadow:0 1px 0 rgba(0,0,0,0.2)}.hero.is-warning .header-icon,.hero.is-warning .header-item>a:not(.button){color:rgba(0,0,0,0.5);opacity:0.5}.hero.is-warning .header-icon:hover,.hero.is-warning .header-icon.is-active,.hero.is-warning .header-item>a:not(.button):hover,.hero.is-warning .header-item>a:not(.button).is-active{opacity:1}.hero.is-warning .tabs a{color:rgba(0,0,0,0.5);opacity:0.5}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,0.5)}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background:rgba(0,0,0,0.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background:rgba(0,0,0,0.5);color:#fce473}.hero.is-warning.is-bold{background-image:-webkit-linear-gradient(309deg, #ffbd3d 0%, #fce473 71%, #fffe8a 100%);background-image:linear-gradient(141deg, #ffbd3d 0%, #fce473 71%, #fffe8a 100%)}@media screen and (max-width: 768px){.hero.is-warning .header-toggle span{background:rgba(0,0,0,0.5)}.hero.is-warning .header-toggle:hover{background:rgba(0,0,0,0.1)}.hero.is-warning .header-toggle.is-active span{background:rgba(0,0,0,0.5)}.hero.is-warning .header-menu{background:#fce473}.hero.is-warning .header-menu .header-item{border-top-color:rgba(0,0,0,0.2)}}.hero.is-danger{background:#ed6c63;color:#fff}.hero.is-danger .title{color:#fff}.hero.is-danger .title a,.hero.is-danger .title strong{color:inherit}.hero.is-danger .subtitle{color:rgba(255,255,255,0.7)}.hero.is-danger .subtitle strong{color:#fff}.hero.is-danger .header .container{box-shadow:0 1px 0 rgba(255,255,255,0.2)}.hero.is-danger .header-icon,.hero.is-danger .header-item>a:not(.button){color:#fff;opacity:0.5}.hero.is-danger .header-icon:hover,.hero.is-danger .header-icon.is-active,.hero.is-danger .header-item>a:not(.button):hover,.hero.is-danger .header-item>a:not(.button).is-active{opacity:1}.hero.is-danger .tabs a{color:#fff;opacity:0.5}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background:rgba(0,0,0,0.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background:#fff;color:#ed6c63}.hero.is-danger.is-bold{background-image:-webkit-linear-gradient(309deg, #f32a3e 0%, #ed6c63 71%, #f39376 100%);background-image:linear-gradient(141deg, #f32a3e 0%, #ed6c63 71%, #f39376 100%)}@media screen and (max-width: 768px){.hero.is-danger .header-toggle span{background:#fff}.hero.is-danger .header-toggle:hover{background:rgba(0,0,0,0.1)}.hero.is-danger .header-toggle.is-active span{background:#fff}.hero.is-danger .header-menu{background:#ed6c63}.hero.is-danger .header-menu .header-item{border-top-color:rgba(255,255,255,0.2)}}@media screen and (min-width: 769px){.hero.is-fullheight .tabs,.hero.is-large .tabs{font-size:18px}}@media screen and (min-width: 769px){.hero.is-medium .hero-content{padding:120px 20px}}@media screen and (min-width: 980px){.hero.is-medium .hero-content{padding:120px 0}}.hero.is-large .tabs a{padding:10px 15px}@media screen and (min-width: 769px){.hero.is-large .hero-content{padding:240px 20px}}@media screen and (min-width: 980px){.hero.is-large .hero-content{padding:240px 0}}.hero.is-fullheight{-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;min-height:100vh}.hero.is-fullheight .tabs a{padding:15px 20px}.hero.is-fullheight .hero-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.hero.is-left{text-align:left}.hero.is-right{text-align:right}.section{background:white;padding:40px 20px}.section+.section{border-top:1px solid rgba(211,214,219,0.5)}@media screen and (min-width: 980px){.section{padding:40px 0}.section.is-medium{padding:120px 0}.section.is-large{padding:240px 0}}.footer{background:#f5f7fa;padding:40px 20px 80px}.footer a{color:#69707a}.footer a:hover{color:#222324}.footer a:not(.icon){border-bottom:1px solid #d3d6db}.footer a:not(.icon):hover{border-bottom-color:#1fc8db}
2 | /*# sourceMappingURL=bulma.min.css.map */
--------------------------------------------------------------------------------
/knexfile.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./server/config/db_config');
2 |
--------------------------------------------------------------------------------
/migrations/20150927195358-create-user-and-phrases.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.up = function(knex, Promise) {
4 | return knex.schema.createTable('users', function(table) {
5 | table.increments('id').primary();
6 | table.text('email').unique();
7 | table.text('encryptedPassword');
8 | table.boolean('admin');
9 | }).createTable('phrases', function(table) {
10 | table.increments('id').primary();
11 | table.text('title');
12 | table.text('description');
13 | table.integer('user_id').references('users.id');
14 | });
15 | };
16 |
17 | exports.down = function(knex, Promise) {
18 | return knex.schema.dropTable('phrases')
19 | .dropTable('users');
20 | };
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lehrer-node",
3 | "version": "1.0.0",
4 | "description": "deutsch lernen",
5 | "main": "index.js",
6 | "author": "",
7 | "license": "ISC",
8 | "dependencies": {
9 | "bcryptjs": "^2.2.1",
10 | "bluebird": "^3.3.3",
11 | "hapi": "^13.0.0",
12 | "hapi-auth-jwt2": "^5.4.1",
13 | "good": "6.6.0",
14 | "good-console": "5.3.1",
15 | "jsonwebtoken": "^5.7.0",
16 | "lodash": "^4.6.1",
17 | "pg": "^4.5.1",
18 | "pg-hstore": "^2.3.2",
19 | "knex": "^0.9.0",
20 | "bookshelf": "^0.9.0",
21 | "axios": "^0.9.1",
22 | "react": "15.0.0",
23 | "react-dom": "15.0.0",
24 | "react-router": "^2.0.0",
25 | "fbemitter": "^2.0.2"
26 | },
27 | "devDependencies": {
28 | "babel-core": "^6.5.2",
29 | "babel-eslint": "^6.0.0-beta.6",
30 | "babel-loader": "^6.2.4",
31 | "babel-polyfill": "^6.7.4",
32 | "babel-preset-es2015": "^6.6.0",
33 | "babel-preset-react": "^6.5.0",
34 | "babel-preset-stage-3": "^6.5.0",
35 | "chai": "^3.5.0",
36 | "code": "^2.1.0",
37 | "eslint": "^2.7.0",
38 | "eslint-config-airbnb": "^6.2.0",
39 | "eslint-config-standard": "^5.1.0",
40 | "eslint-config-standard-react": "^2.2.0",
41 | "eslint-plugin-babel": "^3.0.0",
42 | "eslint-plugin-flow-vars": "^0.3.0",
43 | "eslint-plugin-promise": "^1.0.8",
44 | "eslint-plugin-react": "^4.3.0",
45 | "eslint-plugin-standard": "^1.3.1",
46 | "html-webpack-plugin": "^2.9.0",
47 | "lab": "10.2.0",
48 | "mocha": "^2.4.5",
49 | "webpack": "^1.12.14",
50 | "webpack-dev-server": "^1.14.1"
51 | },
52 | "scripts": {
53 | "test": "node_modules/.bin/lab && node_modules/.bin/mocha 'test/**/*.js'",
54 | "migratedb": "node_modules/.bin/knex migrate:latest",
55 | "serve": "nodemon server.js",
56 | "lint": "eslint ./client/app",
57 | "webpack": "webpack-dev-server",
58 | "webpack-prod": "webpack -p"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Hapi = require('hapi'),
4 | _ = require('lodash'),
5 | config = require('./server/config/app'),
6 | routes = require('./server/config/routes'),
7 | User = require('./server/models/user');
8 |
9 | // Create a server with a host and port
10 | var server = new Hapi.Server();
11 |
12 | //Server config
13 | server.connection(_.pick(config, ['host', 'port', 'routes']));
14 |
15 | //Logging setup
16 | var goodOptions = {
17 | reporters: [{
18 | reporter: require('good-console'),
19 | events: { log: '*', request: '*', response: '*' }
20 | }]
21 | };
22 |
23 | //Auth
24 | server.register([require('hapi-auth-jwt2'), { register: require('good'), options: goodOptions }], function(err) {
25 | if(err){
26 | console.log(err);
27 | }
28 |
29 | server.auth.strategy(
30 | 'jwt', 'jwt', { key: config.secretKey,
31 | validateFunc: User.validate,
32 | verifyOptions: { algorithms: [ 'HS256' ] }
33 | });
34 |
35 | server.auth.default('jwt');
36 | });
37 |
38 | // Add the route
39 | server.route(routes.config);
40 |
41 | // Start the server
42 | server.start(function() {
43 | console.log('Server running at:', server.info.uri);
44 | });
45 |
46 | module.exports = server;
47 |
--------------------------------------------------------------------------------
/server/config/app.js:
--------------------------------------------------------------------------------
1 | var config = {
2 | development: {
3 | host: 'localhost',
4 | port: 3000,
5 | routes: {cors: true},
6 | secretKey: 'so74565467rs3cr3t132189328213213n123123dasd12341239i0dsf'
7 | },
8 | test: {
9 | host: 'localhost',
10 | port: 3001,
11 | secretKey: 'foobar'
12 | }
13 | }
14 |
15 | var env = process.env['NODE_ENV'] || 'development';
16 | module.exports = config[env];
17 |
--------------------------------------------------------------------------------
/server/config/bookshelf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const dbConfig = require('./db_config');
4 | const knex = require('knex')(dbConfig);
5 |
6 | module.exports = require('bookshelf')(knex);
7 |
--------------------------------------------------------------------------------
/server/config/db_config.js:
--------------------------------------------------------------------------------
1 | const db_config = {
2 | development: {
3 | client: 'postgresql',
4 | connection: {
5 | user: "postgresdev",
6 | password: "postgresdev",
7 | database: "lehrer_node_dev",
8 | host: "localhost"
9 | },
10 | pool: {
11 | min: 5,
12 | max: 10
13 | }
14 | },
15 | test: {
16 | client: 'postgresql',
17 | connection: {
18 | user: "postgresdev",
19 | password: "postgresdev",
20 | database: "lehrer_node_test",
21 | host: "localhost"
22 | },
23 | pool: {
24 | min: 5,
25 | max: 10
26 | }
27 | },
28 | };
29 |
30 | var env = process.env['NODE_ENV'] || 'development';
31 | module.exports = db_config[env];
32 |
--------------------------------------------------------------------------------
/server/config/routes.js:
--------------------------------------------------------------------------------
1 | var GreetingController = require('../controllers/greeting_controller'),
2 | UsersController = require('../controllers/users_controller'),
3 | SessionsController = require('../controllers/sessions_controller'),
4 | PhrasesController = require('../controllers/phrases_controller');
5 |
6 |
7 | var routes = {
8 | config: [
9 | {method: 'GET', path: '/api/greeting', config: {handler: GreetingController.show}},
10 | {method: 'POST', path: '/api/users', config: {auth: false, handler: UsersController.create}},
11 | {method: 'GET', path: '/api/session', config: {handler: SessionsController.show}},
12 | {method: 'POST', path: '/api/session', config: {auth: false, handler: SessionsController.create}},
13 | {method: 'DELETE', path: '/api/session', config: {handler: SessionsController.delete}},
14 | {method: 'POST', path: '/api/phrases', config: {handler: PhrasesController.create}},
15 | {method: 'GET', path: '/api/phrases', config: {handler: PhrasesController.index}}
16 | ]
17 | };
18 |
19 | module.exports = routes;
20 |
--------------------------------------------------------------------------------
/server/controllers/greeting_controller.js:
--------------------------------------------------------------------------------
1 | var User = require('./../models/user');
2 |
3 | var greetingController = function() {
4 | return {
5 | show: function(request, reply) {
6 | const userId = request.auth.credentials.id;
7 | const promise = User.find(userId);
8 | promise.then(function(user){ reply({greeting: `Hello ${user.attributes.email}!`}); })
9 | .catch(function(error){ reply(error).code(500); });
10 | }
11 | }
12 | }();
13 |
14 | module.exports = greetingController;
15 |
--------------------------------------------------------------------------------
/server/controllers/phrases_controller.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Phrase = require('./../models/phrase'),
4 | User = require('./../models/user'),
5 | config = require('./../config/app');
6 |
7 | var phrasesController = function() {
8 | return {
9 | create: function(request, reply) {
10 | const userId = request.auth.credentials.id;
11 | const promise = new Phrase().create(request.payload.title, request.payload.description, userId);
12 | promise.then(function(data){ reply(data); })
13 | .catch(function(error){ reply(error); });
14 | },
15 |
16 | index: function(request, reply) {
17 | const userId = request.auth.credentials.id;
18 | const promise = new User().allPhrases(userId);
19 | promise.then(function(data){ reply(data); })
20 | .catch(function(error){ reply(error); });
21 | }
22 | }
23 | }();
24 |
25 | module.exports = phrasesController;
26 |
--------------------------------------------------------------------------------
/server/controllers/sessions_controller.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var User = require('./../models/user');
4 |
5 | var sessionsController = function(){
6 | return {
7 |
8 | create: function(request, reply) {
9 | const promise = new User().login(request.payload.email, request.payload.password);
10 | promise.then(function(data) { reply(data); })
11 | .catch(function(error){ reply(error).code(422); });
12 | },
13 |
14 | show: function(request, reply) {
15 | reply({success: true});
16 | },
17 |
18 | delete: function(request, reply) {
19 | //TODO: Maybe invalidate a token
20 | reply({success: true});
21 | }
22 |
23 | }
24 | }();
25 |
26 | module.exports = sessionsController;
27 |
--------------------------------------------------------------------------------
/server/controllers/users_controller.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var User = require('./../models/user');
4 |
5 | var usersController = function(){
6 | return {
7 |
8 | create: function(request, reply) {
9 | const promise = new User().create(request.payload);
10 | promise.then(function(data){ reply(data).code(201); })
11 | .catch(function(error){ reply(error).code(422); });
12 | }
13 |
14 | }
15 | }();
16 |
17 | module.exports = usersController;
18 |
--------------------------------------------------------------------------------
/server/models/phrase.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
--------------------------------------------------------------------------------
/server/models/user.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var config = require('./../config/app'),
4 | bookshelf = require('./../config/bookshelf'),
5 | Promise = require('bluebird'),
6 | Bcrypt = require('bcryptjs'),
7 | Jwt = require('jsonwebtoken');
8 |
9 | var User = bookshelf.Model.extend({
10 | tableName: 'users',
11 |
12 | create: function(params) {
13 | const self = this;
14 | return new Promise(function(resolve, reject) {
15 | if(params.email === null ||
16 | params.password === null ||
17 | params.password.length < 6 ||
18 | params.password !== params.passwordConfirmation) {
19 | reject({error: 'error while validating data'});
20 | } else {
21 | let promise = self.save({email: params.email,
22 | encryptedPassword: Bcrypt.hashSync(params.password, 10)});
23 | promise.then(function(data) {
24 | resolve(this.token(data.attributes.id));
25 | });
26 | promise.catch(function(e) {
27 | reject({error: e});
28 | });
29 | }
30 | });
31 | },
32 |
33 | login: function(email, password) {
34 | const self = this;
35 | return new Promise(function(resolve, reject) {
36 | var promise = self.fetch({email: email});
37 |
38 | promise.then(function(data) {
39 | if(data === null){
40 | reject({error: 'bad username or password'});
41 | }
42 | Bcrypt.compare(password, data.attributes.encryptedPassword, function (err, isValid) {
43 | if(isValid)
44 | resolve(this.token(data.attributes.id));
45 | else
46 | reject({error: 'bad username or password'});
47 | }.bind(this));
48 | });
49 |
50 | promise.catch(function(e) {
51 | reject({error: e});
52 | });
53 | });
54 | },
55 |
56 | token: function(userId) {
57 | return({token: Jwt.sign({ id: userId }, config.secretKey, {expiresIn: '1h'})});
58 | }
59 | }, {//class methods start
60 |
61 | find: function(userId) {
62 | return new User({id: userId}).fetch();
63 | },
64 |
65 | validate: function (decoded, request, callback) {
66 | const promise = new User({id: decoded.id}).fetch();
67 | promise.then(function(data) {
68 | if(data === null){
69 | return callback(null, false);
70 | } else {
71 | return callback(null, true);
72 | }
73 | });
74 | promise.catch(function(e) {
75 | return callback(null, false);
76 | });
77 | }
78 |
79 | });
80 |
81 |
82 | module.exports = User;
83 |
--------------------------------------------------------------------------------
/server/test/requests/api_test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | //var chai = require('chai'),
4 | // expect = chai.expect;
5 | var Lab = require("lab");
6 | var lab = exports.lab = Lab.script();
7 | var Code = require("code");
8 | var server = require('../../../index.js');
9 |
10 |
11 | lab.experiment('the backend API', function() {
12 |
13 | lab.test('401 to GET /api/hello', function (done) {
14 | var options = {
15 | method: "GET",
16 | url: "/api/hello"
17 | };
18 | server.inject(options, function(response) {
19 | Code.expect(response.statusCode).to.equal(401);
20 | //server.stop(done);
21 | });
22 | });
23 |
24 | lab.test('422 to POST /api/users', function (done) {
25 | var options = {
26 | method: "POST",
27 | url: "/api/users",
28 | payload: {
29 | email: "rockyj@example.com",
30 | password: "123456",
31 | passwordConfirmation: "1234569999"
32 | }
33 | };
34 | server.inject(options, function(response) {
35 | Code.expect(response.statusCode).to.equal(422);
36 | //server.stop(done);
37 | });
38 | });
39 |
40 | lab.afterEach(function (done) {
41 | server.stop(done);
42 | })
43 |
44 | });
45 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var HtmlWebpackPlugin = require('html-webpack-plugin')
2 |
3 | var HTMLWebpackPluginConfig = new HtmlWebpackPlugin({
4 | template: __dirname + '/client/app/index.html',
5 | filename: 'index.html',
6 | inject: 'body'
7 | })
8 |
9 | module.exports = {
10 | entry: [
11 | 'babel-polyfill',
12 | './client/app/index.js'
13 | ],
14 | output: {
15 | path: __dirname + '/client/dist',
16 | filename: "index_bundle.js"
17 | },
18 | module: {
19 | loaders: [
20 | {test: /\.js$/, exclude: /node_modules/, loader: "babel-loader"}
21 | ]
22 | },
23 | plugins: [HTMLWebpackPluginConfig]
24 | }
25 |
--------------------------------------------------------------------------------