├── .eleventy.js
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── netlify.toml
├── package-lock.json
├── package.json
├── src
├── 404.njk
├── assets
│ ├── fonts
│ │ └── .gitkeep
│ ├── icons
│ │ └── github.svg
│ ├── images
│ │ └── favicon
│ │ │ ├── android-chrome-192x192.png
│ │ │ ├── android-chrome-384x384.png
│ │ │ ├── apple-touch-icon.png
│ │ │ ├── favicon-16x16.png
│ │ │ ├── favicon-32x32.png
│ │ │ └── favicon.ico
│ ├── scripts
│ │ ├── __scripts.11ty.js
│ │ ├── main.js
│ │ └── modules
│ │ │ └── nav.js
│ └── styles
│ │ ├── __styles.11ty.js
│ │ ├── base
│ │ ├── _animation.scss
│ │ ├── _focus.scss
│ │ ├── _fonts.scss
│ │ ├── _layout.scss
│ │ ├── _normalize.scss
│ │ ├── _reboot.scss
│ │ ├── _screenreader.scss
│ │ ├── _typography.scss
│ │ └── _utilities.scss
│ │ ├── components
│ │ ├── _footer.scss
│ │ ├── _header.scss
│ │ ├── _icon.scss
│ │ └── _nav.scss
│ │ ├── main.scss
│ │ └── utils
│ │ ├── _functions.scss
│ │ ├── _mixins.scss
│ │ └── _variables.scss
├── build.njk
├── data
│ ├── build.js
│ └── meta.json
├── feed.njk
├── includes
│ ├── footer.njk
│ ├── header.njk
│ ├── meta.njk
│ └── navigation.njk
├── index.njk
├── layouts
│ ├── base.njk
│ └── post.njk
├── pages
│ ├── about.njk
│ └── offline.njk
├── posts
│ ├── 2020-04-12-sample-post.md
│ └── posts.json
├── robots.njk
├── serviceworker.njk
├── site.webmanifest
└── sitemap.njk
└── utils
├── filters.js
├── shortcodes.js
└── transforms.js
/.eleventy.js:
--------------------------------------------------------------------------------
1 | const pluginRss = require('@11ty/eleventy-plugin-rss')
2 | const pluginNavigation = require('@11ty/eleventy-navigation')
3 | const pluginSvgSprite = require("eleventy-plugin-svg-sprite");
4 | const markdownIt = require('markdown-it')
5 |
6 | const filters = require('./utils/filters.js')
7 | const transforms = require('./utils/transforms.js')
8 | const shortcodes = require('./utils/shortcodes.js')
9 |
10 | module.exports = function (config) {
11 | // Plugins
12 | config.addPlugin(pluginRss)
13 | config.addPlugin(pluginNavigation)
14 | config.addPlugin(pluginSvgSprite, {
15 | path: "./src/assets/icons",
16 | svgSpriteShortcode: "iconsprite"
17 | })
18 |
19 | // Filters
20 | Object.keys(filters).forEach((filterName) => {
21 | config.addFilter(filterName, filters[filterName])
22 | })
23 |
24 | // Transforms
25 | Object.keys(transforms).forEach((transformName) => {
26 | config.addTransform(transformName, transforms[transformName])
27 | })
28 |
29 | // Shortcodes
30 | Object.keys(shortcodes).forEach((shortcodeName) => {
31 | config.addShortcode(shortcodeName, shortcodes[shortcodeName])
32 | })
33 |
34 | // Asset Watch Targets
35 | config.addWatchTarget('./src/assets')
36 |
37 | // Markdown
38 | config.setLibrary(
39 | 'md',
40 | markdownIt({
41 | html: true,
42 | breaks: true,
43 | linkify: true,
44 | typographer: true
45 | })
46 | )
47 |
48 | // Layouts
49 | config.addLayoutAlias('base', 'base.njk')
50 | config.addLayoutAlias('post', 'post.njk')
51 |
52 | // Pass-through files
53 | config.addPassthroughCopy('src/robots.txt')
54 | config.addPassthroughCopy('src/site.webmanifest')
55 | config.addPassthroughCopy('src/assets/images')
56 | config.addPassthroughCopy('src/assets/fonts')
57 |
58 | // Deep-Merge
59 | config.setDataDeepMerge(true)
60 |
61 | // Base Config
62 | return {
63 | dir: {
64 | input: 'src',
65 | output: 'dist',
66 | includes: 'includes',
67 | layouts: 'layouts',
68 | data: 'data'
69 | },
70 | templateFormats: ['njk', 'md', '11ty.js'],
71 | htmlTemplateEngine: 'njk',
72 | markdownTemplateEngine: 'njk'
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .env
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "semi": false,
4 | "singleQuote": true,
5 | "trailingComma": "none"
6 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2020 Max Böck
2 |
3 | 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:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | 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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Eleventastic
2 |
3 | A simple Eleventy Starter Kit, my base for all new 11ty projects. ([Demo Site](https://eleventastic.netlify.com))
4 |
5 | [](https://app.netlify.com/sites/eleventastic/deploys)
6 |
7 | ## Features
8 |
9 | * CSS Pipeline (Sass, CleanCSS)
10 | * JS Bundling (Webpack)
11 | * SVG Icon Sprite Generation
12 | * Critical CSS
13 | * HTML Minification
14 | * No external builds, everything runs through 11ty
15 |
16 | ## Getting Started
17 |
18 | To install the necessary packages, run this command in the root folder of the site:
19 |
20 | ```sh
21 | npm install
22 | ```
23 |
24 | ### Commands
25 |
26 | * Run `npm start` for a development server and live reloading
27 | * Run `npm run build` to generate a production build
28 |
29 | ## Deploy a fork of this template to Netlify
30 |
31 | [](https://app.netlify.com/start/deploy?repository=https://github.com/maxboeck/eleventastic)
32 |
33 | ## CSS
34 |
35 | Styling works with Sass. The main index file is in `src/assets/styles/main.scss`. Import any SCSS code you want in there; it will be processed and optimized. The output is in `dist/assets/styles/main.css`
36 |
37 | ## JS
38 |
39 | Javascript can be written in ES6 syntax. The main index file is in `src/assets/scripts/main.js`. It will be transpiled to ES5 with babel, bundled together with webpack, and minified in production. The output is in `dist/assets/scripts/main.js`
40 |
41 | ## SVG Icons
42 |
43 | All SVG files added to `src/assets/icons` will be bundled into a `symbol` sprite file. The SVG filename will then be used as the symbol identifier and the icon can be used as a shortcode.
44 |
45 | For example, if you have a `github.svg` file in that folder, you can display it anywhere by using `{% icon "github" %}` in your templates.
46 |
47 | ## Critical CSS
48 |
49 | Currently, critical CSS will only be inlined in the head of the homepage. This is done by using the [critical](https://github.com/addyosmani/critical) package in an automatic transform.
50 |
51 | ## Credits
52 |
53 | My heartfelt thanks to these people, whom I shamelessly copied ideas from:
54 |
55 | * Phil Hawksworth: [EleventyOne](https://github.com/philhawksworth/eleventyone)
56 | * Mike Riethmuller: [Supermaya](https://github.com/MadeByMike/supermaya)
57 | * Zach Leatherman: [zachleat.com](https://github.com/zachleat/zachleat.com)
58 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | command = "npm run build"
3 | publish = "dist"
4 |
5 | [context.production.environment]
6 | ELEVENTY_ENV = "production"
7 |
8 | [[headers]]
9 | for = "/*"
10 | [headers.values]
11 | X-Frame-Options = "DENY"
12 | X-XSS-Protection = "1; mode=block"
13 | X-Content-Type-Options = "nosniff"
14 | Referrer-Policy= "no-referrer-when-downgrade"
15 | Permissions-Policy = "interest-cohort=()"
16 |
17 | # [[redirects]]
18 | # from = "https://eleventastic.netlify.com/*"
19 | # to = "https://eleventastic.dev/:splat"
20 | # status = 301
21 | # force = true
22 |
23 | [[redirects]]
24 | from = "*"
25 | to = "/404"
26 | status = 404
27 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eleventastic",
3 | "version": "1.0.0",
4 | "description": "A boilerplate for eleventy websites",
5 | "browserslist": [
6 | "defaults"
7 | ],
8 | "scripts": {
9 | "start": "npm run dev",
10 | "dev": "run-s clean eleventy:dev --print-label",
11 | "build": "run-s clean eleventy:prod --print-label",
12 | "eleventy:dev": "cross-env ELEVENTY_ENV=development eleventy --serve",
13 | "eleventy:prod": "cross-env ELEVENTY_ENV=production eleventy",
14 | "clean": "del-cli dist",
15 | "test": "echo \"Error: no test specified\" && exit 1"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "http://github.com/maxboeck/eleventastic"
20 | },
21 | "author": "Max Böck",
22 | "license": "MIT",
23 | "dependencies": {
24 | "@11ty/eleventy": "^0.12.1",
25 | "@11ty/eleventy-img": "^0.8.3",
26 | "@11ty/eleventy-navigation": "^0.1.6",
27 | "@11ty/eleventy-plugin-rss": "^1.1.1",
28 | "@babel/core": "^7.13.14",
29 | "@babel/plugin-transform-runtime": "^7.13.10",
30 | "@babel/preset-env": "^7.13.12",
31 | "babel-loader": "^8.2.2",
32 | "clean-css": "^5.1.2",
33 | "critical": "^3.0.0",
34 | "cssesc": "^3.0.0",
35 | "del-cli": "^3.0.1",
36 | "eleventy-plugin-svg-sprite": "^1.2.1",
37 | "focus-trap": "^6.3.0",
38 | "focus-visible": "^5.2.0",
39 | "html-minifier": "^4.0.0",
40 | "luxon": "^1.26.0",
41 | "markdown-it": "^12.0.4",
42 | "memfs": "^3.2.0",
43 | "node-sass": "^5.0.0",
44 | "npm-run-all": "^4.1.5",
45 | "svg-sprite": "^1.5.0",
46 | "webpack": "^5.28.0"
47 | },
48 | "devDependencies": {
49 | "cross-env": "^7.0.3"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/404.njk:
--------------------------------------------------------------------------------
1 | ---
2 | layout: base
3 | eleventyExcludeFromCollections: true
4 | ---
5 |
6 |
7 |
404
8 |
Page not found.
9 |
--------------------------------------------------------------------------------
/src/assets/fonts/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxboeck/eleventastic/37e469dbb6688255417f99daf74496ec51e4c4ba/src/assets/fonts/.gitkeep
--------------------------------------------------------------------------------
/src/assets/icons/github.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxboeck/eleventastic/37e469dbb6688255417f99daf74496ec51e4c4ba/src/assets/images/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/src/assets/images/favicon/android-chrome-384x384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxboeck/eleventastic/37e469dbb6688255417f99daf74496ec51e4c4ba/src/assets/images/favicon/android-chrome-384x384.png
--------------------------------------------------------------------------------
/src/assets/images/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxboeck/eleventastic/37e469dbb6688255417f99daf74496ec51e4c4ba/src/assets/images/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/src/assets/images/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxboeck/eleventastic/37e469dbb6688255417f99daf74496ec51e4c4ba/src/assets/images/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/src/assets/images/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxboeck/eleventastic/37e469dbb6688255417f99daf74496ec51e4c4ba/src/assets/images/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/src/assets/images/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxboeck/eleventastic/37e469dbb6688255417f99daf74496ec51e4c4ba/src/assets/images/favicon/favicon.ico
--------------------------------------------------------------------------------
/src/assets/scripts/__scripts.11ty.js:
--------------------------------------------------------------------------------
1 | // This file handles the JS build.
2 | // It will run webpack with babel over all JS defined in the main entry file.
3 |
4 | // main entry point name
5 | const ENTRY_FILE_NAME = 'main.js'
6 |
7 | const fs = require('fs')
8 | const path = require('path')
9 | const webpack = require('webpack')
10 | const { fs: mfs } = require('memfs')
11 |
12 | const isProd = process.env.ELEVENTY_ENV === 'production'
13 |
14 | module.exports = class {
15 | // Configure Webpack in Here
16 | async data() {
17 | const entryPath = path.join(__dirname, `/${ENTRY_FILE_NAME}`)
18 | const outputPath = path.resolve(__dirname, '../../memory-fs/js/')
19 |
20 | // Transform .js files, run through Babel
21 | const rules = [
22 | {
23 | test: /\.m?js$/,
24 | exclude: /(node_modules|bower_components)/,
25 | use: {
26 | loader: 'babel-loader',
27 | options: {
28 | presets: ['@babel/preset-env'],
29 | plugins: ['@babel/plugin-transform-runtime']
30 | }
31 | }
32 | }
33 | ]
34 |
35 | // pass environment down to scripts
36 | const envPlugin = new webpack.EnvironmentPlugin({
37 | ELEVENTY_ENV: process.env.ELEVENTY_ENV
38 | })
39 |
40 | // Main Config
41 | const webpackConfig = {
42 | mode: isProd ? 'production' : 'development',
43 | entry: entryPath,
44 | output: { path: outputPath },
45 | module: { rules },
46 | plugins: [envPlugin]
47 | }
48 |
49 | return {
50 | permalink: `/assets/scripts/${ENTRY_FILE_NAME}`,
51 | eleventyExcludeFromCollections: true,
52 | webpackConfig
53 | }
54 | }
55 |
56 | // Compile JS with Webpack, write the result to Memory Filesystem.
57 | // this brilliant idea is taken from Mike Riethmuller / Supermaya
58 | // @see https://github.com/MadeByMike/supermaya/blob/master/site/utils/compile-webpack.js
59 | compile(webpackConfig) {
60 | const compiler = webpack(webpackConfig)
61 | compiler.outputFileSystem = mfs
62 | compiler.inputFileSystem = fs
63 | compiler.intermediateFileSystem = mfs
64 |
65 | return new Promise((resolve, reject) => {
66 | compiler.run((err, stats) => {
67 | if (err || stats.hasErrors()) {
68 | const errors =
69 | err ||
70 | (stats.compilation ? stats.compilation.errors : null)
71 |
72 | reject(errors)
73 | return
74 | }
75 |
76 | mfs.readFile(
77 | webpackConfig.output.path + '/' + ENTRY_FILE_NAME,
78 | 'utf8',
79 | (err, data) => {
80 | if (err) reject(err)
81 | else resolve(data)
82 | }
83 | )
84 | })
85 | })
86 | }
87 |
88 | // render the JS file
89 | async render({ webpackConfig }) {
90 | try {
91 | const result = await this.compile(webpackConfig)
92 | return result
93 | } catch (err) {
94 | console.log(err)
95 | return null
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/assets/scripts/main.js:
--------------------------------------------------------------------------------
1 | // Focus Visible Polyfill
2 | import 'focus-visible'
3 |
4 | // Internal Modules
5 | import './modules/nav'
6 |
--------------------------------------------------------------------------------
/src/assets/scripts/modules/nav.js:
--------------------------------------------------------------------------------
1 | import { createFocusTrap } from 'focus-trap'
2 |
3 | const SELECTORS = {
4 | nav: '.js-nav',
5 | toggleBtn: '.js-nav-toggle'
6 | }
7 |
8 | const CLASSES = {
9 | open: 'is-open'
10 | }
11 |
12 | class Navigation {
13 | constructor() {
14 | this.isOpen = false
15 |
16 | this.nav = document.querySelector(SELECTORS.nav)
17 | this.toggleBtn = this.nav.querySelector(SELECTORS.toggleBtn)
18 | this.focusTrap = createFocusTrap(this.nav)
19 |
20 | this.toggleBtn.addEventListener('click', () => this.toggleMenu())
21 | }
22 |
23 | toggleMenu(force) {
24 | this.isOpen = typeof force === 'boolean' ? force : !this.isOpen
25 |
26 | this.nav.classList.toggle(CLASSES.open, this.isOpen)
27 | this.toggleBtn.setAttribute('aria-expanded', String(this.isOpen))
28 |
29 | if (this.isOpen) {
30 | this.focusTrap.activate()
31 | } else {
32 | this.focusTrap.deactivate()
33 | }
34 | }
35 | }
36 |
37 | if (document.querySelector(SELECTORS.nav)) {
38 | new Navigation()
39 | }
40 |
--------------------------------------------------------------------------------
/src/assets/styles/__styles.11ty.js:
--------------------------------------------------------------------------------
1 | // This file handles the CSS build.
2 | // It will run Sass and compile all styles defined in the main entry file.
3 |
4 | // main entry point name
5 | const ENTRY_FILE_NAME = 'main.scss'
6 |
7 | const path = require('path')
8 | const sass = require('node-sass')
9 | const CleanCSS = require('clean-css')
10 | const cssesc = require('cssesc')
11 | const isProd = process.env.ELEVENTY_ENV === 'production'
12 |
13 | module.exports = class {
14 | async data() {
15 | const entryPath = path.join(__dirname, `/${ENTRY_FILE_NAME}`)
16 | return {
17 | permalink: `/assets/styles/main.css`,
18 | eleventyExcludeFromCollections: true,
19 | entryPath
20 | }
21 | }
22 |
23 | // Compile Sass to CSS,
24 | // Embed Source Map in Development
25 | async compile(config) {
26 | return new Promise((resolve, reject) => {
27 | if (!isProd) {
28 | config.sourceMap = true
29 | config.sourceMapEmbed = true
30 | config.outputStyle = 'expanded'
31 | }
32 | return sass.render(config, (err, result) => {
33 | if (err) {
34 | return reject(err)
35 | }
36 | resolve(result.css.toString())
37 | })
38 | })
39 | }
40 |
41 | // Minify & Optimize with CleanCSS in Production
42 | async minify(css) {
43 | return new Promise((resolve, reject) => {
44 | if (!isProd) {
45 | resolve(css)
46 | }
47 | const minified = new CleanCSS().minify(css)
48 | if (!minified.styles) {
49 | return reject(minified.error)
50 | }
51 | resolve(minified.styles)
52 | })
53 | }
54 |
55 | // display an error overlay when CSS build fails.
56 | // this brilliant idea is taken from Mike Riethmuller / Supermaya
57 | // @see https://github.com/MadeByMike/supermaya/blob/master/site/utils/compile-scss.js
58 | renderError(error) {
59 | return `
60 | /* Error compiling stylesheet */
61 | *,
62 | *::before,
63 | *::after {
64 | box-sizing: border-box;
65 | }
66 | html,
67 | body {
68 | margin: 0;
69 | padding: 0;
70 | min-height: 100vh;
71 | font-family: monospace;
72 | font-size: 1.25rem;
73 | line-height:1.5;
74 | }
75 | body::before {
76 | content: '';
77 | background: #000;
78 | top: 0;
79 | bottom: 0;
80 | width: 100%;
81 | height: 100%;
82 | opacity: 0.7;
83 | position: fixed;
84 | }
85 | body::after {
86 | content: '${cssesc(error)}';
87 | white-space: pre;
88 | display: block;
89 | top: 0;
90 | padding: 30px;
91 | margin: 50px;
92 | width: calc(100% - 100px);
93 | color:#721c24;
94 | background: #f8d7da;
95 | border: solid 2px red;
96 | position: fixed;
97 | }`
98 | }
99 |
100 | // render the CSS file
101 | async render({ entryPath }) {
102 | try {
103 | const css = await this.compile({ file: entryPath })
104 | const result = await this.minify(css)
105 | return result
106 | } catch (err) {
107 | // if things go wrong
108 | if (isProd) {
109 | // throw and abort in production
110 | throw new Error(err)
111 | } else {
112 | // otherwise display the error overlay
113 | console.error(err)
114 | const msg = err.formatted || err.message
115 | return this.renderError(msg)
116 | }
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/assets/styles/base/_animation.scss:
--------------------------------------------------------------------------------
1 | @media (prefers-reduced-motion: reduce) {
2 | * {
3 | animation-duration: 0.01s !important;
4 | transition-duration: 0.01s !important;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/assets/styles/base/_focus.scss:
--------------------------------------------------------------------------------
1 | // =================
2 | // DEFAULT FOCUS STYLES
3 | // =================
4 |
5 | a:focus,
6 | button:focus,
7 | input:focus {
8 | outline: 5px solid $brand-color-primary;
9 | }
10 |
11 | // =================
12 | // FOCUS-VISIBLE POLYFILL
13 | // =================
14 |
15 | .js-focus-visible :focus:not(.focus-visible) {
16 | outline: none;
17 | }
18 |
19 | .js-focus-visible .focus-visible {
20 | }
21 |
22 | // =================
23 | // EXCEPTIONS
24 | // =================
25 |
26 | // sections excluded from the tabindex
27 | [tabindex='-1']:focus {
28 | outline: none !important;
29 | }
30 |
31 | // the skip link is only visible on focus
32 | .sr-skip-link:focus {
33 | outline: none;
34 | }
35 |
36 | // links that are both focused AND hovered
37 | a:focus:hover {
38 | outline: none;
39 | }
40 |
--------------------------------------------------------------------------------
/src/assets/styles/base/_fonts.scss:
--------------------------------------------------------------------------------
1 | // @font-face {
2 | // font-family: 'Noe Display';
3 | // font-style: normal;
4 | // font-display: swap;
5 | // font-weight: 400;
6 | // src: local('Noe Display'), local('NoeDisplay-Regular'),
7 | // url('/assets/fonts/noe-display-regular.woff2') format('woff2'),
8 | // url('/assets/fonts/noe-display-regular.woff') format('woff');
9 | // }
10 |
--------------------------------------------------------------------------------
/src/assets/styles/base/_layout.scss:
--------------------------------------------------------------------------------
1 | // Main Site Layout
2 |
3 | body {
4 | overflow-x: hidden;
5 | }
6 |
7 | .layout {
8 | display: flex;
9 | flex-direction: column;
10 | min-height: 100%;
11 | min-height: 100vh;
12 | position: relative;
13 |
14 | .header,
15 | .footer {
16 | flex: none;
17 | }
18 |
19 | .main {
20 | display: flex;
21 | flex-direction: column;
22 | flex: 1 0 auto;
23 |
24 | &::after {
25 | content: '';
26 | display: block;
27 | height: 0px;
28 | visibility: hidden;
29 | }
30 | }
31 | }
32 |
33 | .container {
34 | width: 90%;
35 | margin-left: auto;
36 | margin-right: auto;
37 | max-width: $container-max-width;
38 | }
39 |
--------------------------------------------------------------------------------
/src/assets/styles/base/_normalize.scss:
--------------------------------------------------------------------------------
1 | /* normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
2 |
3 | //
4 | // 1. Set default font family to sans-serif.
5 | // 2. Prevent iOS and IE text size adjust after device orientation change,
6 | // without disabling user zoom.
7 | //
8 |
9 | html {
10 | font-family: sans-serif; // 1
11 | -ms-text-size-adjust: 100%; // 2
12 | -webkit-text-size-adjust: 100%; // 2
13 | }
14 |
15 | //
16 | // Remove default margin.
17 | //
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | // HTML5 display definitions
24 | // ==========================================================================
25 |
26 | //
27 | // Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | // Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | // and Firefox.
30 | // Correct `block` display not defined for `main` in IE 11.
31 | //
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | //
50 | // 1. Correct `inline-block` display not defined in IE 8/9.
51 | // 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | //
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; // 1
59 | vertical-align: baseline; // 2
60 | }
61 |
62 | //
63 | // Prevent modern browsers from displaying `audio` without controls.
64 | // Remove excess height in iOS 5 devices.
65 | //
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | //
73 | // Address `[hidden]` styling not present in IE 8/9/10.
74 | // Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
75 | //
76 |
77 | [hidden],
78 | template {
79 | display: none !important;
80 | }
81 |
82 | // Links
83 | // ==========================================================================
84 |
85 | //
86 | // Remove the gray background color from active links in IE 10.
87 | //
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | // Text-level semantics
94 | // ==========================================================================
95 |
96 | //
97 | // Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
98 | //
99 |
100 | b,
101 | strong {
102 | font-weight: bold;
103 | }
104 |
105 | //
106 | // Address styling not present in Safari and Chrome.
107 | //
108 |
109 | dfn {
110 | font-style: italic;
111 | }
112 |
113 | //
114 | // Address styling not present in IE 8/9.
115 | //
116 |
117 | mark {
118 | background: #ff0;
119 | color: #000;
120 | }
121 |
122 | //
123 | // Address inconsistent and variable font size in all browsers.
124 | //
125 |
126 | small {
127 | font-size: 80%;
128 | }
129 |
130 | //
131 | // Prevent `sub` and `sup` affecting `line-height` in all browsers.
132 | //
133 |
134 | sub,
135 | sup {
136 | font-size: 75%;
137 | line-height: 0;
138 | position: relative;
139 | vertical-align: baseline;
140 | }
141 |
142 | sup {
143 | top: -0.5em;
144 | }
145 |
146 | sub {
147 | bottom: -0.25em;
148 | }
149 |
150 | // Embedded content
151 | // ==========================================================================
152 |
153 | //
154 | // Remove border when inside `a` element in IE 8/9/10.
155 | //
156 |
157 | img {
158 | border: 0;
159 | }
160 |
161 | //
162 | // Correct overflow not hidden in IE 9/10/11.
163 | //
164 |
165 | svg:not(:root) {
166 | overflow: hidden;
167 | }
168 |
169 | // Grouping content
170 | // ==========================================================================
171 |
172 | //
173 | // Address margin not present in IE 8/9 and Safari.
174 | //
175 |
176 | figure {
177 | margin: 0;
178 | }
179 |
180 | //
181 | // Address differences between Firefox and other browsers.
182 | //
183 |
184 | hr {
185 | box-sizing: content-box;
186 | height: 0;
187 | }
188 |
189 | //
190 | // Contain overflow in all browsers.
191 | //
192 |
193 | pre {
194 | overflow: auto;
195 | }
196 |
197 | //
198 | // Address odd `em`-unit font size rendering in all browsers.
199 | //
200 |
201 | code,
202 | kbd,
203 | pre,
204 | samp {
205 | font-family: monospace, monospace;
206 | font-size: 1em;
207 | }
208 |
209 | // Forms
210 | // ==========================================================================
211 |
212 | //
213 | // Known limitation: by default, Chrome and Safari on OS X allow very limited
214 | // styling of `select`, unless a `border` property is set.
215 | //
216 |
217 | //
218 | // 1. Correct color not being inherited.
219 | // Known issue: affects color of disabled elements.
220 | // 2. Correct font properties not being inherited.
221 | // 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
222 | //
223 |
224 | button,
225 | input,
226 | optgroup,
227 | select,
228 | textarea {
229 | color: inherit; // 1
230 | font: inherit; // 2
231 | margin: 0; // 3
232 | }
233 |
234 | //
235 | // Address `overflow` set to `hidden` in IE 8/9/10/11.
236 | //
237 |
238 | button {
239 | overflow: visible;
240 | }
241 |
242 | //
243 | // Address inconsistent `text-transform` inheritance for `button` and `select`.
244 | // All other form control elements do not inherit `text-transform` values.
245 | // Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
246 | // Correct `select` style inheritance in Firefox.
247 | //
248 |
249 | button,
250 | select {
251 | text-transform: none;
252 | }
253 |
254 | //
255 | // 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
256 | // and `video` controls.
257 | // 2. Correct inability to style clickable `input` types in iOS.
258 | // 3. Improve usability and consistency of cursor style between image-type
259 | // `input` and others.
260 | //
261 |
262 | button,
263 | html input[type="button"],
264 | // 1
265 | input[type="reset"],
266 | input[type="submit"] {
267 | -webkit-appearance: button; // 2
268 | cursor: pointer; // 3
269 | }
270 |
271 | //
272 | // Re-set default cursor for disabled elements.
273 | //
274 |
275 | button[disabled],
276 | html input[disabled] {
277 | cursor: default;
278 | }
279 |
280 | //
281 | // Remove inner padding and border in Firefox 4+.
282 | //
283 |
284 | button::-moz-focus-inner,
285 | input::-moz-focus-inner {
286 | border: 0;
287 | padding: 0;
288 | }
289 |
290 | //
291 | // Address Firefox 4+ setting `line-height` on `input` using `!important` in
292 | // the UA stylesheet.
293 | //
294 |
295 | input {
296 | line-height: normal;
297 | }
298 |
299 | //
300 | // It's recommended that you don't attempt to style these elements.
301 | // Firefox's implementation doesn't respect box-sizing, padding, or width.
302 | //
303 | // 1. Address box sizing set to `content-box` in IE 8/9/10.
304 | // 2. Remove excess padding in IE 8/9/10.
305 | //
306 |
307 | input[type='checkbox'],
308 | input[type='radio'] {
309 | box-sizing: border-box; // 1
310 | padding: 0; // 2
311 | }
312 |
313 | //
314 | // Fix the cursor style for Chrome's increment/decrement buttons. For certain
315 | // `font-size` values of the `input`, it causes the cursor style of the
316 | // decrement button to change from `default` to `text`.
317 | //
318 |
319 | input[type='number']::-webkit-inner-spin-button,
320 | input[type='number']::-webkit-outer-spin-button {
321 | height: auto;
322 | }
323 |
324 | //
325 | // 1. Address `appearance` set to `searchfield` in Safari and Chrome.
326 | // 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
327 | //
328 |
329 | input[type='search'] {
330 | -webkit-appearance: textfield; // 1
331 | box-sizing: content-box; //2
332 | }
333 |
334 | //
335 | // Remove inner padding and search cancel button in Safari and Chrome on OS X.
336 | // Safari (but not Chrome) clips the cancel button when the search input has
337 | // padding (and `textfield` appearance).
338 | //
339 |
340 | input[type='search']::-webkit-search-cancel-button,
341 | input[type='search']::-webkit-search-decoration {
342 | -webkit-appearance: none;
343 | }
344 |
345 | //
346 | // Remove default vertical scrollbar in IE 8/9/10/11.
347 | //
348 |
349 | textarea {
350 | overflow: auto;
351 | }
352 |
353 | //
354 | // Don't inherit the `font-weight` (applied by a rule above).
355 | // NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
356 | //
357 |
358 | optgroup {
359 | font-weight: bold;
360 | }
361 |
362 | // Tables
363 | // ==========================================================================
364 |
365 | //
366 | // Remove most spacing between table cells.
367 | //
368 |
369 | table {
370 | border-collapse: collapse;
371 | border-spacing: 0;
372 | }
373 |
374 | td,
375 | th {
376 | padding: 0;
377 | }
378 |
--------------------------------------------------------------------------------
/src/assets/styles/base/_reboot.scss:
--------------------------------------------------------------------------------
1 | // Reboot
2 | //
3 | // Global resets to common HTML elements and more for easier usage by Bootstrap.
4 | // Adds additional rules on top of Normalize.css, including several overrides.
5 |
6 | // Reset the box-sizing
7 | //
8 | // Change from `box-sizing: content-box` to `border-box` so that when you add
9 | // `padding` or `border`s to an element, the overall declared `width` does not
10 | // change. For example, `width: 100px;` will always be `100px` despite the
11 | // `border: 10px solid red;` and `padding: 20px;`.
12 | //
13 | // Heads up! This reset may cause conflicts with some third-party widgets. For
14 | // recommendations on resolving such conflicts, see
15 | // http://getbootstrap.com/getting-started/#third-box-sizing.
16 | //
17 | // Credit: https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/
18 |
19 | html {
20 | box-sizing: border-box;
21 | overflow-y: scroll;
22 | }
23 |
24 | *,
25 | *::before,
26 | *::after {
27 | box-sizing: inherit;
28 | }
29 |
30 | // Make viewport responsive
31 | //
32 | // @viewport is needed because IE 10+ doesn't honor in
33 | // some cases. See http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/.
34 | // Eventually @viewport will replace . It's been manually
35 | // prefixed for forward-compatibility.
36 | //
37 | // However, `device-width` is broken on IE 10 on Windows (Phone) 8,
38 | // (see http://timkadlec.com/2013/01/windows-phone-8-and-device-width/ and https://github.com/twbs/bootstrap/issues/10497)
39 | // and the fix for that involves a snippet of JavaScript to sniff the user agent
40 | // and apply some conditional CSS.
41 | //
42 | // See http://getbootstrap.com/getting-started/#support-ie10-width for the relevant hack.
43 | //
44 | // Wrap `@viewport` with `@at-root` for when folks do a nested import (e.g.,
45 | // `.class-name { @import "bootstrap"; }`).
46 | //
47 | // Includes future-proofed vendor prefixes as well.
48 | @at-root {
49 | @-moz-viewport {
50 | width: device-width;
51 | }
52 | @-ms-viewport {
53 | width: device-width;
54 | }
55 | @-o-viewport {
56 | width: device-width;
57 | }
58 | @-webkit-viewport {
59 | width: device-width;
60 | }
61 | @viewport {
62 | width: device-width;
63 | }
64 | }
65 |
66 | //
67 | // Reset HTML, body, and more
68 | //
69 |
70 | html {
71 | // Sets a specific default `font-size` for user with `rem` type scales.
72 | font-size: $font-size-root;
73 | // Changes the default tap highlight to be completely transparent in iOS.
74 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
75 | }
76 |
77 | body {
78 | // Make the `body` use the `font-size-root`
79 | font-family: $font-family-base;
80 | font-size: $font-size-base;
81 | line-height: $line-height;
82 |
83 | color: $text-color;
84 | background-color: $bg-color;
85 |
86 | -ms-text-size-adjust: 100%;
87 | -webkit-text-size-adjust: 100%;
88 | }
89 |
90 | //
91 | // Typography
92 | //
93 |
94 | // Remove top margins from headings
95 | //
96 | // By default, ``-`` all receive top and bottom margins. We nuke the top
97 | // margin for easier control within type scales as it avoids margin collapsing.
98 |
99 | // Abbreviations and acronyms
100 | abbr[title],
101 | // Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257
102 | abbr[data-original-title] {
103 | cursor: help;
104 | }
105 |
106 | ol,
107 | ul,
108 | dl {
109 | padding: 0;
110 | margin: 0;
111 | list-style-type: none;
112 | }
113 |
114 | //
115 | // Code
116 | //
117 |
118 | pre {
119 | margin: 0;
120 | }
121 |
122 | //
123 | // Images
124 | //
125 |
126 | img {
127 | // By default, `
`s are `inline-block`. This assumes that, and vertically
128 | // centers them. This won't apply should you reset them to `block` level.
129 | vertical-align: middle;
130 | // Note: `
`s are deliberately not made responsive by default.
131 | // For the rationale behind this, see the comments on the `.img-fluid` class.
132 | }
133 |
134 | // iOS "clickable elements" fix for role="button"
135 | //
136 | // Fixes "clickability" issue (and more generally, the firing of events such as focus as well)
137 | // for traditionally non-focusable elements with role="button"
138 | // see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile
139 |
140 | [role='button'] {
141 | cursor: pointer;
142 | }
143 |
144 | // Avoid 300ms click delay on touch devices that support the `touch-action` CSS property.
145 | //
146 | // In particular, unlike most other browsers, IE11+Edge on Windows 10 on touch devices and IE Mobile 10-11
147 | // DON'T remove the click delay when `` is present.
148 | // However, they DO support removing the click delay via `touch-action: manipulation`.
149 | // See:
150 | // * http://v4-alpha.getbootstrap.com/content/reboot/#click-delay-optimization-for-touch
151 | // * http://caniuse.com/#feat=css-touch-action
152 | // * http://patrickhlauke.github.io/touch/tests/results/#suppressing-300ms-delay
153 |
154 | a,
155 | area,
156 | button,
157 | [role='button'],
158 | input,
159 | label,
160 | select,
161 | summary,
162 | textarea {
163 | touch-action: manipulation;
164 | }
165 |
166 | //
167 | // Tables
168 | //
169 |
170 | th {
171 | // Centered by default, but left-align-ed to match the `td`s below.
172 | text-align: left;
173 | }
174 |
175 | //
176 | // Forms
177 | //
178 |
179 | label {
180 | // Allow labels to use `margin` for spacing.
181 | display: inline-block;
182 | margin: 0;
183 | }
184 |
185 | input,
186 | button,
187 | select,
188 | textarea {
189 | // Remove all `margin`s so our classes don't have to do it themselves.
190 | margin: 0;
191 | // Normalize includes `font: inherit;`, so `font-family`. `font-size`, etc are
192 | // properly inherited. However, `line-height` isn't addressed there. Using this
193 | // ensures we don't need to unnecessarily redeclare the global font stack.
194 | line-height: inherit;
195 | // iOS adds rounded borders by default
196 | border-radius: 0;
197 | }
198 |
199 | textarea {
200 | // Textareas should really only resize vertically so they don't break their (horizontal) containers.
201 | resize: vertical;
202 | }
203 |
204 | fieldset {
205 | // Chrome and Firefox set a `min-width: min-content;` on fieldsets,
206 | // so we reset that to ensure it behaves more like a standard block element.
207 | // See https://github.com/twbs/bootstrap/issues/12359.
208 | min-width: 0;
209 | padding: 0;
210 | margin: 0;
211 | border: 0;
212 | }
213 |
214 | legend {
215 | // Reset the entire legend element to match the `fieldset`
216 | display: block;
217 | width: 100%;
218 | padding: 0;
219 | margin-bottom: 0.5rem;
220 | font-size: 1.5rem;
221 | line-height: inherit;
222 | border: 0;
223 | }
224 |
225 | input[type='search'] {
226 | // Undo Normalize's default here to match our global overrides.
227 | box-sizing: inherit;
228 | // This overrides the extra rounded corners on search inputs in iOS so that our
229 | // `.form-control` class can properly style them. Note that this cannot simply
230 | // be added to `.form-control` as it's not specific enough. For details, see
231 | // https://github.com/twbs/bootstrap/issues/11586.
232 | -webkit-appearance: none;
233 | }
234 |
--------------------------------------------------------------------------------
/src/assets/styles/base/_screenreader.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Screenreaders
3 | //
4 |
5 | .sr-only {
6 | @include sr-only();
7 | }
8 |
9 | .sr-only-focusable {
10 | @include sr-only-focusable();
11 | }
12 |
13 | .sr-skip-link {
14 | @include sr-only();
15 | @include sr-only-focusable();
16 | font-family: $font-family-sans-serif;
17 |
18 | &:focus {
19 | position: absolute;
20 | z-index: 9999;
21 | left: 50%;
22 | top: 0;
23 | font-size: 1rem;
24 | transform: translateX(-50%);
25 | background-color: $gray-darkest;
26 | color: #fff;
27 | border-radius: 0 0 0.5rem 0.5rem;
28 | padding: 1rem 1.5rem;
29 | outline: 0;
30 | white-space: nowrap;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/assets/styles/base/_typography.scss:
--------------------------------------------------------------------------------
1 | h1,
2 | h2,
3 | h3,
4 | h4,
5 | h5,
6 | h6 {
7 | margin: 0;
8 | line-height: 1.3;
9 | }
10 |
11 | h1 {
12 | font-size: 2.5rem;
13 | font-weight: 400;
14 | text-rendering: optimizeLegibility;
15 | }
16 |
17 | h2 {
18 | font-size: 2.125rem;
19 | font-weight: 700;
20 | }
21 |
22 | p {
23 | margin: 0;
24 | }
25 |
26 | .lead {
27 | font-size: 1.5rem;
28 | font-weight: 300;
29 | line-height: 1.4;
30 |
31 | @include mq(md) {
32 | font-size: 2rem;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/assets/styles/base/_utilities.scss:
--------------------------------------------------------------------------------
1 | // UTILITY CLASSES
2 | // All of these have !important because they should always overrule any other style
3 |
4 | // Layout Utils
5 |
6 | .u-mt0 {
7 | margin-top: 0 !important;
8 | }
9 | .u-mt1 {
10 | margin-top: 1rem !important;
11 | }
12 | .u-mt2 {
13 | margin-top: 2rem !important;
14 | }
15 | .u-mt3 {
16 | margin-top: 3rem !important;
17 | }
18 | .u-mt4 {
19 | margin-top: 4rem !important;
20 | }
21 |
22 | .u-mb0 {
23 | margin-bottom: 0 !important;
24 | }
25 | .u-mb1 {
26 | margin-bottom: 1rem !important;
27 | }
28 | .u-mb2 {
29 | margin-bottom: 2rem !important;
30 | }
31 | .u-mb3 {
32 | margin-bottom: 3rem !important;
33 | }
34 | .u-mb4 {
35 | margin-bottom: 4rem !important;
36 | }
37 |
38 | // Text Utilities
39 |
40 | .u-align-left {
41 | text-align: left !important;
42 | }
43 | .u-align-center {
44 | text-align: center !important;
45 | }
46 | .u-align-right {
47 | text-align: right !important;
48 | }
49 |
50 | // Display Utils
51 |
52 | .u-mobile-only {
53 | @include mq(md) {
54 | display: none !important;
55 | }
56 | }
57 | .u-tablet-only {
58 | @include mq(lg) {
59 | display: none !important;
60 | }
61 | }
62 | .u-desktop-only {
63 | @include mq-down(lg) {
64 | display: none !important;
65 | }
66 | }
67 | .u-print-only {
68 | @media not print {
69 | display: none !important;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/assets/styles/components/_footer.scss:
--------------------------------------------------------------------------------
1 | .footer {
2 | font-size: 0.875rem;
3 | padding: $spacing-y 0;
4 | background-color: $gray-lightest;
5 |
6 | &__inner {
7 | display: flex;
8 | justify-content: space-between;
9 | align-items: center;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/assets/styles/components/_header.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | padding: $spacing-y 0;
3 |
4 | &__inner {
5 | display: flex;
6 | justify-content: space-between;
7 | align-items: center;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/assets/styles/components/_icon.scss:
--------------------------------------------------------------------------------
1 | .icon {
2 | display: inline-block;
3 | font-size: 1.5em;
4 | height: 1em;
5 | width: 1em;
6 | vertical-align: middle;
7 | fill: currentColor;
8 | pointer-events: none;
9 | }
10 |
11 | // ==========================
12 | // Animated Pure CSS Menu Burger Icon
13 | // ==========================
14 |
15 | .menuicon {
16 | display: block;
17 | width: 18px;
18 | height: 16px;
19 | position: relative;
20 | transform: rotate(0deg);
21 | transition: transform 0.3s $animation-curve-default;
22 | cursor: pointer;
23 | margin: 0 auto;
24 |
25 | &__bar {
26 | display: block;
27 | position: absolute;
28 | left: 0;
29 | right: 0;
30 | height: 2px;
31 | width: 100%;
32 | background-color: currentColor;
33 | transform: rotate(0deg);
34 | transition: transform 0.25s ease-in-out;
35 |
36 | &:nth-child(1) {
37 | top: 0px;
38 | }
39 | &:nth-child(2),
40 | &:nth-child(3) {
41 | top: 50%;
42 | }
43 | &:nth-child(4) {
44 | top: 100%;
45 | }
46 | }
47 |
48 | // Active State: Transform to "X"
49 | .is-open & {
50 | top: 2px;
51 | transform: rotate(-180deg);
52 |
53 | .menuicon__bar {
54 | &:nth-child(1),
55 | &:nth-child(4) {
56 | width: 0;
57 | top: 50%;
58 | left: 50%;
59 | }
60 | &:nth-child(2) {
61 | transform: rotate(45deg);
62 | }
63 | &:nth-child(3) {
64 | transform: rotate(-45deg);
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/assets/styles/components/_nav.scss:
--------------------------------------------------------------------------------
1 | .nav {
2 | &__menu {
3 | display: none;
4 | position: absolute;
5 | left: 0;
6 | right: 0;
7 | background-color: #fff;
8 | }
9 | &__link {
10 | display: block;
11 | padding: 0.5em 1em;
12 | }
13 | &__toggle {
14 | @include button-reset;
15 | }
16 |
17 | @include mq-down(lg) {
18 | &.is-open &__menu {
19 | display: block;
20 | }
21 | }
22 |
23 | @include mq(lg) {
24 | &__toggle {
25 | display: none;
26 | }
27 | &__menu {
28 | display: flex;
29 | position: static;
30 | background-color: transparent;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/assets/styles/main.scss:
--------------------------------------------------------------------------------
1 | // ---------------------------------
2 | // MASTER STYLESHEET INDEX
3 | // ---------------------------------
4 |
5 | //--------------------
6 | // UTILITIES
7 | //--------------------
8 |
9 | @import 'utils/functions';
10 | @import 'utils/variables';
11 | @import 'utils/mixins';
12 |
13 | //--------------------
14 | // BASE MODULES
15 | // partials with global scope
16 | //--------------------
17 |
18 | @import 'base/fonts';
19 | @import 'base/normalize';
20 | @import 'base/reboot';
21 | @import 'base/layout';
22 | @import 'base/typography';
23 | @import 'base/focus';
24 | @import 'base/utilities';
25 | @import 'base/screenreader';
26 | @import 'base/animation';
27 |
28 | //--------------------
29 | // COMPONENTS
30 | // partials with local scope to a component
31 | //--------------------
32 |
33 | @import 'components/header';
34 | @import 'components/nav';
35 | @import 'components/footer';
36 | @import 'components/icon';
37 |
--------------------------------------------------------------------------------
/src/assets/styles/utils/_functions.scss:
--------------------------------------------------------------------------------
1 | //--------------------
2 | // SCSS CUSTOM FUNCTIONS
3 | //--------------------
4 |
5 | @function breakpoint-min($name, $breakpoints: $grid-breakpoints) {
6 | $min: map-get($breakpoints, $name);
7 | @return if($min != 0, $min, null);
8 | }
9 |
10 | // z-index layering
11 | @function z($layer) {
12 | @if map-has-key($z-layers, $layer) == false {
13 | @warn "No layer found for `#{$layer}` in $z-layers map. Property omitted.";
14 | }
15 |
16 | @return map-get($z-layers, $layer);
17 | }
18 |
--------------------------------------------------------------------------------
/src/assets/styles/utils/_mixins.scss:
--------------------------------------------------------------------------------
1 | //--------------------
2 | // SCSS MIXINS
3 | //--------------------
4 |
5 | @mixin mq($name) {
6 | $min: breakpoint-min($name, $breakpoints);
7 | @if $min {
8 | @media (min-width: $min) {
9 | @content;
10 | }
11 | } @else {
12 | @content;
13 | }
14 | }
15 |
16 | @mixin mq-down($name) {
17 | $min: breakpoint-min($name, $breakpoints);
18 | @if $min {
19 | @media (max-width: ($min - 1px)) {
20 | @content;
21 | }
22 | } @else {
23 | @content;
24 | }
25 | }
26 |
27 | @mixin hover-focus {
28 | &:hover,
29 | &:focus {
30 | @content;
31 | }
32 | }
33 |
34 | @mixin sr-only {
35 | position: absolute;
36 | width: 1px;
37 | height: 1px;
38 | padding: 0;
39 | overflow: hidden;
40 | clip: rect(0, 0, 0, 0);
41 | white-space: nowrap;
42 | border: 0;
43 | }
44 |
45 | @mixin sr-only-focusable {
46 | &:active,
47 | &:focus {
48 | position: static;
49 | width: auto;
50 | height: auto;
51 | overflow: visible;
52 | clip: auto;
53 | white-space: normal;
54 | }
55 | }
56 |
57 | @mixin hyphenate() {
58 | word-wrap: break-word;
59 | overflow-wrap: break-word;
60 | hyphens: auto;
61 | }
62 |
63 | @mixin coverall() {
64 | position: absolute;
65 | top: 0;
66 | left: 0;
67 | bottom: 0;
68 | right: 0;
69 | }
70 |
71 | @mixin scrollable() {
72 | overflow-y: auto;
73 | -webkit-overflow-scrolling: touch;
74 | }
75 |
76 | @mixin button-reset() {
77 | border: 0;
78 | padding: 0;
79 | background-color: transparent;
80 | -webkit-appearance: none;
81 | }
82 |
--------------------------------------------------------------------------------
/src/assets/styles/utils/_variables.scss:
--------------------------------------------------------------------------------
1 | //--------------------
2 | // SCSS VARIABLES
3 | //--------------------
4 |
5 | // Grid breakpoints
6 | //
7 | // Define the minimum and maximum dimensions at which your layout will change,
8 | // adapting to different screen sizes, for use in media queries.
9 |
10 | $breakpoints: (
11 | md: 670px,
12 | lg: 940px,
13 | xl: 1260px
14 | );
15 |
16 | $container-max-width: 1200px;
17 |
18 | // Z-Index Stack
19 | //
20 | // Control the order of layers in the application
21 | $z-layers: (
22 | 'modal': 999,
23 | 'nav': 100,
24 | 'content-overlay': 30
25 | );
26 |
27 | // Colors
28 | //
29 | // Grayscale.
30 |
31 | $gray-darkest: #373a3c;
32 | $gray-dark: #55595c;
33 | $gray: #818a91;
34 | $gray-light: #eceeef;
35 | $gray-lightest: #f7f7f9;
36 |
37 | // Brand Colors
38 |
39 | $brand-red: #fc6767;
40 |
41 | // Color Mappings
42 |
43 | $bg-color: #fff;
44 | $text-color: $gray-darkest;
45 | $brand-color-primary: $brand-red;
46 | $brand-color-primary-offset: darken($brand-red, 6%);
47 |
48 | // Typography
49 | //
50 | // Font, line-height, and color for body text, headings, and more.
51 |
52 | $font-family-sans-serif: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
53 | 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
54 | sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
55 | $font-family-serif: 'Times New Roman', Georgia, Times, serif;
56 | $font-family-monospace: Menlo, Monaco, Consolas, 'Andale Mono', 'Courier New',
57 | monospace;
58 | $font-family-base: $font-family-sans-serif;
59 |
60 | $font-size-root: 100%;
61 | $font-size-base: 1rem;
62 | $line-height: 1.625;
63 |
64 | // Animation
65 | //
66 | // Default easing curves, Transitions, etc
67 |
68 | $animation-curve-fast-out-slow-in: cubic-bezier(0.4, 0, 0.2, 1);
69 | $animation-curve-linear-out-slow-in: cubic-bezier(0, 0, 0.2, 1);
70 | $animation-curve-fast-out-linear-in: cubic-bezier(0.4, 0, 1, 1);
71 | $animation-curve-default: $animation-curve-fast-out-slow-in;
72 |
73 | // Misc Shared
74 | //
75 | // common shared styles
76 |
77 | $spacing-x: 2rem;
78 | $spacing-y: 2rem;
79 |
80 | $box-shadow: 0 2px 16px rgba(0, 0, 0, 0.15);
81 | $box-shadow-elevated: 0 6px 12px 0 rgba(0, 0, 0, 0.1);
82 |
83 | $border-radius: 0.25rem;
84 |
--------------------------------------------------------------------------------
/src/build.njk:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: build.txt
3 | eleventyExcludeFromCollections: true
4 | ---
5 | LAST BUILD: {{ build.timestamp | dateToISO }}
6 | ENV: {{ build.env}}
7 |
--------------------------------------------------------------------------------
/src/data/build.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: process.env.ELEVENTY_ENV,
3 | timestamp: new Date()
4 | }
5 |
--------------------------------------------------------------------------------
/src/data/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Eleventastic",
3 | "description": "An Eleventy Starter Kit",
4 | "url": "https://eleventastic.netlify.app",
5 | "lang": "en",
6 | "locale": "en_us"
7 | }
--------------------------------------------------------------------------------
/src/feed.njk:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: feed.xml
3 | eleventyExcludeFromCollections: true
4 | ---
5 |
6 |
7 | {{ meta.title }}
8 | {{ meta.description }}
9 |
10 |
11 |
12 | {{ meta.title }}
13 |
14 | {% if collections.posts %}
15 | {{ collections.posts | rssLastUpdatedDate }}
16 | {% endif %}
17 | {{ meta.url }}/
18 | {%- for post in collections.posts | reverse -%}
19 | {% set absolutePostUrl %}{{ post.url | url | absoluteUrl(meta.url) }}{% endset %}
20 |
21 | {{ post.data.title }}
22 |
23 | {{ post.date | rssDate }}
24 | {{ absolutePostUrl }}
25 |
28 |
29 | {%- endfor -%}
30 |
--------------------------------------------------------------------------------
/src/includes/footer.njk:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/includes/header.njk:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/includes/meta.njk:
--------------------------------------------------------------------------------
1 | {%- set absolutePageUrl -%}{{ page.url | url | absoluteUrl(meta.url) }}{%- endset -%}
2 |
3 | {# General #}
4 |
5 |
6 |
7 | {# Open Graph #}
8 |
9 |
10 |
11 |
12 |
13 |
14 | {%- if image -%}
15 |
16 | {%- endif -%}
17 |
18 | {# Favicon #}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | {# RSS Feed #}
27 |
--------------------------------------------------------------------------------
/src/includes/navigation.njk:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/index.njk:
--------------------------------------------------------------------------------
1 | ---
2 | layout: base
3 | eleventyNavigation:
4 | key: Home
5 | ---
6 |
7 |
8 |
A blank 11ty project.
9 |
--------------------------------------------------------------------------------
/src/layouts/base.njk:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ meta.title }}
7 |
8 |
9 | {% include "meta.njk" %}
10 |
11 |
12 | {% iconsprite %}
13 | skip to main content
14 |
15 |
16 | {% include "header.njk" %}
17 |
18 | {{ content | safe }}
19 |
20 | {% include "footer.njk" %}
21 |
22 |
23 |
24 |
29 |
30 |
--------------------------------------------------------------------------------
/src/layouts/post.njk:
--------------------------------------------------------------------------------
1 | ---
2 | layout: base
3 | ---
4 |
5 | {{ title }}
6 | {{ content | safe }}
7 |
--------------------------------------------------------------------------------
/src/pages/about.njk:
--------------------------------------------------------------------------------
1 | ---
2 | layout: base
3 | permalink: /about/
4 | eleventyNavigation:
5 | key: About
6 | ---
7 |
8 |
9 |
About
10 |
--------------------------------------------------------------------------------
/src/pages/offline.njk:
--------------------------------------------------------------------------------
1 | ---
2 | layout: base
3 | permalink: /offline/
4 | eleventyExcludeFromCollections: true
5 | ---
6 |
7 |
8 |
Offline
9 |
You are currently offline.
10 |
--------------------------------------------------------------------------------
/src/posts/2020-04-12-sample-post.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: An example post.
3 | ---
4 |
5 | Lorem Ipsum Dolor.
--------------------------------------------------------------------------------
/src/posts/posts.json:
--------------------------------------------------------------------------------
1 | {
2 | "tags": "posts",
3 | "layout": "post"
4 | }
--------------------------------------------------------------------------------
/src/robots.njk:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: robots.txt
3 | eleventyExcludeFromCollections: true
4 | ---
5 | # www.robotstxt.org
6 |
7 | User-agent: *
8 | Disallow: /build.txt
9 |
10 | Sitemap: {{ meta.url }}/sitemap.xml
--------------------------------------------------------------------------------
/src/serviceworker.njk:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: /serviceworker.js
3 | eleventyExcludeFromCollections: true
4 | ---
5 |
6 | const CACHE_NAME = 'cache-{{ build.timestamp|dateToISO }}'
7 | const OFFLINE_PAGE_URL = '/offline/'
8 | const PRECACHE_RESOURCES = []
9 | const IGNORED_HOSTS = ['localhost']
10 |
11 | // On Install
12 | self.addEventListener('install', (event) => {
13 | event.waitUntil(
14 | (async () => {
15 | const cache = await caches.open(CACHE_NAME)
16 |
17 | // add all resources we want to precache first
18 | if (PRECACHE_RESOURCES.length) {
19 | cache.addAll(PRECACHE_RESOURCES)
20 | }
21 |
22 | // then add the offline page.
23 | // Setting {cache: 'reload'} in the new request will ensure that the
24 | // response isn't fulfilled from the HTTP cache; i.e., it will be from
25 | // the network.
26 | await cache.add(new Request(OFFLINE_PAGE_URL, { cache: 'reload' }))
27 | })()
28 | )
29 | // Force the waiting service worker to become the active service worker.
30 | self.skipWaiting()
31 | })
32 |
33 | // On Activation
34 | self.addEventListener('activate', (event) => {
35 | event.waitUntil(
36 | (async () => {
37 | // Enable navigation preload if it's supported.
38 | // See https://developers.google.com/web/updates/2017/02/navigation-preload
39 | if ('navigationPreload' in self.registration) {
40 | await self.registration.navigationPreload.enable()
41 | }
42 |
43 | // clear out any old caches that might still be around
44 | const cacheNames = await caches.keys()
45 | await Promise.all(
46 | cacheNames.map((cacheName) => {
47 | if (cacheName !== CACHE_NAME) {
48 | return caches.delete(cacheName)
49 | }
50 | })
51 | )
52 | })()
53 | )
54 |
55 | // Tell the active service worker to take control of the page immediately.
56 | self.clients.claim()
57 | })
58 |
59 | // On Request
60 | self.addEventListener('fetch', (event) => {
61 | const { hostname } = new URL(event.request.url)
62 | if (IGNORED_HOSTS.indexOf(hostname) >= 0) {
63 | return
64 | }
65 |
66 | // For navigation requests (GET to a new location)
67 | if (event.request.mode === 'navigate') {
68 | event.respondWith(
69 | (async () => {
70 | try {
71 | // First, try to use the navigation preload response if it's supported.
72 | const preloadResponse = await event.preloadResponse
73 | if (preloadResponse) {
74 | return preloadResponse
75 | }
76 | // Then try the network.
77 | const networkResponse = await fetch(event.request)
78 | return networkResponse
79 | } catch (error) {
80 | // catch is only triggered if an exception is thrown, which is likely
81 | // due to a network error.
82 | // If fetch() returns a valid HTTP response with a response code in
83 | // the 4xx or 5xx range, the catch() will NOT be called.
84 | console.log(
85 | 'Fetch failed; returning offline page instead.',
86 | error
87 | )
88 |
89 | const cache = await caches.open(CACHE_NAME)
90 | const cachedResponse = await cache.match(OFFLINE_PAGE_URL)
91 | return cachedResponse
92 | }
93 | })()
94 | )
95 | }
96 | })
97 |
--------------------------------------------------------------------------------
/src/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "lang": "en",
3 | "dir": "ltr",
4 | "name": "Eleventastic",
5 | "short_name": "11tstc",
6 | "icons": [
7 | {
8 | "src": "/assets/images/favicon/android-chrome-192x192.png",
9 | "sizes": "192x192",
10 | "type": "image/png"
11 | },
12 | {
13 | "src": "/assets/images/favicon/android-chrome-384x384.png",
14 | "sizes": "384x384",
15 | "type": "image/png"
16 | }
17 | ],
18 | "theme_color": "#1a1a1a",
19 | "background_color": "#1a1a1a",
20 | "start_url": "/index.html",
21 | "display": "standalone",
22 | "orientation": "natural"
23 | }
24 |
--------------------------------------------------------------------------------
/src/sitemap.njk:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: /sitemap.xml
3 | eleventyExcludeFromCollections: true
4 | ---
5 |
6 |
7 | {%- for item in collections.all -%}
8 | {%- if not item.data.excludeFromSitemap and item.url -%}
9 |
10 | {{ item.url | url | absoluteUrl(meta.url) }}
11 | {{ item.date | dateToFormat('yyyy-MM-dd') }}
12 |
13 | {%- endif -%}
14 | {%- endfor -%}
15 |
--------------------------------------------------------------------------------
/utils/filters.js:
--------------------------------------------------------------------------------
1 | const { DateTime } = require('luxon')
2 |
3 | module.exports = {
4 | dateToFormat: function (date, format) {
5 | return DateTime.fromJSDate(date, { zone: 'utc' }).toFormat(
6 | String(format)
7 | )
8 | },
9 |
10 | dateToISO: function (date) {
11 | return DateTime.fromJSDate(date, { zone: 'utc' }).toISO({
12 | includeOffset: false,
13 | suppressMilliseconds: true
14 | })
15 | },
16 |
17 | obfuscate: function (str) {
18 | const chars = []
19 | for (var i = str.length - 1; i >= 0; i--) {
20 | chars.unshift(['', str[i].charCodeAt(), ';'].join(''))
21 | }
22 | return chars.join('')
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/utils/shortcodes.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | icon: function (name) {
3 | return ``
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/utils/transforms.js:
--------------------------------------------------------------------------------
1 | const htmlmin = require('html-minifier')
2 | const critical = require('critical')
3 | const buildDir = 'dist'
4 |
5 | const shouldTransformHTML = (outputPath) =>
6 | outputPath &&
7 | outputPath.endsWith('.html') &&
8 | process.env.ELEVENTY_ENV === 'production'
9 |
10 | const isHomePage = (outputPath) => outputPath === `${buildDir}/index.html`
11 |
12 | process.setMaxListeners(Infinity)
13 | module.exports = {
14 | htmlmin: function (content, outputPath) {
15 | if (shouldTransformHTML(outputPath)) {
16 | return htmlmin.minify(content, {
17 | useShortDoctype: true,
18 | removeComments: true,
19 | collapseWhitespace: true
20 | })
21 | }
22 | return content
23 | },
24 |
25 | critical: async function (content, outputPath) {
26 | if (shouldTransformHTML(outputPath) && isHomePage(outputPath)) {
27 | try {
28 | const config = {
29 | base: `${buildDir}/`,
30 | html: content,
31 | inline: true,
32 | width: 1280,
33 | height: 800
34 | }
35 | const { html } = await critical.generate(config)
36 | return html
37 | } catch (err) {
38 | console.error(err)
39 | }
40 | }
41 | return content
42 | }
43 | }
44 |
--------------------------------------------------------------------------------