├── src ├── about │ ├── about.json │ ├── index.md │ ├── team.md │ └── privacy.md ├── js │ ├── main.js │ ├── lib │ │ ├── dom.js │ │ └── theme.js │ └── javascript.11ty.js ├── articles │ ├── articles.json │ ├── index.md │ ├── article-05.md │ ├── article-04.md │ ├── article-02.md │ ├── article-03.md │ ├── article-01.md │ └── article-06.md ├── images │ ├── orb.jpg │ ├── phone.jpg │ ├── keyboard.jpg │ ├── logo.svg │ ├── github.svg │ └── images.11ty.js ├── _includes │ ├── partials │ │ ├── htmlfoot.njk │ │ ├── htmlhead.njk │ │ └── pagelist.njk │ ├── post.njk │ └── page.njk ├── scss │ ├── main.njk │ ├── 01-settings │ │ ├── _mixins.scss │ │ └── _variables.scss │ ├── 04-layout │ │ └── _site.scss │ ├── main.scss │ ├── 02-generic │ │ └── _reset.scss │ ├── 05-components │ │ ├── _footer.scss │ │ ├── _article.scss │ │ ├── _themetoggle.scss │ │ └── _header.scss │ └── 03-elements │ │ └── _primary.scss └── index.md ├── .gitignore ├── lib ├── transforms │ ├── inline.js │ ├── htmlminify.js │ └── postcss.js ├── filters │ ├── readtime.js │ └── dateformat.js └── shortcodes │ └── navlist.js ├── LICENSE ├── package.json ├── .eleventy.js └── README.md /src/about/about.json: -------------------------------------------------------------------------------- 1 | { 2 | "layout": "page.njk" 3 | } 4 | -------------------------------------------------------------------------------- /src/js/main.js: -------------------------------------------------------------------------------- 1 | import * as theme from './lib/theme.js'; 2 | -------------------------------------------------------------------------------- /src/articles/articles.json: -------------------------------------------------------------------------------- 1 | { 2 | "layout": "post.njk", 3 | "hero": "phone.jpg" 4 | } 5 | -------------------------------------------------------------------------------- /src/images/orb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craigbuckler/11ty-starter/HEAD/src/images/orb.jpg -------------------------------------------------------------------------------- /src/images/phone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craigbuckler/11ty-starter/HEAD/src/images/phone.jpg -------------------------------------------------------------------------------- /src/images/keyboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craigbuckler/11ty-starter/HEAD/src/images/keyboard.jpg -------------------------------------------------------------------------------- /src/_includes/partials/htmlfoot.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/scss/main.njk: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /css/main.css 3 | eleventyExcludeFromCollections: true 4 | --- 5 | @import 'main.scss'; 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | 3 | node_modules 4 | 5 | .npm 6 | package-lock.json 7 | npm-debug.log* 8 | 9 | logs 10 | *.log 11 | 12 | .DS_Store 13 | Thumbs.db 14 | -------------------------------------------------------------------------------- /src/scss/01-settings/_mixins.scss: -------------------------------------------------------------------------------- 1 | // mixins 2 | @mixin reset { 3 | padding: 0; 4 | margin: 0; 5 | } 6 | 7 | @mixin resetlist { 8 | list-style-type: none; 9 | @include reset; 10 | } 11 | -------------------------------------------------------------------------------- /src/about/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: About us 3 | description: What we do. 4 | hero: keyboard.jpg 5 | eleventyNavigation: 6 | key: about 7 | order: 200 8 | --- 9 | 10 | Some information about us. 11 | -------------------------------------------------------------------------------- /src/about/team.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Our team 3 | description: Information about us. 4 | eleventyNavigation: 5 | key: team 6 | parent: about 7 | order: 210 8 | --- 9 | 10 | Who are we and what we do. 11 | -------------------------------------------------------------------------------- /src/about/privacy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Privacy policy 3 | description: We keep your details private. 4 | eleventyNavigation: 5 | key: privacy 6 | parent: about 7 | order: 220 8 | --- 9 | 10 | Our privacy policy. 11 | -------------------------------------------------------------------------------- /lib/transforms/inline.js: -------------------------------------------------------------------------------- 1 | // inline data 2 | const { inlineSource } = require('inline-source'); 3 | 4 | module.exports = async (content, outputPath) => { 5 | 6 | if (!String(outputPath).endsWith('.html')) return content; 7 | 8 | return await inlineSource(content, { 9 | compress: true, 10 | rootpath: './build/' 11 | }); 12 | 13 | }; 14 | -------------------------------------------------------------------------------- /src/articles/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Article index 3 | description: A list of articles published on this site. 4 | layout: page.njk 5 | eleventyNavigation: 6 | key: articles 7 | order: 900 8 | pagination: 9 | data: collections.post 10 | alias: pagelist 11 | reverse: true 12 | size: 3 13 | --- 14 | 15 | The following articles are available. 16 | -------------------------------------------------------------------------------- /src/_includes/partials/htmlhead.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ title }} 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lib/transforms/htmlminify.js: -------------------------------------------------------------------------------- 1 | // minify HTML 2 | const htmlmin = require('html-minifier'); 3 | 4 | module.exports = (content, outputPath = '.html') => { 5 | 6 | if (!String(outputPath).endsWith('.html')) return content; 7 | 8 | return htmlmin.minify(content, { 9 | useShortDoctype: true, 10 | removeComments: true, 11 | collapseWhitespace: true 12 | }); 13 | 14 | }; 15 | -------------------------------------------------------------------------------- /src/scss/04-layout/_site.scss: -------------------------------------------------------------------------------- 1 | // main layout 2 | html { 3 | height: 100%; 4 | } 5 | 6 | body { 7 | min-height: 100%; 8 | display: grid; 9 | grid-template-columns: minmax(4vw,1fr) minmax(15rem,35rem) minmax(4vw,1fr); 10 | grid-template-rows: auto 1fr auto; 11 | } 12 | 13 | header, footer { 14 | grid-column: 1/4; 15 | } 16 | 17 | main { 18 | grid-column: 2/3; 19 | padding: 2rem 0 3rem 0; 20 | } 21 | -------------------------------------------------------------------------------- /src/scss/main.scss: -------------------------------------------------------------------------------- 1 | // settings 2 | @import '01-settings/_variables'; 3 | @import '01-settings/_mixins'; 4 | 5 | // reset 6 | @import '02-generic/_reset'; 7 | 8 | // elements 9 | @import '03-elements/_primary'; 10 | 11 | // layout 12 | @import '04-layout/_site'; 13 | 14 | // components 15 | @import '05-components/_header'; 16 | @import '05-components/_footer'; 17 | @import '05-components/_article'; 18 | @import '05-components/_themetoggle'; 19 | -------------------------------------------------------------------------------- /lib/filters/readtime.js: -------------------------------------------------------------------------------- 1 | // format number of words and reading time 2 | const 3 | roundTo = 10, 4 | readPerMin = 200, 5 | numFormat = new Intl.NumberFormat('en'); 6 | 7 | module.exports = count => { 8 | 9 | const 10 | words = Math.ceil(count / roundTo) * roundTo, 11 | mins = Math.ceil(count / readPerMin); 12 | 13 | return `${ numFormat.format(words) } words, ${ numFormat.format(mins) }-minute read`; 14 | 15 | }; 16 | -------------------------------------------------------------------------------- /src/js/lib/dom.js: -------------------------------------------------------------------------------- 1 | // get element by Id 2 | export function id(id, doc = document) { 3 | 4 | return doc.getElementById(id); 5 | 6 | } 7 | 8 | // get element by class name 9 | export function className(className, doc = document) { 10 | 11 | return doc.getElementsByClassName(className); 12 | 13 | } 14 | 15 | // get single element CSS selector 16 | export function query(sel, doc = document) { 17 | 18 | return doc.querySelector(sel); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/scss/02-generic/_reset.scss: -------------------------------------------------------------------------------- 1 | // box sizing 2 | *, *:before, *:after { 3 | box-sizing: border-box; 4 | line-height: var(--line-height); 5 | text-rendering: optimizeLegibility; 6 | } 7 | 8 | body { 9 | overflow-y: scroll; 10 | } 11 | 12 | body, h1, h2, h3 { 13 | font-weight: var(--font-weight); 14 | } 15 | 16 | strong { 17 | font-weight: var(--font-bold); 18 | } 19 | 20 | img { 21 | display: block; 22 | width: 100%; 23 | height: auto; 24 | } 25 | -------------------------------------------------------------------------------- /src/articles/article-05.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: The fifth article 3 | description: This is the fifth article. 4 | date: 2020-09-05 5 | tags: 6 | - HTML 7 | --- 8 | 9 | This is an article post. 10 | 11 | ## Sub-Heading 12 | 13 | Ut eu magna fermentum, mollis dui vel, semper mi. Interdum et malesuada fames ac ante ipsum primis in faucibus. Suspendisse quis porta justo. Donec dictum, dolor ac congue sodales, ipsum arcu mattis felis, eu molestie metus erat eget tortor. 14 | 15 | Fusce in augue vitae dui ullamcorper vestibulum. Donec aliquam hendrerit ultricies. Aliquam non ipsum ut dui aliquet congue. 16 | -------------------------------------------------------------------------------- /src/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /lib/filters/dateformat.js: -------------------------------------------------------------------------------- 1 | // date formatting functions 2 | const toMonth = new Intl.DateTimeFormat('en', { month: 'long' }); 3 | 4 | 5 | // format a date to YYYY-MM-DD 6 | module.exports.ymd = date => ( 7 | 8 | date instanceof Date ? 9 | `${ date.getUTCFullYear() }-${ String(date.getUTCMonth() + 1).padStart(2, '0') }-${ String(date.getUTCDate()).padStart(2, '0') }` : '' 10 | 11 | ); 12 | 13 | 14 | // format a date to DD MMMM, YYYY 15 | module.exports.friendly = date => ( 16 | 17 | date instanceof Date ? 18 | date.getUTCDate() + ' ' + toMonth.format(date) + ', ' + date.getUTCFullYear() : '' 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /src/articles/article-04.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: The fourth article 3 | description: This is the fourth article. 4 | date: 2020-09-04 5 | tags: 6 | - CSS 7 | - JavaScript 8 | --- 9 | 10 | This is an article post. 11 | 12 | ## Sub-Heading 13 | 14 | Quisque eget viverra enim, ac vehicula lectus. Nulla mollis sem sed sapien imperdiet tempus. Vestibulum commodo fermentum sem, in tempus nunc ultricies vitae. Aliquam placerat sodales tincidunt. 15 | 16 | Morbi a nisl nec leo iaculis laoreet a molestie nisl. Quisque in neque non purus fermentum dictum volutpat nec ex. Pellentesque blandit sapien vel nibh tempus, at rhoncus turpis elementum. 17 | -------------------------------------------------------------------------------- /src/articles/article-02.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: The second article 3 | description: This is the second article. 4 | date: 2020-09-02 5 | tags: 6 | - HTML 7 | - JavaScript 8 | --- 9 | 10 | This is an article post. 11 | 12 | ## Sub-Heading 13 | 14 | Donec viverra tincidunt tortor, eget feugiat ligula faucibus id. Donec venenatis ipsum non pharetra molestie. Sed tempor efficitur efficitur. 15 | 16 | Sed consectetur porta erat, non consequat magna lobortis vitae. Cras velit orci, tempus ac luctus at, elementum viverra diam. Donec blandit tellus non ante condimentum, sit amet efficitur purus eleifend. Nulla luctus odio ac ipsum posuere vulputate. 17 | -------------------------------------------------------------------------------- /src/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 11ty starter site 3 | description: This is a demonstration website generated using the 11ty static site generator. 4 | layout: page.njk 5 | eleventyNavigation: 6 | key: home 7 | order: 100 8 | --- 9 | 10 | This is a demonstration website using the [11ty static site generator](https://www.11ty.dev/). It shows pages, blog posts, lists, and tags. 11 | 12 | The whole build process is managed through 11ty. 13 | 14 | Images from [unsplash.com](https://unsplash.com/) courtesy of [Gabriel Porras](https://unsplash.com/@gabrielizalo), [Tim Rüßmann](https://unsplash.com/@timaesthetic), and [Michael Dziedzic](https://unsplash.com/@lazycreekimages). 15 | -------------------------------------------------------------------------------- /lib/transforms/postcss.js: -------------------------------------------------------------------------------- 1 | // PostCSS CSS processing 2 | 3 | /* global dev */ 4 | 5 | const 6 | postcss = require('postcss'), 7 | postcssPlugins = [ 8 | require('postcss-advanced-variables'), 9 | require('postcss-nested'), 10 | require('cssnano') 11 | ], 12 | postcssOptions = { 13 | from: 'src/scss/entry.scss', 14 | syntax: require('postcss-scss'), 15 | map: dev ? { inline: true } : false 16 | }; 17 | 18 | module.exports = async (content, outputPath) => { 19 | 20 | if (!String(outputPath).endsWith('.css')) return content; 21 | 22 | return ( 23 | await postcss(postcssPlugins).process(content, postcssOptions) 24 | ).css; 25 | 26 | }; 27 | -------------------------------------------------------------------------------- /src/articles/article-03.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: The third article 3 | description: This is the third article. 4 | date: 2020-09-03 5 | tags: 6 | - CSS 7 | --- 8 | 9 | This is an article post. 10 | 11 | ## Sub-Heading 12 | 13 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis odio orci, ullamcorper non enim in, egestas aliquam turpis. Maecenas id interdum lacus. Donec ornare consequat lectus ac faucibus. Curabitur ligula dolor, egestas non semper vel, euismod at mauris. 14 | 15 | Nullam congue eros quam, id consequat augue ornare sit amet. Mauris id lectus nec tellus eleifend luctus. Sed mattis lacus in mauris suscipit posuere. Phasellus varius aliquet faucibus. Vestibulum tempus mauris nec pulvinar cursus. 16 | -------------------------------------------------------------------------------- /src/articles/article-01.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: The first article 3 | description: This is the first article. 4 | date: 2020-09-01 5 | tags: 6 | - HTML 7 | - CSS 8 | --- 9 | 10 | This is an article post. 11 | 12 | ## Sub-Heading 13 | 14 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis odio orci, ullamcorper non enim in, egestas aliquam turpis. Maecenas id interdum lacus. Donec ornare consequat lectus ac faucibus. Curabitur ligula dolor, egestas non semper vel, euismod at mauris. 15 | 16 | Nullam congue eros quam, id consequat augue ornare sit amet. Mauris id lectus nec tellus eleifend luctus. Sed mattis lacus in mauris suscipit posuere. Phasellus varius aliquet faucibus. Vestibulum tempus mauris nec pulvinar cursus. 17 | -------------------------------------------------------------------------------- /src/js/lib/theme.js: -------------------------------------------------------------------------------- 1 | // light/dark theme switch 2 | import * as dom from './dom.js'; 3 | 4 | window.addEventListener('DOMContentLoaded', () => { 5 | 6 | const 7 | toggleId = 'themetoggle', 8 | body = document.body; 9 | 10 | let 11 | theme = 12 | localStorage.getItem(toggleId) || 13 | (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); 14 | 15 | // set body class 16 | body.classList.add(theme); 17 | 18 | // detect theme switch 19 | dom.id(toggleId).addEventListener('click', e => { 20 | 21 | e.preventDefault(); 22 | 23 | body.classList.remove(theme); 24 | theme = (theme === 'dark' ? 'light' : 'dark'); 25 | body.classList.add(theme); 26 | localStorage.setItem(toggleId, theme); 27 | 28 | }); 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /src/articles/article-06.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: The sixth article 3 | description: This is the sixth article. 4 | draft: true 5 | date: 2029-09-06 6 | tags: 7 | - HTML 8 | - CSS 9 | - JavaScript 10 | --- 11 | 12 | This is a draft article post. It is visible during development but is removed from the production site. 13 | 14 | ## Sub-Heading 15 | 16 | Quisque ornare blandit pretium. Donec vel eleifend felis. Suspendisse cursus laoreet gravida. Phasellus vitae mollis ante. Suspendisse sit amet magna sit amet dolor pretium pharetra vitae at odio. Quisque sollicitudin ipsum nec ullamcorper posuere. Etiam pulvinar hendrerit tortor a ultrices. 17 | 18 | Nulla facilisi. Nulla at massa eget arcu sollicitudin blandit in eget turpis. Suspendisse nibh erat, hendrerit quis rhoncus quis, dignissim ullamcorper erat. 19 | -------------------------------------------------------------------------------- /src/_includes/post.njk: -------------------------------------------------------------------------------- 1 | {% extends "page.njk" %} 2 | 3 | {% block content %} 4 | 5 |

