├── .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 | [![Netlify Status](https://api.netlify.com/api/v1/badges/f78ec52d-8328-4e40-b6da-a0f9164e80d1/deploy-status)](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 | [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](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 | 2 | 3 | 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 | --------------------------------------------------------------------------------