├── .eleventy.js ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── netlify.toml ├── package-lock.json ├── package.json ├── src ├── assets │ ├── fonts │ │ ├── Butler_ButlerStencil_FontLicense_v1_0.pdf │ │ ├── butler-medium.woff │ │ └── butler-medium.woff2 │ ├── icons │ │ ├── behance.svg │ │ ├── dribbble.svg │ │ ├── email.svg │ │ ├── fork.svg │ │ ├── github.svg │ │ ├── laptop.svg │ │ ├── linkedin.svg │ │ ├── medium.svg │ │ ├── print.svg │ │ ├── reddit.svg │ │ ├── skype.svg │ │ ├── slack.svg │ │ ├── star.svg │ │ ├── telephone.svg │ │ ├── twitter.svg │ │ └── whatsapp.svg │ ├── images │ │ ├── avatar.jpg │ │ ├── demo │ │ │ └── resume.png │ │ └── favicon │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-512x512.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon.ico │ │ │ └── safari-pinned-tab.svg │ ├── scripts │ │ ├── main.js │ │ └── scripts.11ty.js │ └── styles │ │ ├── base │ │ ├── _animation.scss │ │ ├── _focus.scss │ │ ├── _fonts.scss │ │ ├── _layout.scss │ │ ├── _normalize.scss │ │ ├── _reboot.scss │ │ ├── _screenreader.scss │ │ ├── _typography.scss │ │ └── _utilities.scss │ │ ├── components │ │ ├── _actions.scss │ │ ├── _bulletlist.scss │ │ ├── _entry.scss │ │ ├── _entrylist.scss │ │ ├── _gh-link.scss │ │ ├── _icon.scss │ │ ├── _markdown.scss │ │ ├── _repolist.scss │ │ ├── _section.scss │ │ ├── _tooltip.scss │ │ └── _vcard.scss │ │ ├── main.scss │ │ ├── print │ │ └── _index.scss │ │ ├── styles.11ty.js │ │ └── utils │ │ ├── _functions.scss │ │ ├── _mixins.scss │ │ └── _variables.scss ├── data │ ├── author.json │ ├── build.js │ ├── meta.json │ ├── repositories.js │ └── strings.json ├── entries │ ├── content │ │ ├── custom.md │ │ └── introduction.md │ ├── education │ │ ├── 2012-a-levels.md │ │ ├── 2014-digital-design.md │ │ └── 2016-master-webdevelopment.md │ ├── entries.json │ └── work │ │ ├── 2019-another-position.md │ │ ├── 2019-lorem-ipsum.md │ │ └── 2020-sample-position.md ├── includes │ ├── entry.njk │ ├── entrylist.njk │ ├── footer.njk │ ├── header.njk │ ├── meta.njk │ ├── repositories.njk │ ├── sidebar.njk │ └── vcard.njk ├── index.njk ├── layouts │ ├── base.njk │ └── resume.njk ├── robots.txt └── site.webmanifest.njk └── utils ├── filters.js ├── iconsprite.js ├── shortcodes.js └── transforms.js /.eleventy.js: -------------------------------------------------------------------------------- 1 | const pluginRss = require('@11ty/eleventy-plugin-rss') 2 | const markdownIt = require('markdown-it') 3 | 4 | const filters = require('./utils/filters.js') 5 | const transforms = require('./utils/transforms.js') 6 | const shortcodes = require('./utils/shortcodes.js') 7 | const iconsprite = require('./utils/iconsprite.js') 8 | 9 | module.exports = function (config) { 10 | // Plugins 11 | config.addPlugin(pluginRss) 12 | 13 | // Filters 14 | Object.keys(filters).forEach((filterName) => { 15 | config.addFilter(filterName, filters[filterName]) 16 | }) 17 | 18 | // Transforms 19 | Object.keys(transforms).forEach((transformName) => { 20 | config.addTransform(transformName, transforms[transformName]) 21 | }) 22 | 23 | // Shortcodes 24 | Object.keys(shortcodes).forEach((shortcodeName) => { 25 | config.addShortcode(shortcodeName, shortcodes[shortcodeName]) 26 | }) 27 | 28 | // Icon Sprite 29 | config.addNunjucksAsyncShortcode('iconsprite', iconsprite) 30 | 31 | // Asset Watch Targets 32 | config.addWatchTarget('./src/assets') 33 | 34 | // Markdown 35 | config.setLibrary( 36 | 'md', 37 | markdownIt({ 38 | html: true, 39 | breaks: true, 40 | linkify: true, 41 | typographer: true 42 | }) 43 | ) 44 | 45 | // Layouts 46 | config.addLayoutAlias('base', 'base.njk') 47 | config.addLayoutAlias('resume', 'resume.njk') 48 | 49 | // Collections 50 | const collections = ['work', 'education'] 51 | collections.forEach((name) => { 52 | config.addCollection(name, function (collection) { 53 | const folderRegex = new RegExp(`\/${name}\/`) 54 | const inEntryFolder = (item) => 55 | item.inputPath.match(folderRegex) !== null 56 | 57 | const byStartDate = (a, b) => { 58 | if (a.data.start && b.data.start) { 59 | return a.data.start - b.data.start 60 | } 61 | return 0 62 | } 63 | 64 | return collection 65 | .getAllSorted() 66 | .filter(inEntryFolder) 67 | .sort(byStartDate) 68 | }) 69 | }) 70 | 71 | // Pass-through files 72 | config.addPassthroughCopy('src/robots.txt') 73 | config.addPassthroughCopy('src/assets/images') 74 | config.addPassthroughCopy('src/assets/fonts') 75 | 76 | // Deep-Merge 77 | config.setDataDeepMerge(true) 78 | 79 | // Base Config 80 | return { 81 | dir: { 82 | input: 'src', 83 | output: 'dist', 84 | includes: 'includes', 85 | layouts: 'layouts', 86 | data: 'data' 87 | }, 88 | templateFormats: ['njk', 'md', '11ty.js'], 89 | htmlTemplateEngine: 'njk', 90 | markdownTemplateEngine: 'njk' 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env 4 | .cache -------------------------------------------------------------------------------- /.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 | # Resume 2 | 3 | An online résumé. [Demo Site](https://demo-resume.netlify.app) 4 | 5 | ![a preview of the generated résumé as a website and in print](src/assets/images/demo/resume.png) 6 | 7 | ## Features 8 | 9 | * Fully Customizable 10 | * Semantic HTML 11 | * Accessible (WCAG AA) 12 | * [h-resume](http://microformats.org/wiki/h-resume) Microformat 13 | * Self-Contained (no external resources) 14 | * Search Engine Optimized (meta, JSON-LD, etc...) 15 | * Critical CSS Inlined 16 | * Print Styles 17 | 18 | ## Getting Started 19 | 20 | To install the necessary packages, run this command in the root folder of the site: 21 | 22 | ``` 23 | npm install 24 | ``` 25 | 26 | __Commands__ 27 | 28 | * Run `npm start` for a development server and live reloading 29 | * Run `npm run build` to generate a production build 30 | 31 | Deploy a fork of this template to Netlify: 32 | 33 | [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/maxboeck/resume) 34 | 35 | ## Customize your Résumé 36 | 37 | To edit the content and design of your résumé, follow these steps: 38 | 39 | ### 1. Personal Details 40 | 41 | Open `src/data/author.json` and edit the information describing yourself. The following properties are supported (optional properties can be removed from the JSON file): 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 |
KeyDescriptionRequired
nameyour full namerequired
occupationyour job descriptionrequired
locationyour town/stateoptional
avatarthe file name of your profile photo. Must be located in src/assets/images/optional
pronounsyour preferred gender pronounsoptional
emailyour email addressoptional
telephoneyour phone numberoptional
websiteyour personal websiteoptional
skillsan array of strings describing your skillsetoptional
languagesan array of objects describing your spoken languages; each object should include a name (e.g. "English") and level (e.g. "fluent") propertyoptional
socialan array of objects for each social profile you want to link; each object should include a name (e.g. "Github"), user (e.g. "@maxboeck") and url (e.g. "https://github.com/maxboeck") propertyoptional
109 | 110 | ### 2. Introduction 111 | 112 | Open `entries/content/introduction.md` and edit the text content of the file with your personal short introduction summary. Limit it to 2-3 sentences and convey your motivation. You can edit the title of the section here as well. 113 | 114 | ### 3. Work Experience & Education 115 | 116 | The entries for the sections "work experience" and "education" are stored as markdown files in `src/entries/work` and `src/entries/education`. 117 | 118 | Delete the demo files in there and create your own. The text should describe your responsibilities, learnings or achievements. Include the following [frontmatter](https://www.11ty.dev/docs/data-frontmatter/) data: 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 |
KeyDescriptionRequired
titlethe title of the entry. in "work experience", this sould be your role/position, in "education" this should be the degree/certification earned.required
startISO timestamp of when you started this job or education.required
endISO timestamp of when you ended this job or education. If not defined, that entry will say "- Present"optional
organizationname of your employer (when "work") or school (when "education")optional
organizationUrllink to website of your employer (when "work") or school (when "education")optional
locationlocation of company or schooloptional
161 | 162 | ### 4. Github Repositories 163 | 164 | If you want, you can include the five most starred repositories from your Github account. This will fetch the current data at build time, at most once a day. To do this, edit `src/data/repositories.js` and change the `YOUR_GITHUB_USERNAME` var to your Github username. 165 | 166 | ### 5. Custom Content 167 | 168 | Edit `entries/content/custom.md` if you want to edit freeform content to the end of the CV. This could be a legal disclaimer or an additional section. Delete the file if you don't want this section to show up. 169 | 170 | ### 6. Meta Data & Design 171 | 172 | Open `src/data/meta.json` and replace the `url` with the URL of your hosted résumé. You can also customize the language and color scheme here. 173 | 174 | Supported properties are: 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 |
KeyDescriptionRequired
urlthe URL of your hosted résumé, e.g. "https://my-resume.com". (no trailing slash)required
langthe 2-digit language identifier of your résumé, e.g. "en", "de", etc.required
localethe locale code of your résumé, e.g. "en_US", "de_DE", etc.required
colors.primaryThe HEX code of the primary brand color. defaults to blue #005b96optional
colors.secondaryThe HEX code of the secondary brand color. defaults to red #fc6767optional
212 | 213 | ### 7. Internationalization 214 | 215 | There are a few hardcoded english strings used in the template, such as the section titles and some labels. If you want to change the default language from english to something else, you can translate these strings by changing the values in `data/strings.json`. 216 | 217 | ## Credits 218 | 219 | Thanks to [Eric Bailey](https://ericwbailey.design/) for his post ["How to not make a résumé in React"](https://ericwbailey.design/writing/how-to-not-make-a-resume-in-react.html), which gave me the idea. 220 | 221 | ## Colophon 222 | 223 | * "Butler" headline font by [Fabian de Smet](https://fabiandesmet.com/portfolio/butler-font/) (CC BY-SA 4.0 [License](https://github.com/maxboeck/resume/tree/master/src/assets/fonts/Butler_ButlerStencil_FontLicense_v1_0.pdf)) 224 | * Avatar image generated by GAN at [thispersondoesnotexist.com](https://www.thispersondoesnotexist.com/). 225 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "npm run build" 3 | publish = "dist" 4 | 5 | [context.production.environment] 6 | ELEVENTY_ENV = "production" 7 | 8 | [[plugins]] 9 | package = "netlify-plugin-cache" 10 | [plugins.inputs] 11 | # Optional (but highly recommended). Defaults to [".cache"]. 12 | paths = [".cache"] 13 | 14 | [[headers]] 15 | for = "/*" 16 | [headers.values] 17 | X-Frame-Options = "DENY" 18 | X-XSS-Protection = "1; mode=block" 19 | X-Content-Type-Options = "nosniff" 20 | Referrer-Policy= "no-referrer-when-downgrade" 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "resume", 3 | "version": "1.1.0", 4 | "description": "An online resume", 5 | "scripts": { 6 | "start": "npm run dev", 7 | "dev": "run-s clean eleventy:dev", 8 | "build": "run-s clean eleventy:prod", 9 | "eleventy:dev": "cross-env ELEVENTY_ENV=development eleventy --serve", 10 | "eleventy:prod": "cross-env ELEVENTY_ENV=production eleventy", 11 | "clean": "del-cli dist", 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/maxboeck/resume" 17 | }, 18 | "author": "Max Böck", 19 | "license": "MIT", 20 | "dependencies": { 21 | "@11ty/eleventy": "^0.12.1", 22 | "@11ty/eleventy-cache-assets": "^2.2.1", 23 | "@11ty/eleventy-plugin-rss": "^1.1.1", 24 | "@babel/core": "^7.13.0", 25 | "@babel/plugin-transform-runtime": "^7.13.10", 26 | "@babel/preset-env": "^7.13.12", 27 | "babel-loader": "^8.2.2", 28 | "clean-css": "^5.1.2", 29 | "critical": "^3.0.0", 30 | "cssesc": "^3.0.0", 31 | "del-cli": "^3.0.1", 32 | "focus-visible": "^5.2.0", 33 | "html-minifier": "^4.0.0", 34 | "lodash": "^4.17.21", 35 | "luxon": "^1.26.0", 36 | "markdown-it": "^12.0.4", 37 | "memfs": "^3.2.0", 38 | "mime": "^2.4.4", 39 | "netlify-plugin-cache": "^1.0.3", 40 | "node-sass": "^5.0.0", 41 | "npm-run-all": "^4.1.5", 42 | "svg-sprite": "^1.5.0", 43 | "webpack": "^5.28.0" 44 | }, 45 | "devDependencies": { 46 | "cross-env": "^7.0.3" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/assets/fonts/Butler_ButlerStencil_FontLicense_v1_0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxboeck/resume/9c177fcb8abc315514e57e2701152871a529e2be/src/assets/fonts/Butler_ButlerStencil_FontLicense_v1_0.pdf -------------------------------------------------------------------------------- /src/assets/fonts/butler-medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxboeck/resume/9c177fcb8abc315514e57e2701152871a529e2be/src/assets/fonts/butler-medium.woff -------------------------------------------------------------------------------- /src/assets/fonts/butler-medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxboeck/resume/9c177fcb8abc315514e57e2701152871a529e2be/src/assets/fonts/butler-medium.woff2 -------------------------------------------------------------------------------- /src/assets/icons/behance.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/dribbble.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/email.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/fork.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/icons/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/laptop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/linkedin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/medium.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/print.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/reddit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/icons/skype.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/slack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/icons/telephone.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/whatsapp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxboeck/resume/9c177fcb8abc315514e57e2701152871a529e2be/src/assets/images/avatar.jpg -------------------------------------------------------------------------------- /src/assets/images/demo/resume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxboeck/resume/9c177fcb8abc315514e57e2701152871a529e2be/src/assets/images/demo/resume.png -------------------------------------------------------------------------------- /src/assets/images/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxboeck/resume/9c177fcb8abc315514e57e2701152871a529e2be/src/assets/images/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/assets/images/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxboeck/resume/9c177fcb8abc315514e57e2701152871a529e2be/src/assets/images/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/assets/images/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxboeck/resume/9c177fcb8abc315514e57e2701152871a529e2be/src/assets/images/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /src/assets/images/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxboeck/resume/9c177fcb8abc315514e57e2701152871a529e2be/src/assets/images/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /src/assets/images/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxboeck/resume/9c177fcb8abc315514e57e2701152871a529e2be/src/assets/images/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/images/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxboeck/resume/9c177fcb8abc315514e57e2701152871a529e2be/src/assets/images/favicon/favicon.ico -------------------------------------------------------------------------------- /src/assets/images/favicon/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/assets/scripts/main.js: -------------------------------------------------------------------------------- 1 | import 'focus-visible' 2 | 3 | document.documentElement.classList.remove('no-js') 4 | 5 | // Scroll State 6 | const onScroll = () => { 7 | const scrollClassName = 'js-scrolled' 8 | const scrollTreshold = 200 9 | const isOverTreshold = window.scrollY > scrollTreshold 10 | 11 | if (isOverTreshold) { 12 | document.documentElement.classList.add(scrollClassName) 13 | } else { 14 | document.documentElement.classList.remove(scrollClassName) 15 | } 16 | } 17 | window.addEventListener('scroll', onScroll, { passive: true }) 18 | 19 | // Print Button 20 | const printButton = document.querySelector('.js-print') 21 | printButton.addEventListener('click', () => { 22 | window.print() 23 | }) 24 | -------------------------------------------------------------------------------- /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/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 $secondary-color; 9 | outline: 5px solid var(--secondary-color, $secondary-color); 10 | } 11 | 12 | // ================= 13 | // FOCUS-VISIBLE POLYFILL 14 | // ================= 15 | 16 | .js-focus-visible :focus:not(.focus-visible) { 17 | outline: none; 18 | } 19 | 20 | // ================= 21 | // EXCEPTIONS 22 | // ================= 23 | 24 | // sections excluded from the tabindex 25 | [tabindex='-1']:focus { 26 | outline: none !important; 27 | } 28 | 29 | // the skip link is only visible on focus 30 | .sr-skip-link:focus { 31 | outline: none; 32 | } 33 | 34 | // links that are both focused AND hovered 35 | a:focus:hover { 36 | outline: none; 37 | } 38 | -------------------------------------------------------------------------------- /src/assets/styles/base/_fonts.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Butler'; 3 | font-style: normal; 4 | font-display: swap; 5 | font-weight: 500; 6 | src: url('/assets/fonts/butler-medium.woff2') format('woff2'), 7 | url('/assets/fonts/butler-medium.woff') format('woff'); 8 | } 9 | -------------------------------------------------------------------------------- /src/assets/styles/base/_layout.scss: -------------------------------------------------------------------------------- 1 | // Main Site Layout 2 | 3 | body { 4 | overflow-x: hidden; 5 | } 6 | 7 | .layout { 8 | min-height: 100%; 9 | min-height: 100vh; 10 | position: relative; 11 | 12 | .header { 13 | grid-area: header; 14 | padding: $spacing-y 0; 15 | } 16 | .main { 17 | grid-area: main; 18 | } 19 | .sidebar { 20 | grid-area: sidebar; 21 | background-color: $primary-color; 22 | background-color: var(--primary-color, $primary-color); 23 | } 24 | .footer { 25 | grid-area: footer; 26 | padding: $spacing-y 0; 27 | background-color: $gray-light; 28 | } 29 | 30 | @include mq(lg) { 31 | display: grid; 32 | grid-template-columns: 25% 1fr; 33 | grid-template-rows: auto 1fr auto; 34 | grid-template-areas: 35 | 'sidebar header' 36 | 'sidebar main' 37 | 'sidebar footer'; 38 | } 39 | } 40 | 41 | .container { 42 | padding-left: 5%; 43 | padding-right: 5%; 44 | max-width: $container-max-width; 45 | 46 | @include mq(lg) { 47 | padding-left: 7%; 48 | padding-right: 7%; 49 | } 50 | } 51 | 52 | .grid { 53 | display: grid; 54 | column-gap: $spacing-x * 2; 55 | page-break-inside: avoid; 56 | 57 | &--2col { 58 | grid-template-columns: 1fr 1fr; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /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 | color: var(--text-color, $text-color); 85 | background-color: $bg-color; 86 | 87 | -ms-text-size-adjust: 100%; 88 | -webkit-text-size-adjust: 100%; 89 | } 90 | 91 | // 92 | // Typography 93 | // 94 | 95 | // Remove top margins from headings 96 | // 97 | // By default, `

`-`

` all receive top and bottom margins. We nuke the top 98 | // margin for easier control within type scales as it avoids margin collapsing. 99 | 100 | // Abbreviations and acronyms 101 | abbr[title], 102 | // Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257 103 | abbr[data-original-title] { 104 | cursor: help; 105 | } 106 | 107 | ol, 108 | ul, 109 | dl { 110 | padding: 0; 111 | margin: 0; 112 | list-style-type: none; 113 | } 114 | 115 | // 116 | // Code 117 | // 118 | 119 | pre { 120 | margin: 0; 121 | } 122 | 123 | // 124 | // Images 125 | // 126 | 127 | img { 128 | // By default, ``s are `inline-block`. This assumes that, and vertically 129 | // centers them. This won't apply should you reset them to `block` level. 130 | vertical-align: middle; 131 | // Note: ``s are deliberately not made responsive by default. 132 | // For the rationale behind this, see the comments on the `.img-fluid` class. 133 | } 134 | 135 | // iOS "clickable elements" fix for role="button" 136 | // 137 | // Fixes "clickability" issue (and more generally, the firing of events such as focus as well) 138 | // for traditionally non-focusable elements with role="button" 139 | // see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile 140 | 141 | [role='button'] { 142 | cursor: pointer; 143 | } 144 | 145 | // Avoid 300ms click delay on touch devices that support the `touch-action` CSS property. 146 | // 147 | // In particular, unlike most other browsers, IE11+Edge on Windows 10 on touch devices and IE Mobile 10-11 148 | // DON'T remove the click delay when `` is present. 149 | // However, they DO support removing the click delay via `touch-action: manipulation`. 150 | // See: 151 | // * http://v4-alpha.getbootstrap.com/content/reboot/#click-delay-optimization-for-touch 152 | // * http://caniuse.com/#feat=css-touch-action 153 | // * http://patrickhlauke.github.io/touch/tests/results/#suppressing-300ms-delay 154 | 155 | a, 156 | area, 157 | button, 158 | [role='button'], 159 | input, 160 | label, 161 | select, 162 | summary, 163 | textarea { 164 | touch-action: manipulation; 165 | } 166 | 167 | // 168 | // Tables 169 | // 170 | 171 | th { 172 | // Centered by default, but left-align-ed to match the `td`s below. 173 | text-align: left; 174 | } 175 | 176 | // 177 | // Forms 178 | // 179 | 180 | label { 181 | // Allow labels to use `margin` for spacing. 182 | display: inline-block; 183 | margin: 0; 184 | } 185 | 186 | input, 187 | button, 188 | select, 189 | textarea { 190 | // Remove all `margin`s so our classes don't have to do it themselves. 191 | margin: 0; 192 | // Normalize includes `font: inherit;`, so `font-family`. `font-size`, etc are 193 | // properly inherited. However, `line-height` isn't addressed there. Using this 194 | // ensures we don't need to unnecessarily redeclare the global font stack. 195 | line-height: inherit; 196 | // iOS adds rounded borders by default 197 | border-radius: 0; 198 | } 199 | 200 | textarea { 201 | // Textareas should really only resize vertically so they don't break their (horizontal) containers. 202 | resize: vertical; 203 | } 204 | 205 | fieldset { 206 | // Chrome and Firefox set a `min-width: min-content;` on fieldsets, 207 | // so we reset that to ensure it behaves more like a standard block element. 208 | // See https://github.com/twbs/bootstrap/issues/12359. 209 | min-width: 0; 210 | padding: 0; 211 | margin: 0; 212 | border: 0; 213 | } 214 | 215 | legend { 216 | // Reset the entire legend element to match the `fieldset` 217 | display: block; 218 | width: 100%; 219 | padding: 0; 220 | margin-bottom: 0.5rem; 221 | font-size: 1.5rem; 222 | line-height: inherit; 223 | border: 0; 224 | } 225 | 226 | input[type='search'] { 227 | // Undo Normalize's default here to match our global overrides. 228 | box-sizing: inherit; 229 | // This overrides the extra rounded corners on search inputs in iOS so that our 230 | // `.form-control` class can properly style them. Note that this cannot simply 231 | // be added to `.form-control` as it's not specific enough. For details, see 232 | // https://github.com/twbs/bootstrap/issues/11586. 233 | -webkit-appearance: none; 234 | } 235 | 236 | ::selection { 237 | background: $primary-color; 238 | background: var(--primary-color, $primary-color); 239 | color: #fff; 240 | } 241 | ::-moz-selection { 242 | background: $primary-color; 243 | background: var(--primary-color, $primary-color); 244 | color: #fff; 245 | } 246 | -------------------------------------------------------------------------------- /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.2; 9 | font-family: $font-family-serif; 10 | font-weight: 500; 11 | text-rendering: optimizeLegibility; 12 | } 13 | 14 | h1 { 15 | font-size: 3.5rem; 16 | line-height: 1; 17 | 18 | @include mq(lg) { 19 | font-size: 4.5rem; 20 | } 21 | } 22 | 23 | h2 { 24 | font-size: 2.5rem; 25 | } 26 | 27 | h3 { 28 | font-size: 1.75rem; 29 | } 30 | 31 | p { 32 | margin: 0; 33 | } 34 | 35 | a { 36 | text-decoration: none; 37 | text-decoration-skip-ink: auto; 38 | text-decoration-thickness: from-font; 39 | 40 | color: $primary-color; 41 | color: var(--primary-color, $primary-color); 42 | 43 | @include hover-focus { 44 | text-decoration: underline; 45 | } 46 | } 47 | 48 | .lead { 49 | font-size: 1.25rem; 50 | font-weight: 300; 51 | line-height: 1.5; 52 | max-width: 60ch; 53 | 54 | @include mq(md) { 55 | font-size: 1.5rem; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /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 | .utl-mt0 { 7 | margin-top: 0 !important; 8 | } 9 | .utl-mt1 { 10 | margin-top: 1rem !important; 11 | } 12 | .utl-mt2 { 13 | margin-top: 2rem !important; 14 | } 15 | .utl-mt3 { 16 | margin-top: 3rem !important; 17 | } 18 | .utl-mt4 { 19 | margin-top: 4rem !important; 20 | } 21 | 22 | .utl-mb0 { 23 | margin-bottom: 0 !important; 24 | } 25 | .utl-mb1 { 26 | margin-bottom: 1rem !important; 27 | } 28 | .utl-mb2 { 29 | margin-bottom: 2rem !important; 30 | } 31 | .utl-mb3 { 32 | margin-bottom: 3rem !important; 33 | } 34 | .utl-mb4 { 35 | margin-bottom: 4rem !important; 36 | } 37 | 38 | .utl-space-between { 39 | display: flex; 40 | flex-wrap: wrap; 41 | justify-content: space-between; 42 | } 43 | 44 | // Text Utilities 45 | 46 | .utl-align-left { 47 | text-align: left !important; 48 | } 49 | .utl-align-center { 50 | text-align: center !important; 51 | } 52 | .utl-align-right { 53 | text-align: right !important; 54 | } 55 | 56 | // Display Utils 57 | 58 | .utl-screen-only { 59 | @media print { 60 | display: none !important; 61 | } 62 | } 63 | .utl-print-only { 64 | @media not print { 65 | display: none !important; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/assets/styles/components/_actions.scss: -------------------------------------------------------------------------------- 1 | .actions { 2 | display: flex; 3 | justify-content: center; 4 | 5 | &__btn { 6 | @include button-reset; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | width: 3rem; 11 | height: 3rem; 12 | margin: 1rem; 13 | background-color: transparent; 14 | border-radius: 50%; 15 | color: #fff; 16 | 17 | @include hover-focus { 18 | background-color: rgba(0, 0, 0, 0.25); 19 | } 20 | } 21 | 22 | @include mq(lg) { 23 | display: none; 24 | flex-direction: column; 25 | position: fixed; 26 | top: 220px; 27 | right: 75%; 28 | padding: 0 0.5rem; 29 | text-align: right; 30 | 31 | &__btn { 32 | margin: 0.5rem 1rem; 33 | } 34 | 35 | .js-scrolled & { 36 | display: block; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/assets/styles/components/_bulletlist.scss: -------------------------------------------------------------------------------- 1 | .bulletlist { 2 | list-style: none; 3 | padding-left: 1.5em; 4 | 5 | li { 6 | position: relative; 7 | 8 | &::before { 9 | content: ''; 10 | display: block; 11 | width: 0.5em; 12 | height: 0.5em; 13 | border-radius: 50%; 14 | background-color: $secondary-color; 15 | background-color: var(--secondary-color, $secondary-color); 16 | transform: translate(-50%, 20%); 17 | position: absolute; 18 | top: 0.5em; 19 | left: -1em; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/assets/styles/components/_entry.scss: -------------------------------------------------------------------------------- 1 | .entry { 2 | &__header { 3 | display: flex; 4 | flex-direction: column; 5 | 6 | &:not(:last-child) { 7 | margin-bottom: 0.5rem; 8 | } 9 | } 10 | 11 | &__time { 12 | color: $gray; 13 | font-size: 0.875rem; 14 | letter-spacing: 0.125em; 15 | text-transform: uppercase; 16 | order: -1; 17 | margin-bottom: 0.25rem; 18 | } 19 | 20 | &__title { 21 | } 22 | 23 | &__organization { 24 | font-size: 1.125rem; 25 | } 26 | 27 | &__content { 28 | max-width: 60ch; 29 | * > * { 30 | margin-top: 1em; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/assets/styles/components/_entrylist.scss: -------------------------------------------------------------------------------- 1 | .entrylist { 2 | position: relative; 3 | padding-left: 1rem; 4 | 5 | &::before { 6 | content: ''; 7 | display: block; 8 | width: 1px; 9 | background: $gray; 10 | background: linear-gradient( 11 | to top, 12 | rgba($gray, 0) 0%, 13 | rgba($gray, 1) 100px, 14 | rgba($gray, 1) 100% 15 | ); 16 | position: absolute; 17 | top: 2.25rem; 18 | left: 1rem; 19 | bottom: 0; 20 | } 21 | 22 | > * + * { 23 | margin-top: $spacing-y; 24 | } 25 | 26 | &__item { 27 | position: relative; 28 | padding-left: $spacing-x; 29 | 30 | &::before { 31 | content: ''; 32 | display: block; 33 | width: 0.625rem; 34 | height: 0.625rem; 35 | border-radius: 50%; 36 | background-color: $secondary-color; 37 | background-color: var(--secondary-color, $secondary-color); 38 | position: absolute; 39 | top: 2.25rem; 40 | left: 0; 41 | transform: translate(-50%, 0); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/assets/styles/components/_gh-link.scss: -------------------------------------------------------------------------------- 1 | .gh-link { 2 | @include mq(lg) { 3 | display: block; 4 | position: fixed; 5 | top: 0; 6 | right: 0; 7 | width: 120px; 8 | height: 120px; 9 | overflow: hidden; 10 | 11 | > span { 12 | display: flex; 13 | flex-direction: column; 14 | justify-content: center; 15 | align-items: center; 16 | width: 100px; 17 | height: 100px; 18 | position: absolute; 19 | top: 0; 20 | right: 0; 21 | transform-origin: 50% 50%; 22 | transform: rotate(45deg) translateY(-20px); 23 | color: #fff; 24 | } 25 | 26 | &::before { 27 | content: ''; 28 | display: block; 29 | width: 0; 30 | height: 0; 31 | border-style: solid; 32 | border-width: 0 120px 120px 0; 33 | border-color: transparent #000000 transparent transparent; 34 | position: absolute; 35 | top: 0; 36 | right: 0; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/assets/styles/components/_markdown.scss: -------------------------------------------------------------------------------- 1 | .markdown { 2 | > * + * { 3 | margin-top: 1rem; 4 | } 5 | 6 | a { 7 | @include hyphenate; 8 | } 9 | 10 | img { 11 | max-width: 100%; 12 | height: auto; 13 | } 14 | 15 | ul { 16 | @extend .bulletlist; 17 | } 18 | 19 | ol { 20 | list-style-type: decimal; 21 | padding-left: 1.375rem; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/assets/styles/components/_repolist.scss: -------------------------------------------------------------------------------- 1 | .repolist { 2 | &__item:not(:last-child) { 3 | margin-bottom: 1.5rem; 4 | } 5 | &__title { 6 | font-family: $font-family-base; 7 | font-size: 1.25rem; 8 | } 9 | &__url { 10 | color: $gray; 11 | font-style: italic; 12 | } 13 | &__stats { 14 | display: flex; 15 | flex-wrap: wrap; 16 | font-size: 0.875rem; 17 | padding-top: 0.25rem; 18 | 19 | > p { 20 | display: flex; 21 | align-items: center; 22 | margin-right: 1rem; 23 | } 24 | 25 | .icon { 26 | margin-right: 0.125em; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/assets/styles/components/_section.scss: -------------------------------------------------------------------------------- 1 | .section { 2 | padding-top: $spacing-y; 3 | padding-bottom: $spacing-y; 4 | page-break-inside: avoid; 5 | 6 | &__title { 7 | margin-bottom: $spacing-y; 8 | padding-bottom: 0.5rem; 9 | border-bottom: 1px solid $gray; 10 | } 11 | } 12 | 13 | .grid .section { 14 | grid-column: span 2; 15 | 16 | @include mq(md, true) { 17 | &--half { 18 | grid-column: span 1; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/assets/styles/components/_tooltip.scss: -------------------------------------------------------------------------------- 1 | // ToolTip 2 | 3 | .has-tooltip { 4 | &[aria-label] { 5 | position: relative; 6 | 7 | &::after { 8 | display: block; 9 | content: attr(aria-label); 10 | width: auto; 11 | position: absolute; 12 | z-index: -1; 13 | bottom: 100%; 14 | left: 50%; 15 | padding: 0.25rem 0.5rem; 16 | font-size: 0.75rem; 17 | white-space: nowrap; 18 | line-height: 1; 19 | color: #fff; 20 | background-color: #000; 21 | border-radius: 0.25rem; 22 | opacity: 0; 23 | transform: translate(-50%, 0); 24 | transition: all 0.2s ease; 25 | pointer-events: none; 26 | } 27 | &:hover::after { 28 | display: block; 29 | opacity: 1; 30 | z-index: 100; 31 | transform: translate(-50%, -0.25rem); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/assets/styles/components/_vcard.scss: -------------------------------------------------------------------------------- 1 | .vcard { 2 | display: flex; 3 | flex-wrap: wrap; 4 | position: relative; 5 | 6 | &__content { 7 | padding-top: 1rem; 8 | } 9 | &__title { 10 | display: flex; 11 | flex-wrap: wrap; 12 | align-items: baseline; 13 | } 14 | &__subtitle { 15 | margin-bottom: 1rem; 16 | } 17 | &__avatar { 18 | width: 11rem; 19 | height: 11rem; 20 | flex: 0 0 11rem; 21 | border-radius: 50%; 22 | border: 5px solid #fff; 23 | box-shadow: $box-shadow-elevated; 24 | margin-right: $spacing-x; 25 | } 26 | &__pronouns { 27 | font-size: 1.125rem; 28 | font-family: $font-family-serif; 29 | color: $gray; 30 | } 31 | &__link { 32 | display: flex; 33 | align-items: center; 34 | margin-right: 1rem; 35 | 36 | @include hover-focus { 37 | text-decoration: none; 38 | 39 | .vcard__link-text { 40 | text-decoration: underline; 41 | } 42 | } 43 | 44 | .icon { 45 | margin-right: 0.125em; 46 | } 47 | } 48 | 49 | @include mq(md) { 50 | &__social { 51 | display: flex; 52 | flex-wrap: wrap; 53 | 54 | .vcard__link { 55 | margin-bottom: 0.4em; // compensate line-height 56 | } 57 | } 58 | &__link { 59 | display: inline-flex; 60 | } 61 | } 62 | 63 | @include mq(lg) { 64 | padding: $spacing-y 0; 65 | 66 | @supports (display: grid) { 67 | &__avatar { 68 | position: fixed; 69 | top: calc(#{$spacing-y} + 1rem); 70 | right: 75%; 71 | transform: translateX(25%); 72 | transition: transform 0.2s ease; 73 | margin: 0; 74 | 75 | .js-scrolled & { 76 | transform: translateX(25%) scale(0.75); 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /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/vcard'; 34 | @import 'components/section'; 35 | @import 'components/entry'; 36 | @import 'components/entrylist'; 37 | @import 'components/repolist'; 38 | @import 'components/bulletlist'; 39 | @import 'components/actions'; 40 | @import 'components/tooltip'; 41 | @import 'components/markdown'; 42 | @import 'components/icon'; 43 | @import 'components/gh-link'; 44 | 45 | //-------------------- 46 | // PRINT 47 | // styles for media: print 48 | //-------------------- 49 | 50 | @import 'print/index'; 51 | -------------------------------------------------------------------------------- /src/assets/styles/print/_index.scss: -------------------------------------------------------------------------------- 1 | @media print { 2 | // GENERAL PRINT RESET 3 | 4 | @page { 5 | margin: 1cm 3cm; 6 | } 7 | 8 | :root { 9 | --text-color: #000000; 10 | } 11 | 12 | html, 13 | body { 14 | width: 100%; 15 | min-height: 100%; 16 | font-size: 10px !important; 17 | overflow: visible; 18 | } 19 | 20 | *, 21 | *:before, 22 | *:after { 23 | box-shadow: none !important; 24 | text-shadow: none !important; 25 | transition: none !important; 26 | } 27 | 28 | p, 29 | h2, 30 | h3 { 31 | orphans: 3; 32 | widows: 3; 33 | } 34 | 35 | h1, 36 | h2, 37 | h3 { 38 | page-break-after: avoid; 39 | } 40 | 41 | // ELEMENTS 42 | // PRINT STYLE ADJUSTMENTS 43 | 44 | .layout { 45 | display: block; 46 | } 47 | 48 | .container { 49 | max-width: none !important; 50 | padding-left: 0; 51 | padding-right: 0; 52 | } 53 | 54 | .header { 55 | padding: 0 !important; 56 | } 57 | 58 | .sidebar { 59 | display: none !important; 60 | } 61 | 62 | .footer { 63 | background: transparent !important; 64 | } 65 | 66 | .section__title { 67 | border-color: #b5b5b5; 68 | } 69 | 70 | .vcard__avatar { 71 | position: static !important; 72 | transform: none !important; 73 | margin: 0 2rem 0 0 !important; 74 | border: 0; 75 | } 76 | 77 | .entrylist::before, 78 | .entrylist__item::before, 79 | .bulletlist li::before { 80 | color-adjust: exact !important; 81 | -webkit-print-color-adjust: exact !important; 82 | } 83 | 84 | .entrylist__item, 85 | .entry { 86 | page-break-inside: avoid; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /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/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 | /// Creates media query for breakpoint and bigger 6 | /// 7 | /// @param {string} $name - The name of the breakpoint 8 | /// @param {boolean} $print [false] - If the styles should also apply in print 9 | @mixin mq($name, $print: false) { 10 | $min: breakpoint-min($name, $breakpoints); 11 | @if $min { 12 | @media #{if($print, 'print, all ', 'all')} and (min-width: $min) { 13 | @content; 14 | } 15 | } @else { 16 | @content; 17 | } 18 | } 19 | 20 | /// Creates media query for smaller than breakpoint 21 | /// 22 | /// @param {string} $name - The name of the breakpoint 23 | /// @param {boolean} $print [false] - If the styles should also apply in print 24 | @mixin mq-down($name, $print: false) { 25 | $min: breakpoint-min($name, $breakpoints); 26 | @if $min { 27 | @media #{if($print, 'print, all ', 'all')} and (max-width: ($min - 1px)) { 28 | @content; 29 | } 30 | } @else { 31 | @content; 32 | } 33 | } 34 | 35 | @mixin hover-focus { 36 | &:hover, 37 | &:focus { 38 | @content; 39 | } 40 | } 41 | 42 | @mixin sr-only { 43 | position: absolute; 44 | width: 1px; 45 | height: 1px; 46 | padding: 0; 47 | overflow: hidden; 48 | clip: rect(0, 0, 0, 0); 49 | white-space: nowrap; 50 | border: 0; 51 | } 52 | 53 | @mixin sr-only-focusable { 54 | &:active, 55 | &:focus { 56 | position: static; 57 | width: auto; 58 | height: auto; 59 | overflow: visible; 60 | clip: auto; 61 | white-space: normal; 62 | } 63 | } 64 | 65 | @mixin hyphenate() { 66 | word-wrap: break-word; 67 | overflow-wrap: break-word; 68 | hyphens: auto; 69 | } 70 | 71 | @mixin coverall() { 72 | position: absolute; 73 | top: 0; 74 | left: 0; 75 | bottom: 0; 76 | right: 0; 77 | } 78 | 79 | @mixin scrollable() { 80 | overflow-y: auto; 81 | -webkit-overflow-scrolling: touch; 82 | } 83 | 84 | @mixin button-reset() { 85 | border: 0; 86 | padding: 0; 87 | background-color: transparent; 88 | -webkit-appearance: none; 89 | } 90 | -------------------------------------------------------------------------------- /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 | // Colors 19 | // 20 | // Grayscale. 21 | 22 | $gray-darkest: #373a3c; 23 | $gray-dark: #55595c; 24 | $gray: #818a91; 25 | $gray-light: #eceeef; 26 | $gray-lightest: #f7f7f9; 27 | 28 | // Brand Colors 29 | 30 | $brand-blue: #005b96; 31 | $brand-red: #fc6767; 32 | 33 | // Color Mappings 34 | 35 | $bg-color: #fff; 36 | $text-color: $gray-darkest; 37 | 38 | $primary-color: $brand-blue; 39 | $secondary-color: $brand-red; 40 | 41 | // Typography 42 | // 43 | // Font, line-height, and color for body text, headings, and more. 44 | 45 | $font-family-sans-serif: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 46 | 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 47 | sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; 48 | $font-family-serif: 'Butler', Georgia, Times, serif; 49 | $font-family-base: $font-family-sans-serif; 50 | 51 | $font-size-root: 100%; 52 | $font-size-base: 1rem; 53 | $line-height: 1.625; 54 | 55 | // Animation 56 | // 57 | // Default easing curves, Transitions, etc 58 | 59 | $animation-curve-fast-out-slow-in: cubic-bezier(0.4, 0, 0.2, 1); 60 | $animation-curve-linear-out-slow-in: cubic-bezier(0, 0, 0.2, 1); 61 | $animation-curve-fast-out-linear-in: cubic-bezier(0.4, 0, 1, 1); 62 | $animation-curve-default: $animation-curve-fast-out-slow-in; 63 | 64 | // Misc Shared 65 | // 66 | // common shared styles 67 | 68 | $spacing-x: 2rem; 69 | $spacing-y: 2rem; 70 | $box-shadow-elevated: 8px 8px 40px -10px rgba(0, 0, 0, 0.3); 71 | -------------------------------------------------------------------------------- /src/data/author.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Anna Quincey", 3 | "occupation": "Front-End Developer", 4 | "location": "Vienna, AT", 5 | "avatar": "avatar.jpg", 6 | "pronouns": "she/her", 7 | "email": "anna.quincey@gmail.com", 8 | "telephone": "+01 23 456 789", 9 | "website": "https://annaquincey.dev", 10 | "skills": [ 11 | "HTML", 12 | "CSS", 13 | "Javascript / ES6", 14 | "UI Design", 15 | "React", 16 | "Git" 17 | ], 18 | "languages": [ 19 | { 20 | "name": "English", 21 | "level": "native" 22 | }, 23 | { 24 | "name": "German", 25 | "level": "fluent" 26 | }, 27 | { 28 | "name": "French", 29 | "level": "basic" 30 | } 31 | ], 32 | "social": [ 33 | { 34 | "name": "Github", 35 | "user": "@annaquincey", 36 | "url": "https://github.com/annaquincey" 37 | }, 38 | { 39 | "name": "LinkedIn", 40 | "user": "AnnaQncy", 41 | "url": "https://www.linkedin.com/in/annaquincey" 42 | } 43 | ] 44 | } -------------------------------------------------------------------------------- /src/data/build.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: process.env.ELEVENTY_ENV, 3 | demo: process.env.DEMO || false, 4 | timestamp: new Date() 5 | } 6 | -------------------------------------------------------------------------------- /src/data/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://demo-resume.netlify.app", 3 | "lang": "en", 4 | "locale": "en_us", 5 | "colors": { 6 | "primary": "#005b96", 7 | "secondary": "#fc6767" 8 | } 9 | } -------------------------------------------------------------------------------- /src/data/repositories.js: -------------------------------------------------------------------------------- 1 | const Cache = require('@11ty/eleventy-cache-assets') 2 | const orderBy = require('lodash/orderBy') 3 | 4 | // if you want to display your most starred github repositories, 5 | // change this to your username. if not, set it to false. 6 | const YOUR_GITHUB_USERNAME = 'maxboeck' 7 | 8 | module.exports = async function () { 9 | if (!YOUR_GITHUB_USERNAME) { 10 | return [] 11 | } 12 | 13 | try { 14 | console.log('Fetching GitHub repos...') 15 | const repos = await Cache( 16 | `https://api.github.com/users/${YOUR_GITHUB_USERNAME}/repos`, 17 | { 18 | duration: '1d', 19 | type: 'json' 20 | } 21 | ) 22 | return orderBy(repos, 'stargazers_count', 'desc') 23 | } catch (e) { 24 | console.log('Failed fetching GitHub repos') 25 | return [] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/data/strings.json: -------------------------------------------------------------------------------- 1 | { 2 | "titles": { 3 | "experience": "Work Experience", 4 | "education": "Education", 5 | "skills": "Skills", 6 | "languages": "Languages", 7 | "open_source": "Open Source" 8 | }, 9 | "labels": { 10 | "email": "Email", 11 | "telephone": "Telephone", 12 | "print": "Print Résumé", 13 | "present": "Present", 14 | "lastUpdated": "Last updated" 15 | } 16 | } -------------------------------------------------------------------------------- /src/entries/content/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: custom 3 | title: Interests 4 | --- 5 | 6 | This is custom content. You can add [anything you want](https://www.youtube.com/watch?v=dQw4w9WgXcQ) in here. 7 | 8 | * Reading 9 | * Punk Rock 10 | * Climbing 11 | * Archery -------------------------------------------------------------------------------- /src/entries/content/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | tags: introduction 3 | title: About Me 4 | --- 5 | 6 | I am a web developer with over 12 years of experience in the front-end and a special background in digital design. My focus is on creating engaging, accessible & performant interfaces for humans. I am currently looking for new challenges. -------------------------------------------------------------------------------- /src/entries/education/2012-a-levels.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: A-Levels 3 | organization: Saint Ipsum High School 4 | location: Graz, AT 5 | start: 2004-09-01 6 | end: 2012-07-01 7 | --- -------------------------------------------------------------------------------- /src/entries/education/2014-digital-design.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Bachelor, Graphic Design 3 | organization: Lorem Arts Academy 4 | location: Vienna, AT 5 | start: 2013-04-16 6 | end: 2013-10-01 7 | --- -------------------------------------------------------------------------------- /src/entries/education/2016-master-webdevelopment.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Master, Webdevelopment 3 | organization: SAE Institute 4 | organizationUrl: https://www.sae.edu 5 | location: Vienna, AT 6 | start: 2014-01-30 7 | end: 2016-08-14 8 | --- -------------------------------------------------------------------------------- /src/entries/entries.json: -------------------------------------------------------------------------------- 1 | { 2 | "permalink": false 3 | } -------------------------------------------------------------------------------- /src/entries/work/2019-another-position.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Intern 3 | organization: Microsoft 4 | organizationUrl: https://www.microsoft.com 5 | location: Redmond, CA 6 | start: 2019-02-18 7 | end: 2019-11-21 8 | --- 9 | 10 | Ipsum a arcu cursus vitae congue. Arcu ac tortor dignissim convallis. Integer malesuada nunc vel risus commodo. Vitae turpis massa sed elementum. Quam elementum pulvinar etiam non quam lacus suspendisse faucibus interdum. -------------------------------------------------------------------------------- /src/entries/work/2019-lorem-ipsum.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Junior Developer 3 | organization: Mozilla 4 | organizationUrl: https://www.mozilla.org 5 | location: Mountain View, CA 6 | start: 2019-11-30 7 | end: 2020-03-14 8 | --- 9 | 10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. In nulla posuere sollicitudin aliquam ultrices sagittis orci. Nunc congue nisi vitae suscipit tellus mauris a diam maecenas. Sit amet consectetur adipiscing elit. -------------------------------------------------------------------------------- /src/entries/work/2020-sample-position.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Senior Front-End Developer 3 | organization: Netlify 4 | organizationUrl: https://www.netlify.com 5 | location: San Francisco, CA 6 | start: 2020-03-14 7 | --- 8 | 9 | Porta non pulvinar neque laoreet suspendisse interdum consectetur libero id. Gravida arcu ac tortor dignissim convallis aenean et. In massa tempor nec feugiat nisl. Sed blandit libero volutpat sed cras ornare arcu dui. Morbi leo urna molestie at. Sapien faucibus et molestie ac feugiat sed lectus. -------------------------------------------------------------------------------- /src/includes/entry.njk: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{ entry.data.title }}

4 | 5 |

6 | 9 | 10 | {% if entry.data.end %} 11 | 14 | {% else %} 15 | {{ strings.labels.present }} 16 | {% endif %} 17 |

18 | 19 | {% if entry.data.organization %} 20 |

21 | {% if entry.data.organizationUrl %} 22 | {{ entry.data.organization }} 23 | {% else %} 24 | {{ entry.data.organization }} 25 | {% endif %} 26 | {% if entry.data.location %} 27 | 28 | {{ entry.data.location }} 29 | {% endif %} 30 |

31 | {% endif %} 32 |
33 | 34 | {% if entry.templateContent %} 35 |
36 | {{ entry.templateContent | safe }} 37 |
38 | {% endif %} 39 |
-------------------------------------------------------------------------------- /src/includes/entrylist.njk: -------------------------------------------------------------------------------- 1 | {% if entries %} 2 |
    3 | {% for entry in entries | reverse %} 4 |
  1. 5 | {% include "entry.njk" %} 6 |
  2. 7 | {% endfor %} 8 |
9 | {% endif %} -------------------------------------------------------------------------------- /src/includes/footer.njk: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/includes/header.njk: -------------------------------------------------------------------------------- 1 |
2 |
3 | {% include "vcard.njk" %} 4 |
5 |
-------------------------------------------------------------------------------- /src/includes/meta.njk: -------------------------------------------------------------------------------- 1 | {%- set absolutePageUrl -%}{{ page.url | url | absoluteUrl(meta.url) }}{%- endset -%} 2 | {%- set pageTitle -%}{{ author.name }}{% if author.occupation %} - {{ author.occupation }}{% endif %}{% if author.location %} in {{ author.location }}{% endif %}{%- endset -%} 3 | {%- set pageDescription -%}Online Resume of {{ author.name }}{%- endset -%} 4 | 5 | 6 | 7 | {{ pageTitle }} 8 | 9 | {# General #} 10 | 11 | 12 | 13 | {# Open Graph #} 14 | 15 | 16 | 17 | 18 | 19 | 20 | {%- if author.avatar -%} 21 | {%- set avatarUrl -%}/assets/images/{{ author.avatar }}{% endset %} 22 | 23 | {%- endif -%} 24 | 25 | {# Favicon #} 26 | 27 | 28 | 29 | 30 | 31 | {% if meta.colors.primary %} 32 | 33 | 34 | {% endif %} 35 | 36 | {# JSON LD #} 37 | -------------------------------------------------------------------------------- /src/includes/repositories.njk: -------------------------------------------------------------------------------- 1 | {% if repositories.length %} 2 |
    3 | {% for repo in repositories.slice(0, 5) %} 4 |
  1. 5 |

    {{ repo.name }}

    6 |

    {{ repo.html_url }}

    7 | {% if repo.description %} 8 |

    {{ repo.description }}

    9 | {% endif %} 10 |
    11 |

    {%- icon "star" -%}{{ repo.stargazers_count }} stars

    12 |

    {%- icon "fork" -%}{{ repo.forks_count }} forks

    13 |
    14 |
  2. 15 | {% endfor %} 16 |
17 | {% endif %} -------------------------------------------------------------------------------- /src/includes/sidebar.njk: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/includes/vcard.njk: -------------------------------------------------------------------------------- 1 |
2 | {% if author.avatar %} 3 | {%- set avatarUrl -%}/assets/images/{{ author.avatar }}{% endset %} 4 | {{ author.name }} 5 | {% endif %} 6 | 7 |
8 |
9 |

{{ author.name }}

10 | {% if author.pronouns %} 11 | ({{ author.pronouns }}) 12 | {% endif %} 13 |
14 | 15 |

16 | {{ author.occupation }} 17 | {% if author.location %} 18 | 19 | {{ author.location }} 20 | {% endif %} 21 |

22 | 23 |
24 | {% if author.email %} 25 | 26 | {% icon "email" %} 27 | {{ author.email | obfuscate | safe }} 28 | 29 | {% endif %} 30 | {% if author.telephone %} 31 | 32 | {% icon "telephone" %} 33 | {{ author.telephone }} 34 | 35 | {% endif %} 36 | {% if author.website %} 37 | 38 | {% icon "laptop" %} 39 | {{ author.website | stripProtocol }} 40 | 41 | {% endif %} 42 | 43 | {% if author.social %} 44 | 54 | {% endif %} 55 |
56 |
57 |
-------------------------------------------------------------------------------- /src/index.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: resume 3 | permalink: / 4 | --- 5 | 6 | {# This file generates the index.html page. #} 7 | {# Do not edit it directly. Instead, edit the files in src/entries. #} 8 | {# see README.md for more. #} -------------------------------------------------------------------------------- /src/layouts/base.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include "meta.njk" %} 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | {% include "header.njk" %} 13 | {{ content | safe }} 14 | {% include "sidebar.njk" %} 15 | {% include "footer.njk" %} 16 |
17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/layouts/resume.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base 3 | --- 4 | 5 |
6 | 7 | {% if collections.introduction.length %} 8 | {% set intro = collections.introduction|first %} 9 |
10 |

{{ intro.data.title }}

11 |
12 | {{ intro.templateContent | safe }} 13 |
14 |
15 | {% endif %} 16 | 17 |
18 |

{{ strings.titles.experience }}

19 | {% set entries = collections.work %} 20 | {% set microformat = "p-experience" %} 21 | {% include "entrylist.njk" %} 22 |
23 | 24 |
25 |

{{ strings.titles.education }}

26 | {% set entries = collections.education %} 27 | {% set microformat = "p-education" %} 28 | {% include "entrylist.njk" %} 29 |
30 | 31 | {% if author.skills or author.languages %} 32 |
33 | {% if author.skills %} 34 |
35 |

{{ strings.titles.skills }}

36 |
    37 | {% for skill in author.skills %} 38 |
  • {{ skill }}
  • 39 | {% endfor %} 40 |
41 |
42 | {% endif %} 43 | 44 | {% if author.languages %} 45 |
46 |

{{ strings.titles.languages }}

47 |
    48 | {% for language in author.languages %} 49 |
  • {{ language.name }} ({{ language.level }})
  • 50 | {% endfor %} 51 |
52 |
53 | {% endif %} 54 |
55 | {% endif %} 56 | 57 | {% if repositories.length %} 58 |
59 |

{{ strings.titles.open_source }}

60 | {% include "repositories.njk" %} 61 |
62 | {% endif %} 63 | 64 | {% if collections.custom.length %} 65 | {% set custom = collections.custom|first %} 66 |
67 | {% if custom.data.title %} 68 |

{{ custom.data.title }}

69 | {% endif %} 70 |
71 | {{ custom.templateContent | safe }} 72 |
73 |
74 | {% endif %} 75 |
-------------------------------------------------------------------------------- /src/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org 2 | 3 | # Allow crawling of all content 4 | User-agent: * 5 | Disallow: -------------------------------------------------------------------------------- /src/site.webmanifest.njk: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: site.webmanifest 3 | --- 4 | { 5 | "lang": "{{ meta.lang }}", 6 | "dir": "ltr", 7 | "name": "Resume of {{ author.name }}", 8 | "short_name": "{{ author.name }}", 9 | "icons": [ 10 | { 11 | "src": "/assets/images/favicon/android-chrome-192x192.png", 12 | "sizes": "192x192", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "/assets/images/favicon/android-chrome-512x512.png", 17 | "sizes": "512x512", 18 | "type": "image/png" 19 | } 20 | ], 21 | "theme_color": "{{ meta.colors.primary or "#1a1a1a" }}", 22 | "background_color": "{{ meta.colors.primary or "#1a1a1a" }}", 23 | "start_url": "/index.html", 24 | "display": "standalone", 25 | "orientation": "natural" 26 | } 27 | -------------------------------------------------------------------------------- /utils/filters.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const mime = require('mime/lite') 4 | const { DateTime } = require('luxon') 5 | const isEmpty = require('lodash/isEmpty') 6 | 7 | module.exports = { 8 | dateToFormat: function (date, format) { 9 | return DateTime.fromJSDate(date, { zone: 'utc' }).toFormat( 10 | String(format) 11 | ) 12 | }, 13 | 14 | dateToISO: function (date) { 15 | return DateTime.fromJSDate(date, { zone: 'utc' }).toISO({ 16 | includeOffset: false, 17 | suppressMilliseconds: true 18 | }) 19 | }, 20 | 21 | obfuscate: function (str) { 22 | const chars = [] 23 | for (var i = str.length - 1; i >= 0; i--) { 24 | chars.unshift(['&#', str[i].charCodeAt(), ';'].join('')) 25 | } 26 | return chars.join('') 27 | }, 28 | 29 | stripSpaces: function (str) { 30 | return str.replace(/\s/g, '') 31 | }, 32 | 33 | stripProtocol: function (str) { 34 | return str.replace(/(^\w+:|^)\/\//, '') 35 | }, 36 | 37 | base64file: function (file) { 38 | const filepath = path.join(__dirname, `../src/${file}`) 39 | const mimeType = mime.getType(file) 40 | const buffer = Buffer.from(fs.readFileSync(filepath)) 41 | 42 | return `data:${mimeType};base64,${buffer.toString('base64')}` 43 | }, 44 | 45 | themeColors: function (colors) { 46 | let style = '' 47 | if (!colors || isEmpty(colors)) { 48 | return '' 49 | } 50 | if (colors.primary) { 51 | style += `--primary-color:${colors.primary};` 52 | } 53 | if (colors.secondary) { 54 | style += `--secondary-color:${colors.secondary};` 55 | } 56 | return style 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /utils/iconsprite.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const util = require('util') 4 | const glob = require('glob') 5 | const File = require('vinyl') 6 | const SVGSpriter = require('svg-sprite') 7 | 8 | const cwd = path.resolve('src/assets/icons') 9 | const spriteConfig = { 10 | mode: { 11 | inline: true, 12 | symbol: { 13 | sprite: 'sprite.svg', 14 | example: false 15 | } 16 | }, 17 | shape: { 18 | transform: ['svgo'], 19 | id: { 20 | generator: 'icon-%s' 21 | } 22 | }, 23 | svg: { 24 | xmlDeclaration: false, 25 | doctypeDeclaration: false 26 | } 27 | } 28 | 29 | module.exports = async () => { 30 | // Make a new SVGSpriter instance w/ configuration 31 | const spriter = new SVGSpriter(spriteConfig) 32 | 33 | // Wrap spriter compile function in a Promise 34 | const compileSprite = async (args) => { 35 | return new Promise((resolve, reject) => { 36 | spriter.compile(args, (error, result) => { 37 | if (error) { 38 | return reject(error) 39 | } 40 | resolve(result.symbol.sprite) 41 | }) 42 | }) 43 | } 44 | 45 | // Get all SVG icon files in working directory 46 | const getFiles = util.promisify(glob) 47 | const files = await getFiles('**/*.svg', { cwd: cwd }) 48 | 49 | // Add them all to the spriter 50 | files.forEach(function (file) { 51 | spriter.add( 52 | new File({ 53 | path: path.join(cwd, file), 54 | base: cwd, 55 | contents: fs.readFileSync(path.join(cwd, file)) 56 | }) 57 | ) 58 | }) 59 | 60 | // Compile the sprite file and return it as a string 61 | const sprite = await compileSprite(spriteConfig.mode) 62 | const spriteContent = sprite.contents.toString('utf8') 63 | return spriteContent 64 | } 65 | -------------------------------------------------------------------------------- /utils/shortcodes.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | icon: function (name, isSocial) { 3 | const id = name.toLowerCase().replace(/\s/g, '') 4 | const availableSocialIcons = [ 5 | 'github', 6 | 'twitter', 7 | 'linkedin', 8 | 'skype', 9 | 'dribbble', 10 | 'behance', 11 | 'medium', 12 | 'reddit', 13 | 'slack', 14 | 'whatsapp' 15 | ] 16 | if (isSocial && !availableSocialIcons.includes(id)) { 17 | return `` 18 | } 19 | return `` 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------