{{ title }}

6 | 7 | {% if date %}

{% endif %} 8 | 9 |

{{ content | wordcount | readtime }}

10 | 11 | {{ content | safe }} 12 | 13 | {% set nextPost = collections.post | getNextCollectionItem(page) %} 14 | {% if nextPost %}

next article: {{ nextPost.data.title }}

{% endif %} 15 | 16 | {% set previousPost = collections.post | getPreviousCollectionItem(page) %} 17 | {% if previousPost %}

previous article: {{ previousPost.data.title }}

{% endif %} 18 | 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /src/scss/05-components/_footer.scss: -------------------------------------------------------------------------------- 1 | // page footer 2 | footer { 3 | 4 | background-color: var(--color-back2); 5 | border-top: 3px solid var(--color-back4); 6 | 7 | .github { 8 | text-align: center; 9 | margin-block: 1em; 10 | 11 | a { 12 | color: var(--color-back4); 13 | 14 | &::before { 15 | display: none; 16 | } 17 | } 18 | 19 | } 20 | 21 | nav { 22 | margin-block: 1em; 23 | 24 | ul { 25 | @include resetlist; 26 | } 27 | 28 | & > ul { 29 | 30 | display: flex; 31 | justify-content: center; 32 | gap: 1.5rem; 33 | 34 | ul a:link { 35 | font-weight: var(--font-weight); 36 | } 37 | 38 | } 39 | 40 | strong { 41 | color: var(--color-back4); 42 | } 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/scss/05-components/_article.scss: -------------------------------------------------------------------------------- 1 | // article formatting 2 | .pagelist { 3 | 4 | p { 5 | margin: 0; 6 | } 7 | 8 | } 9 | 10 | .pagenav { 11 | display: flex; 12 | justify-content: center; 13 | gap: 2em; 14 | 15 | p { 16 | margin: 0; 17 | } 18 | } 19 | 20 | .time, .words { 21 | display: inline-block; 22 | font-size: 0.8rem; 23 | margin: 0; 24 | margin-inline-end: 1em; 25 | } 26 | 27 | .next, .prev { 28 | 29 | font-size: 0.8em; 30 | margin-block: 0; 31 | 32 | em { 33 | display: inline-block; 34 | width: 7em; 35 | } 36 | 37 | } 38 | 39 | .next, .prev, .pagenav { 40 | margin-block-start: 2em; 41 | padding-block-start: 0.5em; 42 | border-top: 2px dotted var(--color-back3); 43 | } 44 | 45 | .next + .prev { 46 | border: 0 none; 47 | margin: 0; 48 | padding: 0; 49 | } 50 | -------------------------------------------------------------------------------- /src/images/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/_includes/partials/pagelist.njk: -------------------------------------------------------------------------------- 1 | {% if pagelist %} 2 | 17 | {% endif %} 18 | 19 | {% if pagination.href.previous or pagination.href.next %} 20 | 31 | {% endif %} 32 | -------------------------------------------------------------------------------- /src/scss/05-components/_themetoggle.scss: -------------------------------------------------------------------------------- 1 | // theme toggle switch 2 | #themetoggle { 3 | 4 | transform: scale(0); 5 | display: inline-block; 6 | appearance: none; 7 | outline: none; 8 | width: 2em; 9 | height: 2em; 10 | vertical-align: text-top; 11 | margin-inline-start: 1em; 12 | background: #ff0; 13 | border: 0; 14 | border-radius: 50%; 15 | cursor: pointer; 16 | box-shadow: 0 0 2px #fff; 17 | transition: all 0.5s linear; 18 | 19 | &::after { 20 | content: ''; 21 | display: block; 22 | width: 1.7em; 23 | height: 1.7em; 24 | background-color: var(--color-back2); 25 | border-radius: 50%; 26 | transform: scale(0); 27 | transition: transform 0.1s linear; 28 | } 29 | 30 | .light &, .dark & { 31 | transform: scale(1); 32 | } 33 | 34 | .light & { 35 | background: #fff; 36 | 37 | &::after { 38 | transform: scale(1); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/images/images.11ty.js: -------------------------------------------------------------------------------- 1 | // image minification 2 | const 3 | dest = './build/images', 4 | 5 | fsp = require('fs').promises, 6 | imagemin = require('imagemin'), 7 | plugins = [ 8 | require('imagemin-mozjpeg')(), 9 | require('imagemin-pngquant')({ strip: true }), 10 | require('imagemin-svgo')() 11 | ]; 12 | 13 | module.exports = class { 14 | 15 | data() { 16 | 17 | return { 18 | permalink: false, 19 | eleventyExcludeFromCollections: true 20 | }; 21 | 22 | } 23 | 24 | // process all files 25 | async render() { 26 | 27 | // destination already exists? 28 | try { 29 | let dir = await fsp.stat(dest); 30 | if (dir.isDirectory()) return true; 31 | } 32 | catch(e){} 33 | 34 | // process images 35 | console.log('optimizing images'); 36 | 37 | await imagemin(['src/images/*', '!src/images/*.js'], { 38 | destination: dest, 39 | plugins 40 | }); 41 | 42 | return true; 43 | 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/scss/01-settings/_variables.scss: -------------------------------------------------------------------------------- 1 | @mixin themelight { 2 | --color-text1: #222; 3 | --color-back1: #f1faee; 4 | --color-back2: #a8dadc; 5 | --color-back3: #457b9d; 6 | --color-back4: #1d3557; 7 | } 8 | 9 | @mixin themedark { 10 | --color-text1: #ddd; 11 | --color-back1: #1d3557; 12 | --color-back2: #457b9d; 13 | --color-back3: #a8dadc; 14 | --color-back4: #f1faee; 15 | } 16 | 17 | :root { 18 | 19 | // fonts 20 | --font-main: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 21 | --font-size: clamp(1rem, calc(1rem + 0.5vw), 2.5rem); 22 | --font-weight: 300; 23 | --font-bold: 600; 24 | --line-height: 1.5; 25 | 26 | // colors: light theme (default) 27 | @include themelight; 28 | 29 | // dark theme (CSS) 30 | @media (prefers-color-scheme: dark) { 31 | @include themedark; 32 | } 33 | 34 | } 35 | 36 | // colors: user set 37 | body.light { 38 | @include themelight; 39 | } 40 | 41 | body.dark { 42 | @include themedark; 43 | } 44 | -------------------------------------------------------------------------------- /src/_includes/page.njk: -------------------------------------------------------------------------------- 1 | {% include "partials/htmlhead.njk" %} 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 |
decoration
14 | 15 |
16 | 17 |
18 | {% block content %} 19 | 20 |

