├── .env.example
├── .eslintrc.js
├── .gitignore
├── LICENSE.md
├── README.md
├── app.js
├── bin
└── www
├── config
├── app.js
└── bookshelf.js
├── controllers
└── .gitkeep
├── knexfile.js
├── migrations
└── 20171203143245_users.js
├── models
└── User.js
├── package.json
├── postcss.config.js
├── public
├── css
│ └── style.css
├── img
│ ├── cc-voyager-logo.svg
│ └── cc-voyager.svg
├── js
│ ├── global.bundle.js
│ └── home.bundle.js
└── robots.txt
├── routes
└── web.js
├── src
├── global.js
├── home.js
├── img
│ ├── .gitkeep
│ ├── cc-voyager-logo.svg
│ └── cc-voyager.svg
└── scss
│ ├── fonts
│ ├── .gitkeep
│ ├── lato.scss
│ └── roboto-mono.scss
│ ├── global
│ ├── _variables.scss
│ └── util.scss
│ ├── pages
│ └── index.scss
│ ├── partials
│ ├── footer.scss
│ └── header.scss
│ └── style.scss
├── test
└── app.test.js
├── views
├── error.hbs
├── index.hbs
├── layouts
│ └── main.hbs
└── partials
│ ├── footer.hbs
│ └── header.hbs
├── webpack.config.js
└── yarn.lock
/.env.example:
--------------------------------------------------------------------------------
1 | DB_HOST=
2 | DB_USER=
3 | DB_PASSWORD=
4 | DB_NAME=
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es6: true
5 | },
6 | extends: 'eslint:recommended',
7 | parserOptions: {
8 | sourceType: 'module',
9 | ecmaVersion: 2017
10 | },
11 | rules: {
12 | 'linebreak-style': ['error', 'unix'],
13 | quotes: ['error', 'single'],
14 | semi: ['error', 'never']
15 | },
16 | globals: {
17 | module: true,
18 | require: true,
19 | __dirname: true,
20 | process: true,
21 | describe: true,
22 | it: true
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://docs.npmjs.com/cli/shrinkwrap#caveats
27 | node_modules
28 |
29 | # Debug log from npm
30 | npm-debug.log
31 | yarn-error.log
32 |
33 | .env
34 | *.todo
35 | .ds_store
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | ## The MIT License (MIT)
2 |
3 | Copyright © 2017-2018 Chris Courses
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 | ## About Voyager
4 |
5 | Voyager is a Node and SQL based web application framework that provides everything you need to produce robust, scalable, database-driven web apps, including:
6 |
7 | * Built-in user authentication (opt-in)
8 | * [Simplistic database migrations (Knex)](http://knexjs.org/)
9 | * [Easy-to-use ORM (Sequelize)](http://docs.sequelizejs.com/)
10 | * [Clean, full-featured layout templating (Handlebars)](http://handlebarsjs.com/)
11 | * [Robust front-end build system (Webpack)](https://webpack.js.org/)
12 | * Modern ES6 syntax
13 |
14 | Built on top of Express, and modeled after Rails and Laravel, Voyager provides a complete solution to getting up and running with a user-based app as quickly as possible.
15 |
16 | ## Getting Started
17 |
18 | 1. Download the [Voyager command line interface (CLI)](<(https://github.com/chriscourses/voyager-cli)>) with NPM:
19 |
20 | npm install -g voyager-cli
21 |
22 | 2. Create a new Voyager project, with or without auth:
23 |
24 | voyager new newApp
25 |
26 | or
27 |
28 | voyager new newApp --auth
29 |
30 | _Notice: You must run the built-in Knex migrations and add a .env file with valid credentials for the auth version to work. Further auth related instructions can be found under **Auth Setup** below._
31 |
32 | 3. Change directory to `newApp` and start the Voyager server:
33 |
34 | cd newApp
35 | voyager start
36 |
37 | 4. Open up a new terminal tab and run webpack (requires webpack installed globally):
38 |
39 | webpack
40 |
41 | 5. Your app should open up automatically at `http://localhost:3001` and you should see the Voyager start up page.
42 |
43 | ## Auth Setup
44 |
45 | To get started with Voyager's built-in authentication, you must follow a few steps required to connect your app to a database and utilize key functionalities such as email confirmations and password resets.
46 |
47 | 1. Create a file called `.env` in the root of your newly generated Voyager project
48 | 2. Copy the contents of `.env.example` and paste inside of the newly created `.env` file:
49 |
50 | DB_HOST=
51 | DB_USER=
52 | DB_PASSWORD=
53 | DB_NAME=
54 |
55 | MAILGUN_KEY=
56 | MAILGUN_DOMAIN=
57 |
58 | 3. Create a MySQL database for your app, grab a [Mailgun API key](https://www.mailgun.com/), and insert the corresponding values into the `.env` file. A finished version will look something like this for a local server:
59 |
60 | DB_HOST=localhost
61 | DB_USER=root
62 | DB_PASSWORD=root
63 | DB_NAME=newApp
64 |
65 | MAILGUN_KEY=key-kfvud83k3kf3vbn22k223222
66 | MAILGUN_DOMAIN=mailgun.yourdomain.com
67 |
68 | 4. Install knex if you haven't already:
69 |
70 | npm install knex -g
71 |
72 | 5. Run knex migrations:
73 |
74 | knex migrate:latest
75 |
76 | 6. Restart the Voyager server:
77 |
78 | voyager start
79 |
80 | 7. Open a new tab in terminal and start webpack:
81 |
82 | webpack
83 |
84 | You should now have a fully functioning app with auth features such as user registration, user login, email confirmation, and password reset functionality. For more information and instruction regarding Voyager auth, check the [Chris Courses YouTube channel](https://www.youtube.com/c/chriscourses) for Voyager tutorials and more.
85 |
86 | ## Quick Docs
87 |
88 | #### Knex Migration API
89 |
90 | Run Migrations: `knex migrate:latest`
91 |
92 | Rollback Last Migrations: `knex migrate:rollback`
93 |
94 | ## License
95 |
96 | Voyager is an open-source framework licensed under the [MIT License](https://opensource.org/licenses/MIT).
97 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const path = require('path')
3 | const favicon = require('serve-favicon')
4 | const logger = require('morgan')
5 | const cookieParser = require('cookie-parser')
6 | const bodyParser = require('body-parser')
7 | const config = require('./config/app')
8 |
9 | const exphbs = require('express-handlebars')
10 |
11 | if (config.useEnv) require('dotenv-safe').load() // Must load as early as possible
12 |
13 | const routes = require('./routes/web')
14 |
15 | const app = express()
16 |
17 | // view engine setup
18 | const hbs = exphbs.create({
19 | extname: '.hbs',
20 | defaultLayout: 'main',
21 | helpers: {
22 | ifeq(a, b, options) {
23 | if (a === b) return options.fn(this)
24 | return options.inverse(this)
25 | },
26 | toJSON(object) {
27 | return JSON.stringify(object)
28 | }
29 | }
30 | })
31 |
32 | app.engine('.hbs', hbs.engine)
33 | app.set('views', path.join(__dirname, 'views'))
34 | app.set('view engine', '.hbs')
35 |
36 | // uncomment after placing your favicon in /public
37 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
38 | app.use(logger('dev'))
39 | app.use(bodyParser.json())
40 | app.use(bodyParser.urlencoded({ extended: false }))
41 | app.use(cookieParser())
42 | app.use(express.static(path.join(__dirname, 'public')))
43 |
44 | app.use('/', routes)
45 |
46 | // catch 404 and forward to error handler
47 | app.use((req, res, next) => {
48 | const err = new Error('Not Found')
49 | err.status = 404
50 | next(err)
51 | })
52 |
53 | // error handler
54 | app.use((err, req, res) => {
55 | // set locals, only providing error in development
56 | res.locals.message = err.message
57 | res.locals.error = req.app.get('env') === 'development' ? err : {}
58 |
59 | // render the error page
60 | res.status(err.status || 500)
61 | res.render('error')
62 | })
63 |
64 | module.exports = app
65 |
--------------------------------------------------------------------------------
/bin/www:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 |
5 | const app = require('../app')
6 | const debug = require('debug')('chris-courses-2018:server')
7 | const http = require('http')
8 |
9 | /**
10 | * Get port from environment and store in Express.
11 | */
12 |
13 | const port = normalizePort(process.env.PORT || '3000')
14 | app.set('port', port)
15 |
16 | /**
17 | * Create HTTP server.
18 | */
19 |
20 | const server = http.createServer(app)
21 |
22 | /**
23 | * Listen on provided port, on all network interfaces.
24 | */
25 |
26 | server.listen(port)
27 | server.on('error', onError)
28 | server.on('listening', onListening)
29 |
30 | /**
31 | * Normalize a port into a number, string, or false.
32 | */
33 |
34 | function normalizePort(val) {
35 | const port = parseInt(val, 10)
36 |
37 | if (isNaN(port)) {
38 | // named pipe
39 | return val
40 | }
41 |
42 | if (port >= 0) {
43 | // port number
44 | return port
45 | }
46 |
47 | return false
48 | }
49 |
50 | /**
51 | * Event listener for HTTP server "error" event.
52 | */
53 |
54 | function onError(error) {
55 | if (error.syscall !== 'listen') {
56 | throw error
57 | }
58 |
59 | const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port
60 |
61 | // handle specific listen errors with friendly messages
62 | switch (error.code) {
63 | case 'EACCES':
64 | console.error(bind + ' requires elevated privileges')
65 | process.exit(1)
66 | break
67 | case 'EADDRINUSE':
68 | console.error(bind + ' is already in use')
69 | process.exit(1)
70 | break
71 | default:
72 | throw error
73 | }
74 | }
75 |
76 | /**
77 | * Event listener for HTTP server "listening" event.
78 | */
79 |
80 | function onListening() {
81 | const addr = server.address()
82 | const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port
83 | debug('Listening on ' + bind)
84 | }
85 |
--------------------------------------------------------------------------------
/config/app.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | useEnv: false
3 | }
4 |
--------------------------------------------------------------------------------
/config/bookshelf.js:
--------------------------------------------------------------------------------
1 | const config = require('../knexfile')
2 | const knex = require('knex')(config)
3 | const bookshelf = require('bookshelf')(knex)
4 |
5 | knex.migrate.latest()
6 |
7 | module.exports = bookshelf
8 |
--------------------------------------------------------------------------------
/controllers/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriscourses/voyager/21a1ede8acb366fdc0bd29be4a879359b3c1df0e/controllers/.gitkeep
--------------------------------------------------------------------------------
/knexfile.js:
--------------------------------------------------------------------------------
1 | const dotenv = require('dotenv')
2 |
3 | dotenv.load()
4 |
5 | module.exports = {
6 | client: 'mysql',
7 | connection: process.env.DATABASE_URL || {
8 | host: process.env.DB_HOST,
9 | user: process.env.DB_USER,
10 | password: process.env.DB_PASSWORD,
11 | database: process.env.DB_NAME
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/migrations/20171203143245_users.js:
--------------------------------------------------------------------------------
1 | exports.up = function(knex, Promise) {
2 | return Promise.all([
3 | knex.schema.createTable('users', function(table) {
4 | table.increments()
5 | table.string('username', 20).unique()
6 | table.string('email', 100).unique()
7 | table.binary('password', 60)
8 | table.timestamps()
9 | })
10 | ])
11 | }
12 |
13 | exports.down = function(knex, Promise) {
14 | return Promise.all([knex.schema.dropTable('users')])
15 | }
16 |
--------------------------------------------------------------------------------
/models/User.js:
--------------------------------------------------------------------------------
1 | const bookshelf = require('../config/bookshelf')
2 |
3 | const User = bookshelf.Model.extend({
4 | tableName: 'users'
5 | })
6 |
7 | module.exports = User
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "voyager",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "nodemon ./bin/www"
7 | },
8 | "dependencies": {
9 | "body-parser": "~1.18.2",
10 | "bookshelf": "^0.12.0",
11 | "cookie-parser": "~1.4.3",
12 | "debug": "~2.6.9",
13 | "dotenv-safe": "^4.0.4",
14 | "express": "~4.15.5",
15 | "express-handlebars": "^3.0.0",
16 | "hbs": "~4.0.1",
17 | "knex": "^0.14.2",
18 | "morgan": "~1.9.0",
19 | "mysql": "^2.15.0",
20 | "serve-favicon": "~2.4.5"
21 | },
22 | "devDependencies": {
23 | "babel-core": "^6.26.0",
24 | "babel-loader": "^7.1.2",
25 | "babel-preset-env": "^1.6.1",
26 | "browser-sync": "^2.18.13",
27 | "browser-sync-webpack-plugin": "^1.2.0",
28 | "chai": "^4.1.2",
29 | "css-loader": "^0.28.7",
30 | "extract-text-webpack-plugin": "^3.0.2",
31 | "file-loader": "^1.1.6",
32 | "mocha": "^4.0.1",
33 | "node-sass": "^4.7.2",
34 | "postcss-loader": "^2.0.9",
35 | "prettier": "^1.10.2",
36 | "sass-loader": "^6.0.6",
37 | "style-loader": "^0.19.1",
38 | "supertest": "^3.0.0",
39 | "webpack": "^3.10.0"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer')({
4 | /* ...options */
5 | })
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/public/css/style.css:
--------------------------------------------------------------------------------
1 | /* latin-ext */
2 | @font-face {
3 | font-family: 'Lato';
4 | font-style: normal;
5 | font-weight: 300;
6 | src: local("Lato Light"), local("Lato-Light"), url(https://fonts.gstatic.com/s/lato/v14/S6u9w4BMUTPHh7USSwaPGQ3q5d0N7w.woff2) format("woff2");
7 | unicode-range: U+0100-024f, U+0259, U+1-1eff, U+2020, U+20a0-20ab, U+20ad-20cf, U+2113, U+2c60-2c7f, U+A720-A7FF; }
8 |
9 | /* latin */
10 | @font-face {
11 | font-family: 'Lato';
12 | font-style: normal;
13 | font-weight: 300;
14 | src: local("Lato Light"), local("Lato-Light"), url(https://fonts.gstatic.com/s/lato/v14/S6u9w4BMUTPHh7USSwiPGQ3q5d0.woff2) format("woff2");
15 | unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02bb-02bc, U+02c6, U+02da, U+02dc, U+2000-206f, U+2074, U+20ac, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
16 |
17 | /* cyrillic-ext */
18 | @font-face {
19 | font-family: 'Roboto Mono';
20 | font-style: normal;
21 | font-weight: 400;
22 | src: local("Roboto Mono"), local("RobotoMono-Regular"), url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhGq3-cXbKDO1w.woff2) format("woff2");
23 | unicode-range: U+0460-052f, U+1c80-1c88, U+20b4, U+2de0-2dff, U+A640-A69F, U+FE2E-FE2F; }
24 |
25 | /* cyrillic */
26 | @font-face {
27 | font-family: 'Roboto Mono';
28 | font-style: normal;
29 | font-weight: 400;
30 | src: local("Roboto Mono"), local("RobotoMono-Regular"), url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhPq3-cXbKDO1w.woff2) format("woff2");
31 | unicode-range: U+0400-045f, U+0490-0491, U+04b0-04b1, U+2116; }
32 |
33 | /* greek-ext */
34 | @font-face {
35 | font-family: 'Roboto Mono';
36 | font-style: normal;
37 | font-weight: 400;
38 | src: local("Roboto Mono"), local("RobotoMono-Regular"), url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhHq3-cXbKDO1w.woff2) format("woff2");
39 | unicode-range: U+1f00-1fff; }
40 |
41 | /* greek */
42 | @font-face {
43 | font-family: 'Roboto Mono';
44 | font-style: normal;
45 | font-weight: 400;
46 | src: local("Roboto Mono"), local("RobotoMono-Regular"), url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhIq3-cXbKDO1w.woff2) format("woff2");
47 | unicode-range: U+0370-03ff; }
48 |
49 | /* vietnamese */
50 | @font-face {
51 | font-family: 'Roboto Mono';
52 | font-style: normal;
53 | font-weight: 400;
54 | src: local("Roboto Mono"), local("RobotoMono-Regular"), url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhEq3-cXbKDO1w.woff2) format("woff2");
55 | unicode-range: U+0102-0103, U+0110-0111, U+1ea0-1ef9, U+20ab; }
56 |
57 | /* latin-ext */
58 | @font-face {
59 | font-family: 'Roboto Mono';
60 | font-style: normal;
61 | font-weight: 400;
62 | src: local("Roboto Mono"), local("RobotoMono-Regular"), url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhFq3-cXbKDO1w.woff2) format("woff2");
63 | unicode-range: U+0100-024f, U+0259, U+1-1eff, U+2020, U+20a0-20ab, U+20ad-20cf, U+2113, U+2c60-2c7f, U+A720-A7FF; }
64 |
65 | /* latin */
66 | @font-face {
67 | font-family: 'Roboto Mono';
68 | font-style: normal;
69 | font-weight: 400;
70 | src: local("Roboto Mono"), local("RobotoMono-Regular"), url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhLq3-cXbKD.woff2) format("woff2");
71 | unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02bb-02bc, U+02c6, U+02da, U+02dc, U+2000-206f, U+2074, U+20ac, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
72 |
73 | /* cyrillic-ext */
74 | @font-face {
75 | font-family: 'Roboto Mono';
76 | font-style: normal;
77 | font-weight: 700;
78 | src: local("Roboto Mono Bold"), local("RobotoMono-Bold"), url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq8f7-pAVU_Lrg.woff2) format("woff2");
79 | unicode-range: U+0460-052f, U+1c80-1c88, U+20b4, U+2de0-2dff, U+A640-A69F, U+FE2E-FE2F; }
80 |
81 | /* cyrillic */
82 | @font-face {
83 | font-family: 'Roboto Mono';
84 | font-style: normal;
85 | font-weight: 700;
86 | src: local("Roboto Mono Bold"), local("RobotoMono-Bold"), url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq1f7-pAVU_Lrg.woff2) format("woff2");
87 | unicode-range: U+0400-045f, U+0490-0491, U+04b0-04b1, U+2116; }
88 |
89 | /* greek-ext */
90 | @font-face {
91 | font-family: 'Roboto Mono';
92 | font-style: normal;
93 | font-weight: 700;
94 | src: local("Roboto Mono Bold"), local("RobotoMono-Bold"), url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq9f7-pAVU_Lrg.woff2) format("woff2");
95 | unicode-range: U+1f00-1fff; }
96 |
97 | /* greek */
98 | @font-face {
99 | font-family: 'Roboto Mono';
100 | font-style: normal;
101 | font-weight: 700;
102 | src: local("Roboto Mono Bold"), local("RobotoMono-Bold"), url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmqyf7-pAVU_Lrg.woff2) format("woff2");
103 | unicode-range: U+0370-03ff; }
104 |
105 | /* vietnamese */
106 | @font-face {
107 | font-family: 'Roboto Mono';
108 | font-style: normal;
109 | font-weight: 700;
110 | src: local("Roboto Mono Bold"), local("RobotoMono-Bold"), url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq-f7-pAVU_Lrg.woff2) format("woff2");
111 | unicode-range: U+0102-0103, U+0110-0111, U+1ea0-1ef9, U+20ab; }
112 |
113 | /* latin-ext */
114 | @font-face {
115 | font-family: 'Roboto Mono';
116 | font-style: normal;
117 | font-weight: 700;
118 | src: local("Roboto Mono Bold"), local("RobotoMono-Bold"), url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq_f7-pAVU_Lrg.woff2) format("woff2");
119 | unicode-range: U+0100-024f, U+0259, U+1-1eff, U+2020, U+20a0-20ab, U+20ad-20cf, U+2113, U+2c60-2c7f, U+A720-A7FF; }
120 |
121 | /* latin */
122 | @font-face {
123 | font-family: 'Roboto Mono';
124 | font-style: normal;
125 | font-weight: 700;
126 | src: local("Roboto Mono Bold"), local("RobotoMono-Bold"), url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmqxf7-pAVU_.woff2) format("woff2");
127 | unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02bb-02bc, U+02c6, U+02da, U+02dc, U+2000-206f, U+2074, U+20ac, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
128 |
129 | .m0 {
130 | margin: 0; }
131 |
132 | .mt0 {
133 | margin-top: 0; }
134 |
135 | .mb0 {
136 | margin-bottom: 0; }
137 |
138 | .mmt10 {
139 | margin-top: 10px; }
140 | @media screen and (min-width: 576px) {
141 | .mmt10 {
142 | margin-top: 0; } }
143 |
144 | .mmt25 {
145 | margin-top: 25px; }
146 | @media screen and (min-width: 576px) {
147 | .mmt25 {
148 | margin-top: 0; } }
149 |
150 | .mmt50 {
151 | margin-top: 50px; }
152 | @media screen and (min-width: 576px) {
153 | .mmt50 {
154 | margin-top: 0; } }
155 |
156 | .mmt75 {
157 | margin-top: 75px; }
158 | @media screen and (min-width: 576px) {
159 | .mmt75 {
160 | margin-top: 0; } }
161 |
162 | .mmt100 {
163 | margin-top: 100px; }
164 | @media screen and (min-width: 576px) {
165 | .mmt100 {
166 | margin-top: 0; } }
167 |
168 | .mmb10 {
169 | margin-bottom: 10px; }
170 | @media screen and (min-width: 576px) {
171 | .mmb10 {
172 | margin-bottom: 0; } }
173 |
174 | .mmb25 {
175 | margin-bottom: 25px; }
176 | @media screen and (min-width: 576px) {
177 | .mmb25 {
178 | margin-bottom: 0; } }
179 |
180 | .mmb50 {
181 | margin-bottom: 50px; }
182 | @media screen and (min-width: 576px) {
183 | .mmb50 {
184 | margin-bottom: 0; } }
185 |
186 | .mmb75 {
187 | margin-bottom: 75px; }
188 | @media screen and (min-width: 576px) {
189 | .mmb75 {
190 | margin-bottom: 0; } }
191 |
192 | .mmb100 {
193 | margin-bottom: 100px; }
194 | @media screen and (min-width: 576px) {
195 | .mmb100 {
196 | margin-bottom: 0; } }
197 |
198 | .mpt10 {
199 | padding-top: 10px; }
200 | @media screen and (min-width: 576px) {
201 | .mpt10 {
202 | padding-top: 0; } }
203 |
204 | .mpt25 {
205 | padding-top: 25px; }
206 | @media screen and (min-width: 576px) {
207 | .mpt25 {
208 | padding-top: 0; } }
209 |
210 | .mpt50 {
211 | padding-top: 50px; }
212 | @media screen and (min-width: 576px) {
213 | .mpt50 {
214 | padding-top: 0; } }
215 |
216 | .mpt75 {
217 | padding-top: 75px; }
218 | @media screen and (min-width: 576px) {
219 | .mpt75 {
220 | padding-top: 0; } }
221 |
222 | .mpt100 {
223 | padding-top: 100px; }
224 | @media screen and (min-width: 576px) {
225 | .mpt100 {
226 | padding-top: 0; } }
227 |
228 | .mpb10 {
229 | padding-bottom: 10px; }
230 | @media screen and (min-width: 576px) {
231 | .mpb10 {
232 | padding-bottom: 0; } }
233 |
234 | .mpb25 {
235 | padding-bottom: 25px; }
236 | @media screen and (min-width: 576px) {
237 | .mpb25 {
238 | padding-bottom: 0; } }
239 |
240 | .mpb50 {
241 | padding-bottom: 50px; }
242 | @media screen and (min-width: 576px) {
243 | .mpb50 {
244 | padding-bottom: 0; } }
245 |
246 | .mpb75 {
247 | padding-bottom: 75px; }
248 | @media screen and (min-width: 576px) {
249 | .mpb75 {
250 | padding-bottom: 0; } }
251 |
252 | .mpb100 {
253 | padding-bottom: 100px; }
254 | @media screen and (min-width: 576px) {
255 | .mpb100 {
256 | padding-bottom: 0; } }
257 |
258 | @media screen and (min-width: 576px) {
259 | .mt10 {
260 | margin-top: 10px; }
261 | .mt25 {
262 | margin-top: 25px; }
263 | .mt50 {
264 | margin-top: 50px; }
265 | .mt75 {
266 | margin-top: 75px; }
267 | .mt100 {
268 | margin-top: 100px; }
269 | .mt150 {
270 | margin-top: 150px; }
271 | .mt200 {
272 | margin-top: 200px; }
273 | .mb10 {
274 | margin-bottom: 10px; }
275 | .mb15 {
276 | margin-bottom: 15px; }
277 | .mb25 {
278 | margin-bottom: 25px; }
279 | .mb50 {
280 | margin-bottom: 50px; }
281 | .mb75 {
282 | margin-bottom: 75px; }
283 | .mb100 {
284 | margin-bottom: 100px; }
285 | .pt10 {
286 | padding-top: 10px; }
287 | .pt25 {
288 | padding-top: 25px; }
289 | .pt50 {
290 | padding-top: 50px; }
291 | .pt75 {
292 | padding-top: 75px; }
293 | .pt100 {
294 | padding-top: 100px; }
295 | .pt125 {
296 | padding-top: 125px; }
297 | .pt150 {
298 | padding-top: 150px; }
299 | .pt175 {
300 | padding-top: 175px; }
301 | .pt200 {
302 | padding-top: 200px; }
303 | .pb10 {
304 | padding-bottom: 10px; }
305 | .pb25 {
306 | padding-bottom: 25px; }
307 | .pb50 {
308 | padding-bottom: 50px; }
309 | .pb75 {
310 | padding-bottom: 75px; }
311 | .pb100 {
312 | padding-bottom: 100px; }
313 | .pb150 {
314 | padding-bottom: 150px; }
315 | .pb200 {
316 | padding-bottom: 200px; } }
317 |
318 | @media screen and (min-width: 576px) {
319 | .hide-desktop {
320 | display: none; } }
321 |
322 | .hide-mobile {
323 | display: none; }
324 | @media screen and (min-width: 576px) {
325 | .hide-mobile {
326 | display: block; } }
327 |
328 | header {
329 | padding: 12px;
330 | border-bottom: 1px solid #eee;
331 | background: white; }
332 |
333 | .header-container {
334 | display: -webkit-box;
335 | display: -ms-flexbox;
336 | display: flex;
337 | -webkit-box-pack: justify;
338 | -ms-flex-pack: justify;
339 | justify-content: space-between;
340 | -webkit-box-align: center;
341 | -ms-flex-align: center;
342 | align-items: center; }
343 |
344 | .header-logo {
345 | width: 100px; }
346 | @media screen and (min-width: 576px) {
347 | .header-logo {
348 | width: 150px; } }
349 |
350 | .header-nav a {
351 | display: inline-block;
352 | margin: 0 15px;
353 | font-family: "Roboto Mono", monospace; }
354 | .header-nav a:nth-last-child(1) {
355 | margin-right: 0; }
356 |
357 | .index-container {
358 | height: calc(100vh - 75px);
359 | display: -webkit-box;
360 | display: -ms-flexbox;
361 | display: flex;
362 | -webkit-box-orient: vertical;
363 | -webkit-box-direction: normal;
364 | -ms-flex-direction: column;
365 | flex-direction: column;
366 | -webkit-box-align: center;
367 | -ms-flex-align: center;
368 | align-items: center;
369 | -webkit-box-pack: center;
370 | -ms-flex-pack: center;
371 | justify-content: center; }
372 | .index-container h1 {
373 | margin-bottom: 0; }
374 |
375 | .body-logo {
376 | width: 90%;
377 | max-width: 480px;
378 | margin-bottom: 30px; }
379 |
380 | .index-nav a {
381 | display: inline-block;
382 | margin: 0 3px; }
383 |
384 | body {
385 | margin: 0;
386 | font-family: "Lato", sans-serif;
387 | color: #333; }
388 |
389 | a {
390 | color: #00b7ff;
391 | text-decoration: none; }
392 |
393 | .container {
394 | max-width: 1170px;
395 | margin: 0 auto; }
396 |
--------------------------------------------------------------------------------
/public/img/cc-voyager-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
91 |
--------------------------------------------------------------------------------
/public/img/cc-voyager.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
138 |
--------------------------------------------------------------------------------
/public/js/global.bundle.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 | /******/
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 | /******/
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId]) {
10 | /******/ return installedModules[moduleId].exports;
11 | /******/ }
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ i: moduleId,
15 | /******/ l: false,
16 | /******/ exports: {}
17 | /******/ };
18 | /******/
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 | /******/
22 | /******/ // Flag the module as loaded
23 | /******/ module.l = true;
24 | /******/
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 | /******/
29 | /******/
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 | /******/
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 | /******/
36 | /******/ // define getter function for harmony exports
37 | /******/ __webpack_require__.d = function(exports, name, getter) {
38 | /******/ if(!__webpack_require__.o(exports, name)) {
39 | /******/ Object.defineProperty(exports, name, {
40 | /******/ configurable: false,
41 | /******/ enumerable: true,
42 | /******/ get: getter
43 | /******/ });
44 | /******/ }
45 | /******/ };
46 | /******/
47 | /******/ // getDefaultExport function for compatibility with non-harmony modules
48 | /******/ __webpack_require__.n = function(module) {
49 | /******/ var getter = module && module.__esModule ?
50 | /******/ function getDefault() { return module['default']; } :
51 | /******/ function getModuleExports() { return module; };
52 | /******/ __webpack_require__.d(getter, 'a', getter);
53 | /******/ return getter;
54 | /******/ };
55 | /******/
56 | /******/ // Object.prototype.hasOwnProperty.call
57 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
58 | /******/
59 | /******/ // __webpack_public_path__
60 | /******/ __webpack_require__.p = "";
61 | /******/
62 | /******/ // Load entry module and return exports
63 | /******/ return __webpack_require__(__webpack_require__.s = 0);
64 | /******/ })
65 | /************************************************************************/
66 | /******/ ([
67 | /* 0 */
68 | /***/ (function(module, exports, __webpack_require__) {
69 |
70 | "use strict";
71 | eval("\n\n__webpack_require__(1);\n\n__webpack_require__(7);\n\n__webpack_require__(8);//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9nbG9iYWwuanM/MWMyMCJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbnJlcXVpcmUoJy4vc2Nzcy9zdHlsZS5zY3NzJyk7XG5cbnJlcXVpcmUoJy4vaW1nL2NjLXZveWFnZXItbG9nby5zdmcnKTtcblxucmVxdWlyZSgnLi9pbWcvY2Mtdm95YWdlci5zdmcnKTtcblxuXG4vLy8vLy8vLy8vLy8vLy8vLy9cbi8vIFdFQlBBQ0sgRk9PVEVSXG4vLyAuL3NyYy9nbG9iYWwuanNcbi8vIG1vZHVsZSBpZCA9IDBcbi8vIG1vZHVsZSBjaHVua3MgPSAwIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///0\n");
72 |
73 | /***/ }),
74 | /* 1 */
75 | /***/ (function(module, exports) {
76 |
77 | eval("// removed by extract-text-webpack-plugin//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9zY3NzL3N0eWxlLnNjc3M/Y2Q3NiJdLCJzb3VyY2VzQ29udGVudCI6WyIvLyByZW1vdmVkIGJ5IGV4dHJhY3QtdGV4dC13ZWJwYWNrLXBsdWdpblxuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vc3JjL3Njc3Mvc3R5bGUuc2Nzc1xuLy8gbW9kdWxlIGlkID0gMVxuLy8gbW9kdWxlIGNodW5rcyA9IDAiXSwibWFwcGluZ3MiOiJBQUFBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///1\n");
78 |
79 | /***/ }),
80 | /* 2 */,
81 | /* 3 */,
82 | /* 4 */,
83 | /* 5 */,
84 | /* 6 */,
85 | /* 7 */
86 | /***/ (function(module, exports, __webpack_require__) {
87 |
88 | eval("module.exports = __webpack_require__.p + \"./img/cc-voyager-logo.svg\";//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiNy5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9pbWcvY2Mtdm95YWdlci1sb2dvLnN2Zz84YTg3Il0sInNvdXJjZXNDb250ZW50IjpbIm1vZHVsZS5leHBvcnRzID0gX193ZWJwYWNrX3B1YmxpY19wYXRoX18gKyBcIi4vaW1nL2NjLXZveWFnZXItbG9nby5zdmdcIjtcblxuXG4vLy8vLy8vLy8vLy8vLy8vLy9cbi8vIFdFQlBBQ0sgRk9PVEVSXG4vLyAuL3NyYy9pbWcvY2Mtdm95YWdlci1sb2dvLnN2Z1xuLy8gbW9kdWxlIGlkID0gN1xuLy8gbW9kdWxlIGNodW5rcyA9IDAiXSwibWFwcGluZ3MiOiJBQUFBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///7\n");
89 |
90 | /***/ }),
91 | /* 8 */
92 | /***/ (function(module, exports, __webpack_require__) {
93 |
94 | eval("module.exports = __webpack_require__.p + \"./img/cc-voyager.svg\";//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiOC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9pbWcvY2Mtdm95YWdlci5zdmc/OTU0YSJdLCJzb3VyY2VzQ29udGVudCI6WyJtb2R1bGUuZXhwb3J0cyA9IF9fd2VicGFja19wdWJsaWNfcGF0aF9fICsgXCIuL2ltZy9jYy12b3lhZ2VyLnN2Z1wiO1xuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vc3JjL2ltZy9jYy12b3lhZ2VyLnN2Z1xuLy8gbW9kdWxlIGlkID0gOFxuLy8gbW9kdWxlIGNodW5rcyA9IDAiXSwibWFwcGluZ3MiOiJBQUFBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///8\n");
95 |
96 | /***/ })
97 | /******/ ]);
--------------------------------------------------------------------------------
/public/js/home.bundle.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 | /******/
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 | /******/
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId]) {
10 | /******/ return installedModules[moduleId].exports;
11 | /******/ }
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ i: moduleId,
15 | /******/ l: false,
16 | /******/ exports: {}
17 | /******/ };
18 | /******/
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 | /******/
22 | /******/ // Flag the module as loaded
23 | /******/ module.l = true;
24 | /******/
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 | /******/
29 | /******/
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 | /******/
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 | /******/
36 | /******/ // define getter function for harmony exports
37 | /******/ __webpack_require__.d = function(exports, name, getter) {
38 | /******/ if(!__webpack_require__.o(exports, name)) {
39 | /******/ Object.defineProperty(exports, name, {
40 | /******/ configurable: false,
41 | /******/ enumerable: true,
42 | /******/ get: getter
43 | /******/ });
44 | /******/ }
45 | /******/ };
46 | /******/
47 | /******/ // getDefaultExport function for compatibility with non-harmony modules
48 | /******/ __webpack_require__.n = function(module) {
49 | /******/ var getter = module && module.__esModule ?
50 | /******/ function getDefault() { return module['default']; } :
51 | /******/ function getModuleExports() { return module; };
52 | /******/ __webpack_require__.d(getter, 'a', getter);
53 | /******/ return getter;
54 | /******/ };
55 | /******/
56 | /******/ // Object.prototype.hasOwnProperty.call
57 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
58 | /******/
59 | /******/ // __webpack_public_path__
60 | /******/ __webpack_require__.p = "";
61 | /******/
62 | /******/ // Load entry module and return exports
63 | /******/ return __webpack_require__(__webpack_require__.s = 2);
64 | /******/ })
65 | /************************************************************************/
66 | /******/ ({
67 |
68 | /***/ 2:
69 | /***/ (function(module, exports, __webpack_require__) {
70 |
71 | "use strict";
72 | eval("//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMi5qcyIsInNvdXJjZXMiOltdLCJtYXBwaW5ncyI6IiIsInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///2\n");
73 |
74 | /***/ })
75 |
76 | /******/ });
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
--------------------------------------------------------------------------------
/routes/web.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 |
4 | /* GET home page. */
5 | router.get('/', async (req, res) => {
6 | res.render('index', { title: 'Voyager' })
7 | })
8 |
9 | module.exports = router
10 |
--------------------------------------------------------------------------------
/src/global.js:
--------------------------------------------------------------------------------
1 | import './scss/style.scss'
2 | import './img/cc-voyager-logo.svg'
3 | import './img/cc-voyager.svg'
4 |
--------------------------------------------------------------------------------
/src/home.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriscourses/voyager/21a1ede8acb366fdc0bd29be4a879359b3c1df0e/src/home.js
--------------------------------------------------------------------------------
/src/img/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriscourses/voyager/21a1ede8acb366fdc0bd29be4a879359b3c1df0e/src/img/.gitkeep
--------------------------------------------------------------------------------
/src/img/cc-voyager-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
91 |
--------------------------------------------------------------------------------
/src/img/cc-voyager.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
138 |
--------------------------------------------------------------------------------
/src/scss/fonts/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriscourses/voyager/21a1ede8acb366fdc0bd29be4a879359b3c1df0e/src/scss/fonts/.gitkeep
--------------------------------------------------------------------------------
/src/scss/fonts/lato.scss:
--------------------------------------------------------------------------------
1 | /* latin-ext */
2 | @font-face {
3 | font-family: 'Lato';
4 | font-style: normal;
5 | font-weight: 300;
6 | src: local('Lato Light'), local('Lato-Light'),
7 | url(https://fonts.gstatic.com/s/lato/v14/S6u9w4BMUTPHh7USSwaPGQ3q5d0N7w.woff2)
8 | format('woff2');
9 | unicode-range: U+0100-024f, U+0259, U+1-1eff, U+2020, U+20a0-20ab, U+20ad-20cf,
10 | U+2113, U+2c60-2c7f, U+A720-A7FF;
11 | }
12 | /* latin */
13 | @font-face {
14 | font-family: 'Lato';
15 | font-style: normal;
16 | font-weight: 300;
17 | src: local('Lato Light'), local('Lato-Light'),
18 | url(https://fonts.gstatic.com/s/lato/v14/S6u9w4BMUTPHh7USSwiPGQ3q5d0.woff2)
19 | format('woff2');
20 | unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02bb-02bc, U+02c6, U+02da,
21 | U+02dc, U+2000-206f, U+2074, U+20ac, U+2122, U+2191, U+2193, U+2212, U+2215,
22 | U+FEFF, U+FFFD;
23 | }
24 |
--------------------------------------------------------------------------------
/src/scss/fonts/roboto-mono.scss:
--------------------------------------------------------------------------------
1 | /* cyrillic-ext */
2 | @font-face {
3 | font-family: 'Roboto Mono';
4 | font-style: normal;
5 | font-weight: 400;
6 | src: local('Roboto Mono'), local('RobotoMono-Regular'),
7 | url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhGq3-cXbKDO1w.woff2)
8 | format('woff2');
9 | unicode-range: U+0460-052f, U+1c80-1c88, U+20b4, U+2de0-2dff, U+A640-A69F,
10 | U+FE2E-FE2F;
11 | }
12 | /* cyrillic */
13 | @font-face {
14 | font-family: 'Roboto Mono';
15 | font-style: normal;
16 | font-weight: 400;
17 | src: local('Roboto Mono'), local('RobotoMono-Regular'),
18 | url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhPq3-cXbKDO1w.woff2)
19 | format('woff2');
20 | unicode-range: U+0400-045f, U+0490-0491, U+04b0-04b1, U+2116;
21 | }
22 | /* greek-ext */
23 | @font-face {
24 | font-family: 'Roboto Mono';
25 | font-style: normal;
26 | font-weight: 400;
27 | src: local('Roboto Mono'), local('RobotoMono-Regular'),
28 | url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhHq3-cXbKDO1w.woff2)
29 | format('woff2');
30 | unicode-range: U+1f00-1fff;
31 | }
32 | /* greek */
33 | @font-face {
34 | font-family: 'Roboto Mono';
35 | font-style: normal;
36 | font-weight: 400;
37 | src: local('Roboto Mono'), local('RobotoMono-Regular'),
38 | url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhIq3-cXbKDO1w.woff2)
39 | format('woff2');
40 | unicode-range: U+0370-03ff;
41 | }
42 | /* vietnamese */
43 | @font-face {
44 | font-family: 'Roboto Mono';
45 | font-style: normal;
46 | font-weight: 400;
47 | src: local('Roboto Mono'), local('RobotoMono-Regular'),
48 | url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhEq3-cXbKDO1w.woff2)
49 | format('woff2');
50 | unicode-range: U+0102-0103, U+0110-0111, U+1ea0-1ef9, U+20ab;
51 | }
52 | /* latin-ext */
53 | @font-face {
54 | font-family: 'Roboto Mono';
55 | font-style: normal;
56 | font-weight: 400;
57 | src: local('Roboto Mono'), local('RobotoMono-Regular'),
58 | url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhFq3-cXbKDO1w.woff2)
59 | format('woff2');
60 | unicode-range: U+0100-024f, U+0259, U+1-1eff, U+2020, U+20a0-20ab, U+20ad-20cf,
61 | U+2113, U+2c60-2c7f, U+A720-A7FF;
62 | }
63 | /* latin */
64 | @font-face {
65 | font-family: 'Roboto Mono';
66 | font-style: normal;
67 | font-weight: 400;
68 | src: local('Roboto Mono'), local('RobotoMono-Regular'),
69 | url(https://fonts.gstatic.com/s/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhLq3-cXbKD.woff2)
70 | format('woff2');
71 | unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02bb-02bc, U+02c6, U+02da,
72 | U+02dc, U+2000-206f, U+2074, U+20ac, U+2122, U+2191, U+2193, U+2212, U+2215,
73 | U+FEFF, U+FFFD;
74 | }
75 | /* cyrillic-ext */
76 | @font-face {
77 | font-family: 'Roboto Mono';
78 | font-style: normal;
79 | font-weight: 700;
80 | src: local('Roboto Mono Bold'), local('RobotoMono-Bold'),
81 | url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq8f7-pAVU_Lrg.woff2)
82 | format('woff2');
83 | unicode-range: U+0460-052f, U+1c80-1c88, U+20b4, U+2de0-2dff, U+A640-A69F,
84 | U+FE2E-FE2F;
85 | }
86 | /* cyrillic */
87 | @font-face {
88 | font-family: 'Roboto Mono';
89 | font-style: normal;
90 | font-weight: 700;
91 | src: local('Roboto Mono Bold'), local('RobotoMono-Bold'),
92 | url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq1f7-pAVU_Lrg.woff2)
93 | format('woff2');
94 | unicode-range: U+0400-045f, U+0490-0491, U+04b0-04b1, U+2116;
95 | }
96 | /* greek-ext */
97 | @font-face {
98 | font-family: 'Roboto Mono';
99 | font-style: normal;
100 | font-weight: 700;
101 | src: local('Roboto Mono Bold'), local('RobotoMono-Bold'),
102 | url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq9f7-pAVU_Lrg.woff2)
103 | format('woff2');
104 | unicode-range: U+1f00-1fff;
105 | }
106 | /* greek */
107 | @font-face {
108 | font-family: 'Roboto Mono';
109 | font-style: normal;
110 | font-weight: 700;
111 | src: local('Roboto Mono Bold'), local('RobotoMono-Bold'),
112 | url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmqyf7-pAVU_Lrg.woff2)
113 | format('woff2');
114 | unicode-range: U+0370-03ff;
115 | }
116 | /* vietnamese */
117 | @font-face {
118 | font-family: 'Roboto Mono';
119 | font-style: normal;
120 | font-weight: 700;
121 | src: local('Roboto Mono Bold'), local('RobotoMono-Bold'),
122 | url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq-f7-pAVU_Lrg.woff2)
123 | format('woff2');
124 | unicode-range: U+0102-0103, U+0110-0111, U+1ea0-1ef9, U+20ab;
125 | }
126 | /* latin-ext */
127 | @font-face {
128 | font-family: 'Roboto Mono';
129 | font-style: normal;
130 | font-weight: 700;
131 | src: local('Roboto Mono Bold'), local('RobotoMono-Bold'),
132 | url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmq_f7-pAVU_Lrg.woff2)
133 | format('woff2');
134 | unicode-range: U+0100-024f, U+0259, U+1-1eff, U+2020, U+20a0-20ab, U+20ad-20cf,
135 | U+2113, U+2c60-2c7f, U+A720-A7FF;
136 | }
137 | /* latin */
138 | @font-face {
139 | font-family: 'Roboto Mono';
140 | font-style: normal;
141 | font-weight: 700;
142 | src: local('Roboto Mono Bold'), local('RobotoMono-Bold'),
143 | url(https://fonts.gstatic.com/s/robotomono/v5/L0xkDF4xlVMF-BfR8bXMIjDwjmqxf7-pAVU_.woff2)
144 | format('woff2');
145 | unicode-range: U+0000-00ff, U+0131, U+0152-0153, U+02bb-02bc, U+02c6, U+02da,
146 | U+02dc, U+2000-206f, U+2074, U+20ac, U+2122, U+2191, U+2193, U+2212, U+2215,
147 | U+FEFF, U+FFFD;
148 | }
149 |
--------------------------------------------------------------------------------
/src/scss/global/_variables.scss:
--------------------------------------------------------------------------------
1 | $light-gray: #c5c5c5;
2 | $gray: #636363;
3 | $red: #ff0000;
4 | $purple: #635afe;
5 |
6 | $lato: 'Lato', sans-serif;
7 | $roboto: 'Roboto Mono', monospace;
8 |
9 | $screen-sm-min: 576px;
10 | $screen-md-min: 768px;
11 | $screen-lg-min: 992px;
12 | $screen-xl-min: 1200px;
13 |
14 | @mixin breakpoint($point) {
15 | @if $point == $screen-sm-min {
16 | @media screen and (min-width: $screen-sm-min) {
17 | @content;
18 | }
19 | }
20 | @elseif $point == $screen-md-min {
21 | @media screen and (min-width: $screen-md-min) {
22 | @content;
23 | }
24 | }
25 | @elseif $point == $screen-lg-min {
26 | @media screen and (min-width: $screen-lg-min) {
27 | @content;
28 | }
29 | }
30 | @elseif $point == $screen-xl-min {
31 | @media screen and (min-width: $screen-xl-min) {
32 | @content;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/scss/global/util.scss:
--------------------------------------------------------------------------------
1 | .m0 {
2 | margin: 0;
3 | }
4 |
5 | .mt0 {
6 | margin-top: 0;
7 | }
8 |
9 | .mb0 {
10 | margin-bottom: 0;
11 | }
12 |
13 | // Mobile Margin Top
14 | .mmt10 {
15 | margin-top: 10px;
16 | @include breakpoint($screen-sm-min) {
17 | margin-top: 0;
18 | }
19 | }
20 |
21 | .mmt25 {
22 | margin-top: 25px;
23 | @include breakpoint($screen-sm-min) {
24 | margin-top: 0;
25 | }
26 | }
27 |
28 | .mmt50 {
29 | margin-top: 50px;
30 | @include breakpoint($screen-sm-min) {
31 | margin-top: 0;
32 | }
33 | }
34 |
35 | .mmt75 {
36 | margin-top: 75px;
37 | @include breakpoint($screen-sm-min) {
38 | margin-top: 0;
39 | }
40 | }
41 |
42 | .mmt100 {
43 | margin-top: 100px;
44 | @include breakpoint($screen-sm-min) {
45 | margin-top: 0;
46 | }
47 | }
48 |
49 | // Mobile Margin Bottom
50 | .mmb10 {
51 | margin-bottom: 10px;
52 | @include breakpoint($screen-sm-min) {
53 | margin-bottom: 0;
54 | }
55 | }
56 |
57 | .mmb25 {
58 | margin-bottom: 25px;
59 | @include breakpoint($screen-sm-min) {
60 | margin-bottom: 0;
61 | }
62 | }
63 |
64 | .mmb50 {
65 | margin-bottom: 50px;
66 | @include breakpoint($screen-sm-min) {
67 | margin-bottom: 0;
68 | }
69 | }
70 |
71 | .mmb75 {
72 | margin-bottom: 75px;
73 | @include breakpoint($screen-sm-min) {
74 | margin-bottom: 0;
75 | }
76 | }
77 |
78 | .mmb100 {
79 | margin-bottom: 100px;
80 | @include breakpoint($screen-sm-min) {
81 | margin-bottom: 0;
82 | }
83 | }
84 |
85 | // Mobile Padding Top
86 | .mpt10 {
87 | padding-top: 10px;
88 | @include breakpoint($screen-sm-min) {
89 | padding-top: 0;
90 | }
91 | }
92 |
93 | .mpt25 {
94 | padding-top: 25px;
95 | @include breakpoint($screen-sm-min) {
96 | padding-top: 0;
97 | }
98 | }
99 |
100 | .mpt50 {
101 | padding-top: 50px;
102 | @include breakpoint($screen-sm-min) {
103 | padding-top: 0;
104 | }
105 | }
106 |
107 | .mpt75 {
108 | padding-top: 75px;
109 | @include breakpoint($screen-sm-min) {
110 | padding-top: 0;
111 | }
112 | }
113 |
114 | .mpt100 {
115 | padding-top: 100px;
116 | @include breakpoint($screen-sm-min) {
117 | padding-top: 0;
118 | }
119 | }
120 |
121 | // Mobile Padding Bottom
122 | .mpb10 {
123 | padding-bottom: 10px;
124 | @include breakpoint($screen-sm-min) {
125 | padding-bottom: 0;
126 | }
127 | }
128 |
129 | .mpb25 {
130 | padding-bottom: 25px;
131 | @include breakpoint($screen-sm-min) {
132 | padding-bottom: 0;
133 | }
134 | }
135 |
136 | .mpb50 {
137 | padding-bottom: 50px;
138 | @include breakpoint($screen-sm-min) {
139 | padding-bottom: 0;
140 | }
141 | }
142 |
143 | .mpb75 {
144 | padding-bottom: 75px;
145 | @include breakpoint($screen-sm-min) {
146 | padding-bottom: 0;
147 | }
148 | }
149 |
150 | .mpb100 {
151 | padding-bottom: 100px;
152 | @include breakpoint($screen-sm-min) {
153 | padding-bottom: 0;
154 | }
155 | }
156 |
157 | @include breakpoint($screen-sm-min) {
158 | // Margin Top
159 | .mt10 {
160 | margin-top: 10px;
161 | }
162 |
163 | .mt25 {
164 | margin-top: 25px;
165 | }
166 |
167 | .mt50 {
168 | margin-top: 50px;
169 | }
170 |
171 | .mt75 {
172 | margin-top: 75px;
173 | }
174 |
175 | .mt100 {
176 | margin-top: 100px;
177 | }
178 |
179 | .mt150 {
180 | margin-top: 150px;
181 | }
182 |
183 | .mt200 {
184 | margin-top: 200px;
185 | }
186 |
187 | // Margin Bottom
188 | .mb10 {
189 | margin-bottom: 10px;
190 | }
191 |
192 | .mb15 {
193 | margin-bottom: 15px;
194 | }
195 |
196 | .mb25 {
197 | margin-bottom: 25px;
198 | }
199 |
200 | .mb50 {
201 | margin-bottom: 50px;
202 | }
203 |
204 | .mb75 {
205 | margin-bottom: 75px;
206 | }
207 |
208 | .mb100 {
209 | margin-bottom: 100px;
210 | }
211 |
212 | // Padding Top
213 | .pt10 {
214 | padding-top: 10px;
215 | }
216 |
217 | .pt25 {
218 | padding-top: 25px;
219 | }
220 |
221 | .pt50 {
222 | padding-top: 50px;
223 | }
224 |
225 | .pt75 {
226 | padding-top: 75px;
227 | }
228 |
229 | .pt100 {
230 | padding-top: 100px;
231 | }
232 |
233 | .pt125 {
234 | padding-top: 125px;
235 | }
236 |
237 | .pt150 {
238 | padding-top: 150px;
239 | }
240 |
241 | .pt175 {
242 | padding-top: 175px;
243 | }
244 |
245 | .pt200 {
246 | padding-top: 200px;
247 | }
248 |
249 | // Padding Bottom
250 | .pb10 {
251 | padding-bottom: 10px;
252 | }
253 |
254 | .pb25 {
255 | padding-bottom: 25px;
256 | }
257 |
258 | .pb50 {
259 | padding-bottom: 50px;
260 | }
261 |
262 | .pb75 {
263 | padding-bottom: 75px;
264 | }
265 |
266 | .pb100 {
267 | padding-bottom: 100px;
268 | }
269 |
270 | .pb150 {
271 | padding-bottom: 150px;
272 | }
273 |
274 | .pb200 {
275 | padding-bottom: 200px;
276 | }
277 | }
278 |
279 | .hide-desktop {
280 | @include breakpoint($screen-sm-min) {
281 | display: none;
282 | }
283 | }
284 |
285 | .hide-mobile {
286 | display: none;
287 | @include breakpoint($screen-sm-min) {
288 | display: block;
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/src/scss/pages/index.scss:
--------------------------------------------------------------------------------
1 | .index-container {
2 | height: calc(100vh - 75px);
3 | display: flex;
4 | flex-direction: column;
5 | align-items: center;
6 | justify-content: center;
7 |
8 | h1 {
9 | margin-bottom: 0;
10 | }
11 | }
12 |
13 | .body-logo {
14 | width: 90%;
15 | max-width: 480px;
16 | margin-bottom: 30px;
17 | }
18 |
19 | .index-nav {
20 | a {
21 | display: inline-block;
22 | margin: 0 5px;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/scss/partials/footer.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriscourses/voyager/21a1ede8acb366fdc0bd29be4a879359b3c1df0e/src/scss/partials/footer.scss
--------------------------------------------------------------------------------
/src/scss/partials/header.scss:
--------------------------------------------------------------------------------
1 | header {
2 | padding: 12px;
3 | border-bottom: 1px solid #eee;
4 | background: white;
5 | }
6 |
7 | .header-container {
8 | display: flex;
9 | justify-content: space-between;
10 | align-items: center;
11 | }
12 |
13 | .header-logo {
14 | width: 100px;
15 | @include breakpoint($screen-sm-min) {
16 | width: 150px;
17 | }
18 | }
19 |
20 | .header-nav {
21 | a {
22 | display: inline-block;
23 | margin: 0 15px;
24 | font-family: $roboto;
25 | &:nth-last-child(1) {
26 | margin-right: 0;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/scss/style.scss:
--------------------------------------------------------------------------------
1 | @import './fonts/lato';
2 | @import './fonts/roboto-mono';
3 |
4 | // Global
5 | @import './global/_variables';
6 | @import './global/util';
7 |
8 | // Partials
9 | @import './partials/header';
10 | @import './partials/footer';
11 |
12 | // Pages
13 | @import './pages/index';
14 |
15 | body {
16 | margin: 0;
17 | font-family: $lato;
18 | color: #333;
19 | }
20 |
21 | a {
22 | color: #00b7ff;
23 | text-decoration: none;
24 | }
25 |
26 | .container {
27 | max-width: 1170px;
28 | margin: 0 auto;
29 | }
30 |
--------------------------------------------------------------------------------
/test/app.test.js:
--------------------------------------------------------------------------------
1 | const request = require('supertest')
2 | const app = require('../app')
3 |
4 | describe('GET /', function() {
5 | it('should render successfully', function(done) {
6 | request(app)
7 | .get('/')
8 | .expect(200, done)
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/views/error.hbs:
--------------------------------------------------------------------------------
1 | {{ message }}
2 | {{ error.status }}
3 | {{ error.stack }}
4 |
--------------------------------------------------------------------------------
/views/index.hbs:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
Resources
5 |
11 |
12 |
20 |
21 |
--------------------------------------------------------------------------------
/views/layouts/main.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ title }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 | {{> header }}
18 | {{{ body }}}
19 | {{> footer }}
20 |
21 |
22 |
--------------------------------------------------------------------------------
/views/partials/footer.hbs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriscourses/voyager/21a1ede8acb366fdc0bd29be4a879359b3c1df0e/views/partials/footer.hbs
--------------------------------------------------------------------------------
/views/partials/header.hbs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
4 | const BrowserSyncPlugin = require('browser-sync-webpack-plugin')
5 |
6 | module.exports = {
7 | entry: {
8 | global: './src/global.js',
9 | auth: './src/auth.js'
10 | },
11 | output: {
12 | path: path.resolve(__dirname, 'public'),
13 | filename: './js/[name].bundle.js'
14 | },
15 | module: {
16 | loaders: [
17 | {
18 | test: /\.scss$/,
19 | use: ExtractTextPlugin.extract({
20 | fallback: 'style-loader',
21 | use: 'css-loader!postcss-loader!sass-loader'
22 | })
23 | },
24 | {
25 | test: /\.js$/,
26 | exclude: /(node_modules|bower_components)/,
27 | use: {
28 | loader: 'babel-loader',
29 | options: {
30 | presets: ['env']
31 | }
32 | }
33 | },
34 | {
35 | test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
36 | use: [
37 | {
38 | loader: 'file-loader',
39 | options: {
40 | name: '[name].[ext]',
41 | outputPath: './css/fonts/',
42 | publicPath: '../'
43 | }
44 | }
45 | ]
46 | },
47 | {
48 | test: /\.(png|jpg|gif|svg)$/,
49 | exclude: /(node_modules|bower_components)/,
50 | use: [
51 | {
52 | loader: 'file-loader',
53 | options: {
54 | name: '[name].[ext]',
55 | outputPath: './img/',
56 | publicPath: '../'
57 | }
58 | }
59 | ]
60 | }
61 | ]
62 | },
63 | plugins: [
64 | // new webpack.HotModuleReplacementPlugin(), // Enable HMR
65 | new ExtractTextPlugin('css/style.css'),
66 | new BrowserSyncPlugin(
67 | {
68 | host: 'localhost',
69 | port: 3001,
70 | proxy: 'http://localhost:3000/',
71 | files: [
72 | {
73 | match: ['**/*.hbs'],
74 | fn: function(event) {
75 | if (event === 'change') {
76 | const bs = require('browser-sync').get(
77 | 'bs-webpack-plugin'
78 | )
79 | bs.reload()
80 | }
81 | }
82 | }
83 | ]
84 | },
85 | {
86 | reload: false
87 | }
88 | )
89 | ],
90 | resolve: {
91 | alias: {
92 | vue: 'vue/dist/vue.js',
93 | '@': path.join(__dirname, 'src')
94 | }
95 | },
96 | watch: true,
97 | devtool: 'cheap-eval-source-map'
98 | }
99 |
100 | if (process.env.NODE_ENV === 'production') {
101 | module.exports.devtool = '#source-map'
102 | module.exports.plugins = (module.exports.plugins || []).concat([
103 | new webpack.DefinePlugin({
104 | 'process.env': {
105 | NODE_ENV: '"production"'
106 | }
107 | }),
108 | new webpack.optimize.UglifyJsPlugin({
109 | sourceMap: true,
110 | compress: {
111 | warnings: false
112 | }
113 | }),
114 | new webpack.LoaderOptionsPlugin({
115 | minimize: true
116 | })
117 | ])
118 | }
119 |
--------------------------------------------------------------------------------