{{ title }}

21 | 22 | {{ content | safe }} 23 | 24 | {% include "partials/pagelist.njk" %} 25 | 26 | {% endblock %} 27 |
28 | 29 | 38 | 39 | {% include "partials/htmlfoot.njk" %} 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Craig Buckler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "11ty-starter", 3 | "version": "1.0.0", 4 | "description": "An 11ty starter website", 5 | "main": ".eleventy.js", 6 | "scripts": { 7 | "start": "npm run clean && clear && ELEVENTY_ENV=development npx eleventy --serve", 8 | "production": "npm run clean && npx eleventy", 9 | "debug": "DEBUG=Eleventy* npx @11ty/eleventy", 10 | "clean": "rm -rf build" 11 | }, 12 | "keywords": [ 13 | "11ty", 14 | "website", 15 | "starter", 16 | "template" 17 | ], 18 | "author": "Craig Buckler", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "@11ty/eleventy": "^0.11.0", 22 | "@11ty/eleventy-navigation": "^0.1.6", 23 | "@11ty/eleventy-plugin-rss": "^1.0.9", 24 | "cssnano": "^4.1.10", 25 | "html-minifier": "^4.0.0", 26 | "imagemin": "^7.0.1", 27 | "imagemin-mozjpeg": "^9.0.0", 28 | "imagemin-pngquant": "^9.0.1", 29 | "imagemin-svgo": "^8.0.0", 30 | "inline-source": "^7.2.0", 31 | "postcss": "^7.0.32", 32 | "postcss-advanced-variables": "^3.0.1", 33 | "postcss-nested": "^4.2.3", 34 | "postcss-scss": "^2.1.1", 35 | "rollup": "^2.26.11", 36 | "rollup-plugin-terser": "^7.0.2" 37 | }, 38 | "dependencies": {} 39 | } 40 | -------------------------------------------------------------------------------- /src/scss/03-elements/_primary.scss: -------------------------------------------------------------------------------- 1 | // default element styles 2 | html { 3 | font-size: var(--font-size); 4 | } 5 | 6 | body { 7 | font-family: var(--font-main); 8 | font-size: 1rem; 9 | @include reset; 10 | color: var(--color-text1); 11 | background-color: var(--color-back1); 12 | } 13 | 14 | h1, h2 { 15 | font-size: 2rem; 16 | margin: 0; 17 | } 18 | 19 | h2 { 20 | font-size: 1.6rem; 21 | margin-block-start: 1em; 22 | } 23 | 24 | p { 25 | margin-block: 1rem 0; 26 | } 27 | 28 | h1 + p, h2 + p { 29 | margin-block-start: 0; 30 | } 31 | 32 | svg { 33 | display: inline-block; 34 | vertical-align: middle; 35 | height: 3rem; 36 | margin-inline: 4px; 37 | fill: var(--color-back4); 38 | } 39 | 40 | a { 41 | 42 | position: relative; 43 | 44 | &::before { 45 | content: ""; 46 | position: absolute; 47 | bottom: 2px; 48 | left: 0; 49 | right: 0; 50 | height: 2px; 51 | background-color: var(--color-back4); 52 | transform-origin: 0; 53 | transform: scaleX(0); 54 | transition: transform 0.3s ease-in; 55 | } 56 | 57 | &:link, &:visited { 58 | font-weight: var(--font-bold); 59 | text-decoration: none; 60 | color: var(--color-back3); 61 | } 62 | 63 | &:hover, &:active, &:focus { 64 | color: var(--color-back4); 65 | 66 | &::before { 67 | transform: scaleX(1); 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /lib/shortcodes/navlist.js: -------------------------------------------------------------------------------- 1 | // generates a page navigation list 2 | const 3 | listType = 'ul', 4 | elementActive = 'strong', 5 | classActive = 'active', 6 | classOpen = 'open'; 7 | 8 | // pass in collections.all | eleventyNavigation, (current) page, and maximum depth level 9 | module.exports = (pageNav, page, maxLevel = 999) => { 10 | 11 | function navRecurse(entry, level = 1) { 12 | 13 | let childPages = ''; 14 | 15 | if (level < maxLevel) { 16 | for (let child of entry.children) { 17 | childPages += navRecurse(child, level++); 18 | } 19 | } 20 | 21 | let 22 | active = (entry.url === page.url), 23 | classList = []; 24 | 25 | if ((active && childPages) || childPages.includes(`<${ elementActive }>`)) classList.push(classOpen); 26 | if (active) classList.push(classActive); 27 | 28 | return ( 29 | '' + 32 | (active ? `<${ elementActive }>` : ``) + 33 | entry.title + 34 | (active ? `` : '') + 35 | (childPages ? `<${ listType }>${ childPages }` : '') + 36 | '' 37 | ); 38 | 39 | } 40 | 41 | let nav = ''; 42 | for (let entry of pageNav) { 43 | nav += navRecurse(entry); 44 | } 45 | 46 | return `<${ listType }>${ nav }`; 47 | 48 | }; 49 | -------------------------------------------------------------------------------- /src/js/javascript.11ty.js: -------------------------------------------------------------------------------- 1 | // JavaScript processing 2 | 3 | /* global dev */ 4 | 5 | const 6 | jsMain = 'js/main.js', 7 | 8 | rollup = require('rollup'), 9 | terser = require('rollup-plugin-terser').terser, 10 | 11 | inputOpts = { 12 | input: './src/' + jsMain 13 | }, 14 | 15 | outputOpts = { 16 | format: 'es', 17 | sourcemap: dev, 18 | plugins: [ 19 | terser({ 20 | mangle: { 21 | toplevel: true 22 | }, 23 | compress: { 24 | drop_console: !dev, 25 | drop_debugger: !dev 26 | }, 27 | output: { 28 | quote_style: 1 29 | } 30 | }) 31 | ] 32 | } 33 | ; 34 | 35 | 36 | module.exports = class { 37 | 38 | data() { 39 | 40 | return { 41 | permalink: jsMain, 42 | eleventyExcludeFromCollections: true 43 | }; 44 | 45 | } 46 | 47 | // PostCSS processing 48 | async render() { 49 | 50 | const 51 | bundle = await rollup.rollup(inputOpts), 52 | { output } = await bundle.generate(outputOpts), 53 | out = output.length && output[0]; 54 | 55 | let code = ''; 56 | if (out) { 57 | 58 | // JS code 59 | code = out.code; 60 | 61 | // inline source map 62 | if (out.map) { 63 | let b64 = new Buffer.from(out.map.toString()); 64 | code += '//# sourceMappingURL=data:application/json;base64,' + b64.toString('base64'); 65 | } 66 | 67 | } 68 | 69 | return code; 70 | 71 | } 72 | }; 73 | -------------------------------------------------------------------------------- /src/scss/05-components/_header.scss: -------------------------------------------------------------------------------- 1 | // page header 2 | header { 3 | 4 | background-color: var(--color-back2); 5 | 6 | .logo { 7 | 8 | display: inline-block; 9 | font-size: 2rem; 10 | letter-spacing: -0.1vw; 11 | @include reset; 12 | margin-inline-start: 0.5rem; 13 | 14 | a { 15 | color: var(--color-back4); 16 | 17 | &::before { 18 | display: none; 19 | } 20 | } 21 | 22 | } 23 | 24 | nav { 25 | 26 | float: right; 27 | margin-inline-end: 1rem; 28 | margin-block-end: 1rem; 29 | 30 | ul { 31 | display: flex; 32 | gap: 1.5rem; 33 | @include resetlist; 34 | 35 | a, strong { 36 | position: relative; 37 | display: block; 38 | padding-block-start: 1rem; 39 | color: var(--color-back4); 40 | } 41 | 42 | strong::after { 43 | content: ""; 44 | position: absolute; 45 | left: calc(50% - 5px); 46 | bottom: -10px; 47 | width: 0; 48 | height: 0; 49 | border-width: 5px; 50 | border-style: solid; 51 | border-color: transparent; 52 | border-top-color: var(--color-back4); 53 | } 54 | } 55 | } 56 | 57 | figure { 58 | width: 100%; 59 | @include reset; 60 | border-top: 3px solid var(--color-back4); 61 | border-bottom: 3px solid var(--color-back4); 62 | overflow: hidden; 63 | 64 | img { 65 | height: 35vh; 66 | object-fit: cover; 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /.eleventy.js: -------------------------------------------------------------------------------- 1 | // 11ty configuration 2 | const 3 | dev = global.dev = (process.env.ELEVENTY_ENV === 'development'), 4 | now = new Date(); 5 | 6 | module.exports = config => { 7 | 8 | /* --- PLUGINS --- */ 9 | 10 | // navigation 11 | config.addPlugin( require('@11ty/eleventy-navigation') ); 12 | 13 | 14 | /* --- TRANSFORMS -- */ 15 | 16 | // inline assets 17 | config.addTransform('inline', require('./lib/transforms/inline')); 18 | 19 | // minify HTML 20 | config.addTransform('htmlminify', require('./lib/transforms/htmlminify')); 21 | 22 | // CSS processing 23 | config.addTransform('postcss', require('./lib/transforms/postcss')); 24 | 25 | 26 | /* --- FILTERS --- */ 27 | 28 | // format dates 29 | const dateformat = require('./lib/filters/dateformat'); 30 | config.addFilter('datefriendly', dateformat.friendly); 31 | config.addFilter('dateymd', dateformat.ymd); 32 | 33 | // format word count and reading time 34 | config.addFilter('readtime', require('./lib/filters/readtime')); 35 | 36 | 37 | /* --- SHORTCODES --- */ 38 | 39 | // page navigation 40 | config.addShortcode('navlist', require('./lib/shortcodes/navlist.js')); 41 | 42 | 43 | /* --- CUSTOM COLLECTIONS --- */ 44 | 45 | // post collection (in src/articles) 46 | config.addCollection('post', collection => 47 | 48 | collection 49 | .getFilteredByGlob('./src/articles/*.md') 50 | .filter(p => dev || (!p.data.draft && p.date <= now)) 51 | 52 | ); 53 | 54 | 55 | /* --- WATCH FOLDERS --- */ 56 | 57 | config.addWatchTarget('./src/scss/'); 58 | config.addWatchTarget('./src/js/'); 59 | 60 | 61 | // 11ty defaults 62 | return { 63 | 64 | dir: { 65 | input: 'src', 66 | output: 'build' 67 | } 68 | 69 | }; 70 | }; 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 11ty-starter 2 | 3 | An example static site which uses [Eleventy](https://www.11ty.dev/) as a complete build system for HTML, CSS, JavaScript, and image optimization. 4 | 5 | The following Eleventy features are demonstrated: 6 | 7 | * markdown content and front-matter 8 | * custom Nunchucks templates 9 | * pages and article posts 10 | * draft and future articles 11 | * article indexes 12 | * filters for dates and word counts 13 | * a shortcode to generate navigation menus 14 | * transformations for HTML and CSS 15 | * image optimization 16 | * PostCSS SCSS processing 17 | * Rollup.js JavaScript processing 18 | 19 | 20 | ## Installation 21 | 22 | Ensure Node.js v12 or above is installed, clone the repository, and install all modules: 23 | 24 | ```sh 25 | git clone https://github.com/craigbuckler/11ty-starter 26 | cd 11ty-starter 27 | npm i 28 | ``` 29 | 30 | 31 | ## Development mode build 32 | 33 | Set `ELEVENTY_ENV` to `development` to enable development mode on Linux/macOS: 34 | 35 | ```sh 36 | ELEVENTY_ENV=development 37 | ``` 38 | 39 | Windows cmd: 40 | 41 | ```sh 42 | set ELEVENTY_ENV=development 43 | ``` 44 | 45 | Or Windows Powershell: 46 | 47 | ```sh 48 | $env:ELEVENTY_ENV="development" 49 | ``` 50 | 51 | Launch the Eleventy build process and server: 52 | 53 | ```sh 54 | npx eleventy --serve 55 | ``` 56 | 57 | Navigate to in your browser. 58 | 59 | 60 | ## Production mode build 61 | 62 | Set `ELEVENTY_ENV` to `production` to enable production mode on Linux/macOS: 63 | 64 | ```sh 65 | ELEVENTY_ENV=production 66 | ``` 67 | 68 | Windows cmd: 69 | 70 | ```sh 71 | set ELEVENTY_ENV=production 72 | ``` 73 | 74 | Or Windows Powershell: 75 | 76 | ```sh 77 | $env:ELEVENTY_ENV="production" 78 | ``` 79 | 80 | Run the Eleventy build process: 81 | 82 | ```sh 83 | npx eleventy 84 | ``` 85 | 86 | The files generated in the `build` folder can be uploaded to any web host. 87 | --------------------------------------------------------------------------------