1<!doctype html> 2<html lang="en"> 3<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Get started</title><base href="/get-started/">
3<script><!-- inlined for all pages --> 4(()=>{var p=Object.defineProperty;var u=(c,t,e)=>t in c?p(c,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):c[t]=e;var s=(c,t,e)=>u(c,typeof t!="symbol"?t+"":t,e);var r=class r extends HTMLElement{constructor(){super(),this.ready=new Promise(t=>{this.readyResolve=t})}static getParents(t,e=!1){let a=[];for(;t;){if(t.matches&&t.matches(r.tagName)){if(e&&t===e)break;d.hasConditions(t)&&a.push(t)}t=t.parentNode}return a}static async ready(t,e){if(e||(e=r.getParents(t)),e.length===0)return;let a=await Promise.all(e.map(i=>i.wait()));if(a.length)return a[0]}forceFallback(){window.Island&&Object.assign(r.fallback,window.Island.fallback);for(let t in r.fallback){let e=Array.from(this.querySelectorAll(t)).reverse();for(let a of e){if(!a.isConnected)continue;let i=r.getParents(a);if(i.length===1){let o=r.ready(a,i);r.fallback[t](o,a,r.prefix)}}}}wait(){return this.ready}async connectedCallback(){d.hasConditions(this)&&this.forceFallback(),await this.hydrate()}getTemplates(){return this.querySelectorAll(`template[${r.attr.template}]`)}replaceTemplates(t){for(let e of t){if(r.getParents(e,this).length>0)continue;let a=e.getAttribute(r.attr.template);if(a==="replace"){let i=Array.from(this.childNodes);for(let o of i)this.removeChild(o);this.appendChild(e.content);break}else{let i=e.innerHTML;if(a==="once"&&i){if(r.onceCache.has(i)){e.remove();return}r.onceCache.set(i,!0)}e.replaceWith(e.content)}}}async hydrate(){let t=[];this.parentNode&&t.push(r.ready(this.parentNode));let e=d.getConditions(this);for(let a in e)d.map[a]&&t.push(d.map[a](e[a],this));await Promise.all(t),this.replaceTemplates(this.getTemplates());for(let a of r.onReady.values())await a.call(this,r);this.readyResolve(),this.setAttribute(r.attr.ready,""),this.querySelectorAll(`[${r.attr.defer}]`).forEach(a=>a.removeAttribute(r.attr.defer))}};s(r,"tagName","is-land"),s(r,"prefix","is-land--"),s(r,"attr",{template:"data-island",ready:"ready",defer:"defer-hydration"}),s(r,"onceCache",new Map),s(r,"onReady",new Map),s(r,"fallback",{":not(is-land,:defined,[defer-hydration])":(t,e,a)=>{let i=document.createElement(a+e.localName);for(let n of e.getAttributeNames())i.setAttribute(n,e.getAttribute(n));let o=e.shadowRoot;if(!o){let n=e.querySelector(":scope > template:is([shadowrootmode], [shadowroot])");if(n){let m=n.getAttribute("shadowrootmode")||n.getAttribute("shadowroot")||"closed";o=e.attachShadow({mode:m}),o.appendChild(n.content.cloneNode(!0))}}return o&&i.attachShadow({mode:o.mode}).append(...o.childNodes),i.append(...e.childNodes),e.replaceWith(i),t.then(()=>{i.shadowRoot&&e.shadowRoot.append(...i.shadowRoot.childNodes),e.append(...i.childNodes),i.replaceWith(e)})}});var f=r,l=class l{static hasConditions(t){return Object.keys(l.getConditions(t)).length>0}static getConditions(t){let e={};for(let a of Object.keys(l.map))t.hasAttribute(`on:${a}`)&&(e[a]=t.getAttribute(`on:${a}`));return e}static visible(t,e){if("IntersectionObserver"in window)return new Promise(a=>{let i=new IntersectionObserver(o=>{let[n]=o;n.isIntersecting&&(i.unobserve(n.target),a())});i.observe(e)})}static idle(){let t=new Promise(e=>{document.readyState!=="complete"?window.addEventListener("load",()=>e(),{once:!0}):e()});return"requestIdleCallback"in window?Promise.all([new Promise(e=>{requestIdleCallback(()=>{e()})}),t]):t}static interaction(t,e){let a=["click","touchstart"];return t&&(a=(t||"").split(",").map(i=>i.trim())),new Promise(i=>{function o(n){i();for(let m of a)e.removeEventListener(m,o)}for(let n of a)e.addEventListener(n,o,{once:!0})})}static media(t){let e={matches:!0};if(t&&"matchMedia"in window&&(e=window.matchMedia(t)),!e.matches)return new Promise(a=>{e.addListener(i=>{i.matches&&a()})})}static saveData(t){if(!(!("connection"in navigator)||navigator.connection.saveData===(t!=="false")))return new Promise(()=>{})}};s(l,"map",{visible:l.visible,idle:l.idle,interaction:l.interaction,media:l.media,"save-data":l.saveData});var d=l;"customElements"in window&&(window.customElements.define(f.tagName,f),window.Island=f);var y=f.ready;var h=class{};s(h,"attr",{autoInitType:"autoinit",import:"import"}),s(h,"types",{"petite-vue":function(t){t.createApp().mount(this)},vue:function(t){t.createApp().mount(this)},svelte:function(t){new t.default({target:this})},"svelte-ssr":function(t){new t.default({target:this,hydrate:!0})},preact:function(t){t.default(this)}});Island.onReady.set("import-autoinit",async function(c){let t,e=this.getAttribute(h.attr.import);
4if(e&&(t=await import(e)),t){let a=h.types[this.getAttribute(h.attr.autoInitType)||e];a&&await a.call(this,t)}});})(); 5 6(()=>{var i="theme-preference",l={dark:"#2e2e2e",light:"#f8f8f8"},t={value:s()};window.addEventListener("load",()=>{let e=document.querySelector("#light-theme-toggle"),r=document.querySelector("#dark-theme-toggle");document.querySelector("[data-theme-switcher]")&&(o(),a(),e.addEventListener("click",()=>n("light")),r.addEventListener("click",()=>n("dark")),e.setAttribute("aria-pressed",t.value==="light"),r.setAttribute("aria-pressed",t.value==="dark"))});window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",({matches:e})=>{if(d())return;t.value=e?"dark":"light",o(),a();let r=document.querySelector("#light-theme-toggle"),c=document.querySelector("#dark-theme-toggle");r?.setAttribute("aria-pressed",t.value==="light"),c?.setAttribute("aria-pressed",t.value==="dark")});function n(e){t.value=e,document.querySelector("#light-theme-toggle").setAttribute("aria-pressed",e==="light"),document.querySelector("#dark-theme-toggle").setAttribute("aria-pressed",e==="dark"),u(),a()}function d(){try{return localStorage.getItem(i)}catch{return null}}function s(){return d()||(window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light")}function u(){try{localStorage.setItem(i,t.value)}catch{}o(),a()}function o(){document.firstElementChild.setAttribute("data-theme",t.value)}function a(){let e=document.querySelector('meta[name="theme-color"]'),r=t.value==="dark"?l.dark:l.light;e.setAttribute("content",r)}o();})();</script>
6<script type="application/ld+json">{ 7 "@context": "https://schema.org", 8 "@graph": [ 9 { 10 "@type": "WebSite", 11 "@id": "https://eleventy-excellent.netlify.app#website", 12 "url": "https://eleventy-excellent.netlify.app", 13 "name": "Eleventy Excellent", 14 "description": "Eleventy starter for building modern, resilient websites", 15 "inLanguage": "en_EN" 16 } 17 ] 18 }</script>
18<style>@layer tailwindBase{.transform{--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1}.filter{--tw-blur:;--tw-brightness:;--tw-contrast:;--tw-grayscale:;--tw-hue-rotate:;--tw-invert:;--tw-saturate:;--tw-sepia:;--tw-drop-shadow:}}@layer reset{*,:after,:before{box-sizing:border-box}*{text-wrap:pretty}h1,h2,h3,h4{text-wrap:balance}html:focus-within{scroll-behavior:smooth}body{min-height:100vh;min-height:100dvh;text-rendering:optimizeSpeed;line-height:1.5}blockquote,body,dd,dl,figure,h1,h2,h3,h4,p{margin:0}ol[role=list],ul[role=list]{list-style:none}[role=list]{padding:0}a{-webkit-text-decoration-skip:ink;text-decoration-skip-ink:auto;color:currentColor}canvas,img,picture,svg{max-inline-size:100%;block-size:auto;vertical-align:middle;font-style:italic;background-repeat:no-repeat;background-size:cover;shape-margin:.75rem}picture{display:block}button{all:unset}button,input,select,textarea{font:inherit;color:inherit}textarea{resize:vertical;resize:block}textarea:not([rows]){min-height:10em}[role=button],[role=option],button,label,select,summary{cursor:pointer}:target{scroll-margin-block-start:2ex}:focus{scroll-margin-block-end:8vh}dialog{max-width:unset;max-height:unset}[popover],dialog{border:none;background:none;inset:unset}[popover]{color:inherit}[popover]:not(:popover-open),dialog:not([open],[popover]){display:none!important}html:has(dialog[open]:modal){overflow:hidden}@media (prefers-reduced-motion:reduce){:focus-within{scroll-behavior:auto}*,:after,:before{animation-duration:.01ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.01ms!important}}@view-transition{navigation:auto}}@layer fonts{@font-face{font-family:Atkinson Hyperlegible;font-style:normal;font-display:swap;font-weight:400;src:local(""),url(/assets/fonts/atkinson/atkinson-hyperlegible-regular.woff2) format("woff2")}@font-face{font-family:Atkinson Hyperlegible;font-style:italic;font-display:swap;font-weight:400;src:local(""),url(/assets/fonts/atkinson/atkinson-hyperlegible-italic.woff2) format("woff2")}@font-face{font-family:Atkinson Hyperlegible;font-style:normal;font-display:swap;font-weight:700;src:local(""),url(/assets/fonts/atkinson/atkinson-hyperlegible-bold.woff2) format("woff2")}@font-face{font-family:Redhat;font-style:normal;font-display:swap;font-weight:900;src:local(""),url(/assets/fonts/redhat/red-hat-display-v7-latin-900.woff2) format("woff2")}}@layer tailwindComponents{:root{--color-gray-100:#f8f8f8;--color-gray-200:#e4e4e4;--color-gray-300:#aeaeae;--color-gray-400:gray;--color-gray-500:#636363;--color-gray-600:#484848;--color-gray-700:#3a3a3a;--color-gray-800:#2e2e2e;--color-gray-900:#161616;--color-pink:#dd4462;--color-pink-subdued:#ce5769;--color-blue:#4467dd;--color-blue-subdued:#4d6cc8;--color-gold:#fbbe25;--color-gold-subdued:#f1c15b;--color-theme-light:var(--color-light);--color-theme-dark:var(--color-dark);--color-theme-mid:var(--color-mid);--color-theme-text:var(--color-text);
18--color-theme-text-accent:var(--color-text-accent);--color-theme-bg:var(--color-bg);--color-theme-bg-accent:var(--color-bg-accent);--color-theme-primary:var(--color-primary);--color-theme-secondary:var(--color-secondary);--color-theme-tertiary:var(--color-tertiary);--border-radius-small:0.1875rem;--border-radius-medium:0.3rem;--space-3xs:clamp(.125rem, .11rem + .1vw, .1875rem);--space-2xs:clamp(.1875rem, .15rem + .19vw, .3125rem);--space-xs:clamp(.3125rem, .27rem + .19vw, .4375rem);--space-s:clamp(.625rem, .55rem + .38vw, .875rem);--space-m:clamp(.875rem, .74rem + .67vw, 1.3125rem);--space-l:clamp(1.1875rem, .96rem + 1.15vw, 1.9375rem);--space-xl:clamp(2.375rem, 2.03rem + 1.73vw, 3.5rem);--space-2xl:clamp(3.5625rem, 3.04rem + 2.6vw, 5.25rem);--space-3xl:clamp(5.9375rem, 5.07rem + 4.33vw, 8.75rem);--space-xs-s:clamp(.3125rem, .14rem + .87vw, .875rem);--space-s-m:clamp(.625rem, .41rem + 1.06vw, 1.3125rem);--space-m-l:clamp(.875rem, .55rem + 1.63vw, 1.9375rem);--space-l-xl:clamp(1.1875rem, .48rem + 3.56vw, 3.5rem);--space-l-2xl:clamp(1.1875rem, -.06rem + 6.25vw, 5.25rem);--space-xl-2xl:clamp(2.375rem, 1.49rem + 4.42vw, 5.25rem);--space-2xl-3xl:clamp(3.5625rem, 1.97rem + 7.98vw, 8.75rem);--size-step-min-2:clamp(.8125rem, .75rem + .29vw, 1rem);--size-step-min-1:clamp(1rem, .88rem + .58vw, 1.375rem);--size-step-0:clamp(1.1875rem, 1.01rem + .87vw, 1.75rem);--size-step-1:clamp(1.4375rem, 1.21rem + 1.15vw, 2.1875rem);--size-step-2:clamp(1.6875rem, 1.36rem + 1.63vw, 2.75rem);--size-step-3:clamp(2.0625rem, 1.64rem + 2.12vw, 3.4375rem);--size-step-4:clamp(2.5rem, 1.96rem + 2.69vw, 4.25rem);--size-step-5:clamp(2.9375rem, 2.19rem + 3.75vw, 5.375rem);--size-step-6:clamp(3.5rem, 2.52rem + 4.9vw, 6.6875rem);--leading-flat:1;--leading-fine:1.2;--leading-standard:1.4;--font-display:Redhat,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;--font-base:Atkinson Hyperlegible,system-ui,sans-serif;--font-mono:ui-monospace,Cascadia Code,Source Code Pro,Menlo,Consolas,DejaVu Sans Mono,monospace;--font-regular:400;--font-bold:700;--font-extra-bold:900}}@layer variables{:root{--gutter:var(--space-m-l);--transition-duration:250ms;--transition-timing:ease;--wrapper-width:85rem;--tracking:-0.04ch;--tracking-s:-0.075ch;--tracking-wide:0.04ch;--stroke:1px solid var(--color-bg-accent);--gradient-rainbow:linear-gradient(90deg, #cf4662 10%, #fbbe25 30%, #62c95c 50%, #08bccb 75%, #a977d5 90%);--gradient-conic:conic-gradient( 19 var(--color-primary) 0 28%, 20 var(--color-secondary) 0 67%, 21 var(--color-tertiary) 0 100% 22 );--gradient-stripes:linear-gradient( 23 45deg, 24 var(--color-gray-900) 0 75%, 25 var(--color-primary) 0 85%, 26 var(--color-secondary) 0 92%, 27 var(--color-tertiary) 0 100% 28 );--color-light:var(--color-gray-100);--color-dark:var(--color-gray-900);--color-mid:var(--color-gray-400)}:root,:root[data-theme=light]{--color-text:var(--color-gray-800);--color-bg:var(--color-gray-100);--color-primary:var(--color-pink);--color-secondary:var(--color-blue);--color-tertiary:var(--color-gold);
28--color-text-accent:var(--color-gray-700);--color-bg-accent:var(--color-gray-200);--color-bg-accent-2:var(--color-gray-300)}@media (prefers-color-scheme:dark){:root{--color-text:var(--color-gray-100);--color-bg:var(--color-gray-800);--color-primary:var(--color-pink-subdued);--color-secondary:var(--color-blue-subdued);--color-tertiary:var(--color-gold-subdued);--color-text-accent:var(--color-gray-300);--color-bg-accent:var(--color-gray-700);--color-bg-accent-2:var(--color-gray-600)}}:root[data-theme=dark]{--color-text:var(--color-gray-100);--color-bg:var(--color-gray-800);--color-primary:var(--color-pink-subdued);--color-secondary:var(--color-blue-subdued);--color-tertiary:var(--color-gold-subdued);--color-text-accent:var(--color-gray-300);--color-bg-accent:var(--color-gray-700);--color-bg-accent-2:var(--color-gray-600)}}@layer global{html{color-scheme:light dark}body{display:flex;flex-direction:column;font-family:var(--font-base);font-size:var(--size-step-0);font-weight:var(--font-regular);font-size-adjust:from-font;line-height:var(--leading-standard);color:var(--color-text);background-color:var(--color-bg);accent-color:var(--color-primary);letter-spacing:var(--tracking)}main{flex:auto}h1,h2,h3{font-family:var(--font-display);font-weight:var(--font-extra-bold);line-height:var(--leading-fine);letter-spacing:var(--tracking-s)}h1{font-size:var(--size-step-6)}h2{font-size:var(--size-step-4)}blockquote,h3{font-size:var(--size-step-2)}blockquote{padding:var(--space-m-l);border-inline-start:.5rem solid var(--color-primary)}blockquote>*+*{margin-block-start:var(--space-m-l)}blockquote :last-child{font-family:var(--font-base);font-style:normal;font-size:var(--size-step-1)}input,textarea{caret-color:var(--color-primary)}svg{block-size:.6lh;inline-size:auto;flex:none}b,strong{font-weight:var(--font-bold)}hr{border:none;height:1px;width:10%;margin-block:var(--flow-space,var(--space-m-l));margin-inline-start:0;background-color:var(--color-bg-accent-2)}figcaption{margin-block-start:var(--space-s);font-size:var(--size-step-min-1);text-align:center;padding-block-end:var(--space-xs)}figcaption:after{border-block:var(--stroke);content:"";display:block;margin-block:var(--space-xs);inline-size:50%;margin-inline:auto;opacity:.8}a{text-decoration-thickness:.15ex;text-underline-offset:.2ch}a:not([class]):hover{text-decoration:none}:focus{outline:none}:focus-visible{outline:3px solid var(--focus-color,currentColor);outline-offset:var(--focus-offset,.3ch)}@supports (-moz-appearance:none){:root{--focus-offset:0.08em}}::-moz-selection{background-color:var(--color-text);color:var(--color-bg)}::selection{background-color:var(--color-text);color:var(--color-bg)}.preload-transitions *{transition:none!important}@media (scripting:none){.require-js{display:none}}@media (prefers-reduced-motion:no-preference){@view-transition{navigation:auto}}}@layer compositions{.cluster{display:flex;flex-direction:var(--cluster-direction,row);flex-wrap:var(--cluster-wrap,wrap);gap:var(--gutter,var(--space-s-l));justify-content:var(--cluster-horizontal-alignment,flex-start);align-items:var(--cluster-vertical-alignment,center)}.flow>*+*{margin-block-start:var(--flow-space,1em)}.grid{display:grid;grid-template-columns:repeat(var(--grid-placement,auto-fill),minmax(var(--grid-min-item-size,16rem),1fr));gap:var(--gutter,var(--space-s-m))}.grid[data-rows=masonry]{grid-template-rows:masonry;align-items:start}.grid[data-layout="50-50"]{--grid-placement:auto-fit;--grid-min-item-size:clamp(16rem, 50vw, 28rem)}.grid[data-layout=thirds]{--grid-placement:auto-fit;--grid-min-item-size:clamp(16rem, 33%, 20rem)}.repel{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:var(--repel-vertical-alignment,center);gap:var(--gutter,var(--space-s-l))}.repel[data-nowrap]{flex-wrap:nowrap}.sidebar{display:flex;flex-wrap:wrap;gap:var(--gutter,var(--space-s-l))}.sidebar:not([data-direction])>:first-child{flex-basis:var(--sidebar-target-width,20rem);flex-grow:1}.sidebar:not([data-direction])>:last-child{flex-basis:0;flex-grow:999;min-inline-size:var(--sidebar-content-min-width,50%)}.sidebar[data-direction=rtl]>:last-child{flex-basis:var(--sidebar-target-width,20rem);flex-grow:1}.sidebar[data-direction=rtl]>:first-child{flex-basis:0;flex-grow:999;min-inline-size:var(--sidebar-content-min-width,50%)}.wrapper{--gap:clamp(1rem, 6vw, 3rem);--full:minmax(var(--gap), 1fr);--content:min(var(--wrapper-width, 85rem), 100% - 2 * var(--gap));--popout:minmax(0, 2rem);--feature:minmax(0, 5rem);display:grid;grid-template-columns:[full-start] var(--full) [feature-start] var(--feature) [popout-start] var(--popout) [content-start] var(--content) [content-end] var(--popout) [popout-end] var(--feature) [feature-end] var(--full) [full-end]}.wrapper>*{grid-column:content}.prose-wrapper{--wrapper-width:64rem}.popout{grid-column:popout}.feature{grid-column:feature}.full{grid-column:full}}@layer blocks{.button{--button-bg:var(--color-text);--button-color:color-mix(in oklab, var(--button-bg) 10%, var(--color-bg));--button-hover-bg:color-mix(in oklab, var(--button-bg) 90%, var(--color-bg));--button-hover-color:var(--button-color);--button-border-width:var(--border-thickness);--button-border-style:solid;--button-border-color:color-mix(in oklab, var(--button-bg) 80%, var(--color-text));--button-radius:var(--border-radius-small);--button-gap:var(--space-2xs);--button-padding:var(--space-xs) var(--space-m);--button-font-family:var(--font-body);--button-font-weight:var(--font-regular);--button-font-size:var(--size-step-0);--button-text-transform:none;--button-tracking:normal;display:inline-flex;align-items:center;gap:var(--button-gap);padding:var(--button-padding);background:var(--button-bg);color:var(--button-color);border-width:var(--button-border-width);border-style:var(--button-border-style);border-color:var(--button-border-color);border-radius:var(--button-radius);text-decoration:none;font-family:var(--button-font-family);font-weight:var(--button-font-weight);font-size:var(--button-font-size);line-height:var(--leading-flat);text-transform:var(--button-text-transform);letter-spacing:var(--button-tracking);text-box-trim:trim-both;text-box-edge:cap alphabetic}.button svg{block-size:var(--button-icon-size,1.2cap)}.button:hover,.button[aria-current=page],.button[aria-pressed=true],.button[data-state=active]{background:var(--button-hover-bg);color:var(--button-hover-color)}.button:active{transform:scale(99%)}.button[data-button-variant=primary]{--button-bg:var(--color-primary);--button-color:var(--color-light);--button-color:color-mix(in oklab, var(--color-primary) 5%, var(--color-light))}.button[data-button-variant=secondary]{--button-bg:var(--color-secondary);--button-color:var(--color-light);--button-color:color-mix(in oklab, var(--color-secondary) 5%, var(--color-light))}.button[data-button-variant=tertiary]{--button-bg:var(--color-tertiary);--button-color:var(--color-dark);--button-color:color-mix(in oklab, var(--color-tertiary) 10%, var(--color-dark))}.button[data-ghost-button]{--button-bg:var(--color-bg);--button-border-color:var(--color-text);--button-color:var(--color-text);--button-hover-color:var(--color-bg)}.button[data-ghost-button]:hover{--_ghost-hover-bg:var(--color-bg);--_ghost-hover-bg:color-mix(in oklab, var(--button-bg) 95%, var(--color-dark));background:var(--_ghost-hover-bg);color:var(--button-color)}.button[data-small-button]{--button-border-width:2px;--button-radius:var(--border-radius-small);--button-font-size:var(--size-step-min-2);--button-padding:var(--space-2xs) var(--space-s) var(--space-3xs) var(--space-s);--button-text-transform:uppercase;--button-tracking:var(--tracking-wide)}.button[data-button-radius=hard]{--button-radius:0}.button[data-icon]{--button-padding:var(--space-2xs);--button-bg:var(--color-bg);--button-border-color:var(--color-bg);--button-color:var(--color-text);--button-hover-color:var(--color-primary);--button-radius:50%;aspect-ratio:1;display:inline-grid;place-items:center}:root{--color-code-orange:#e09952;--color-code-blue:var(--color-secondary);--color-code-indigo:#7d59c5;--color-code-violet:#e052bf;--color-code-pink:#e05269;--color-code-gray:#949494;--color-code-bg:color-mix(in oklab, var(--color-bg) 92%, #000)}code,pre{font-family:var(--font-mono);font-size:var(--size-step-min-1);background-color:var(--color-code-bg);border-radius:var(--border-radius-medium)}code[class*=language-],pre[class*=language-]{white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;hyphens:none}pre{grid-column:popout!important;overflow-x:auto;padding:var(--space-s) var(--space-l)}
28:where(:not(pre))>code{position:relative;top:-.05em;padding:.1em .4em}pre[class*=language-]{overflow:auto;position:relative}[class*=language-] .namespace{opacity:.7}.token.atrule{color:var(--color-code-pink)}.token.attr-name{color:var(--color-code-orange)}.token.attr-value{color:var(--color-text-accent)}.token.attribute{color:var(--color-code-blue)}.token.boolean{color:var(--color-code-pink)}.token.builtin,.token.cdata,.token.char,.token.class,.token.class-name,.token.color{color:var(--color-code-orange)}.token.comment{color:var(--color-code-gray)}.token.constant,.token.deleted{color:var(--color-code-pink)}.token.doctype{color:var(--color-code-orange)}.token.entity,.token.function{color:var(--color-code-pink)}.token.hexcode{color:var(--color-code-orange)}.token.id,.token.important{color:var(--color-code-pink);font-weight:var(--font-bold)}.token.inserted{color:var(--color-code-orange)}.token.keyword{color:var(--color-code-pink);font-style:italic}.token.number{color:var(--color-text-accent)}.token.operator{color:var(--color-code-gray)}.token.prolog,.token.property{color:var(--color-code-orange)}.token.pseudo-class,.token.pseudo-element{color:var(--color-code-blue)}.token.punctuation{color:var(--color-code-gray)}.token.regex{color:var(--color-code-orange)}.token.selector{color:var(--color-code-pink)}.token.string{color:var(--color-text-accent)}.token.symbol,.token.tag,.token.unit{color:var(--color-code-pink)}.token.url{color:var(--color-code-violet)}.token.variable{color:var(--color-code-pink)}.codepen a{--icon-size:1.2em;display:flex;align-items:center;gap:var(--space-2xs)}.prose .cp_embed_wrapper,.prose .cp_embed_wrapper+script+:not(h2){--flow-space:var(--space-l)}.cp_embed_wrapper{grid-column:popout;position:relative;overflow:auto;display:grid;place-items:center;grid-template-areas:"container";resize:horizontal}.cp_embed_wrapper iframe{grid-area:container;width:100%}.indicator,a[href^=http]:not([href*="localhost:8080"]):not([href*="eleventy-excellent.netlify.app"]):not(.button):not(.no-indicator){padding-inline-end:.8em}.indicator:after,a[href^=http]:not([href*="localhost:8080"]):not([href*="eleventy-excellent.netlify.app"]):not(.no-indicator):after{position:absolute;display:inline-block;inline-size:1em;block-size:1em;background-image:url(/assets/images/template/external.svg);background-repeat:no-repeat;background-position:50%;background-size:60% auto;content:"(external link)";overflow:hidden;white-space:nowrap;text-indent:1em}.mainnav{--nav-list-timing-function:cubic-bezier(0.68, -0.55, 0.27, 1.55);position:var(--nav-position,absolute);inset-inline-end:0}.mainnav:has([data-drawer-toggle][aria-expanded=true]){--nav-position:fixed;inset-inline-end:var(--gap)}.mainnav ul{--gutter:var(--space-xs);--cluster-vertical-alignment:normal;--cluster-wrap:wrap;--cluster-direction:column;--nav-list-background:var(--color-bg);--nav-list-shadow:-5px 0 11px 0 rgba(0,0,0,.2);--nav-list-height:100dvh;--nav-list-padding-block:var(--space-2xl);--nav-list-padding-inline:var(--space-s);--nav-list-position:fixed;--nav-list-width:min(18rem, 100vw);--nav-list-visibility:hidden;--nav-list-opacity:0;background:var(--nav-list-background);box-shadow:var(--nav-list-shadow);block-size:var(--nav-list-height);list-style:none;margin:0;padding-block:var(--nav-list-padding-block);padding-inline:var(--nav-list-padding-inline);position:var(--nav-list-position);inset-block-start:0;inset-inline-end:0;inline-size:var(--nav-list-width);opacity:var(--nav-list-opacity);transition:opacity .3 var(--nav-list-timing-function),visibility .3s ease-in-out;visibility:var(--nav-list-visibility)}.mainnav ul[data-no-flash]{transition:none}@media (prefers-reduced-motion:no-preference){.mainnav>ul{--nav-list-transform:translateX(100%);--nav-list-opacity:1;transform:var(--nav-list-transform);transition:transform .5s var(--nav-list-timing-function),visibility .3s linear}.mainnav svg{transition:transform .4s var(--nav-list-timing-function)}}.mainnav [data-drawer-toggle]{--gutter:var(--space-2xs);--cluster-vertical-alignment:center;background:var(--color-bg);display:var(--nav-button-display,inline-flex);position:relative;z-index:2;padding:var(--space-2xs) 0;line-height:var(--leading-flat)}.mainnav [data-drawer-toggle][aria-expanded=true]+ul{--cluster-wrap:nowrap;--nav-list-visibility:visible;--nav-list-transform:translateX(0);--nav-list-opacity:1;overflow-y:auto}body:has([data-drawer-toggle][aria-expanded=true]){overflow:hidden}.mainnav span{font-weight:var(--font-bold);text-transform:uppercase;font-family:var(--font-display);font-size:var(--size-step-min-1)}.mainnav svg{block-size:.9em;color:var(--color-text);stroke-width:3}.mainnav [aria-expanded=true] svg{transform:rotate(45deg)}.mainnav :is(a,[data-submenu-toggle]){--nav-item-background:transparent;--nav-item-text-color:var(--color-text);--nav-item-padding:var(--space-xs) var(--space-2xs);--nav-item-decoration-color:transparent;--nav-item-font-size:var(--size-step-0);
28--nav-item-font-weight:var(--font-bold);background:var(--nav-item-background);color:var(--nav-item-text-color);font-size:var(--nav-item-font-size);padding:var(--nav-item-padding);display:block;border-radius:var(--border-radius-medium);text-decoration-line:underline;text-decoration-color:var(--nav-item-decoration-color);text-decoration-thickness:3px;text-underline-offset:.2em}.mainnav:has(.nav-sublist) :is(a,[data-submenu-toggle]){font-weight:var(--nav-item-font-weight)}.mainnav [data-submenu-toggle]{gap:var(--space-2xs);display:flex;inline-size:100%;align-items:center;justify-content:space-between}.mainnav [data-submenu-toggle] svg{margin-inline-end:calc(var(--gap) - var(--nav-list-padding-inline))}.mainnav :is(a,[data-submenu-toggle]):hover{--nav-item-background:transparent;--nav-item-text-color:var(--color-text);--nav-item-decoration-color:var(--color-bg-accent-2)}.mainnav [aria-current=page],.mainnav [data-state=active]{--nav-item-background:var(--color-bg);--nav-item-text-color:var(--color-primary);--nav-item-decoration-color:var(--color-primary)}li:has(ul a[aria-current=page])>[data-submenu-toggle]{--nav-item-background:var(--color-bg);--nav-item-text-color:var(--color-text);--nav-item-decoration-color:var(--color-text)}.mainnav [data-submenu-toggle][aria-expanded=false]+ul{display:none}.mainnav .nav-sublist{--gutter:0;--cluster-direction:column;--nav-sublist-position:relative;--nav-sublist-background:var(--color-bg);--nav-sublist-width:100%;--nav-list-visibility:visible;--nav-list-opacity:1;--nav-list-padding-block:0 var(--space-m);--nav-list-padding-inline:0;box-shadow:none;position:var(--nav-sublist-position);inline-size:var(--nav-sublist-width);block-size:auto;background:var(--nav-sublist-background);z-index:2}.mainnav .nav-sublist a{--nav-item-font-weight:var(--font-regular)}@media (min-width:662px){.mainnav{--nav-position:static;--nav-button-display:none}.mainnav :is(a,[data-submenu-toggle]){--nav-item-font-weight:var(--font-regular)}.mainnav ul{--cluster-direction:row;--nav-list-position:static;--nav-list-padding-block:0;--nav-list-padding-inline:0;--nav-list-height:auto;--nav-list-width:100%;--nav-list-shadow:none;--nav-list-visibility:visible;--nav-list-transform:translateX(0);--nav-list-opacity:1}.mainnav [aria-current=page],.mainnav [data-state=active],li:has(ul a[aria-current=page])>[data-submenu-toggle]{--nav-item-background:transparent;--nav-item-text-color:var(--color-primary);--nav-item-decoration-color:var(--color-primary)}.mainnav [data-submenu-toggle]{inline-size:auto}.mainnav [data-submenu-toggle] svg{margin-inline-end:0}.mainnav .nav-sublist{--nav-sublist-position:absolute;--nav-sublist-background:var(--color-bg);--nav-sublist-border:var(--color-text);--nav-sublist-width:max-content;--nav-list-padding-block:var(--space-xs);--nav-list-padding-inline:var(--space-xs);border:3px solid var(--nav-sublist-border);inset-block-start:var(--space-xl);inset-inline-start:var(--space-2xs)}}.mainnav ul:first-child{--cluster-direction:row;--nav-list-position:static;--nav-list-padding-block:0;--nav-list-padding-inline:0;--nav-list-height:auto;--nav-list-width:100%;--nav-list-shadow:none;--nav-list-visibility:visible;--nav-list-transform:translateX(0);--nav-list-opacity:1}.mainnav ul:first-child [aria-current=page],.mainnav ul:first-child [data-state=active]{--nav-item-background:transparent;--nav-item-text-color:var(--color-primary);--nav-item-decoration-color:var(--color-primary)}.mainnav:has(ul:first-child){--nav-position:relative}@media (scripting:none){.mainnav ul:first-child ul.nav-sublist{--cluster-direction:row;--cluster-wrap:wrap}@media (min-width:662px){.mainnav ul:first-child ul.nav-sublist{--cluster-direction:column}}}.prose{--flow-space:var(--space-m-l);--wrapper-width:64rem}.prose :is(pre,pre+*,figure,picture){--flow-space:var(--space-m-l)}.prose :is(figure+*,picture+*){--flow-space:var(--space-xl)}.prose :is(img,video){border:var(--stroke)}.prose :is(h2,h3,h4){--flow-space:1.5em;overflow-wrap:anywhere;hyphens:auto}@media (min-width:1000px){.prose :is(h1,h2,h3){overflow-wrap:unset;hyphens:unset}}
28.prose :is(h1,h2,h3,h4)+:not([class]):not(figure){--flow-space:var(--space-l)}.prose :is(p,li,dl,blockquote){max-inline-size:60ch;text-wrap:pretty}.prose .heading-anchor:where(:hover,:focus){text-decoration:none}.heading-anchor{text-decoration:none}.prose mark{background-color:var(--color-tertiary);color:var(--color-dark)}.prose :is(ul,ol):not([class]) li+li,.prose :is(ul,ol):not([class]) li>:is(ol,ul){padding-block-start:var(--space-s)}.prose :is(ul:not([class]):not([role=list])) li::marker{color:var(--color-primary);content:"â "}.prose ul:not([class]){padding-inline-start:1.3ch}.prose ol:not([class]){padding-inline-start:1.7ch}.prose ol li::marker{color:var(--color-primary)}.prose img{border-radius:var(--border-radius-medium)}@media (max-width:640px){.prose a,.prose>*{overflow-wrap:break-word;word-wrap:break-word;word-break:break-word;hyphens:auto}}.section>.divider:first-child{transform:rotate(180deg) translateY(-1px)}.section__inner{background-color:var(--spot-color,var(--color-bg-accent));color:var(--color-text)}.section blockquote{font-weight:var(--font-bold);line-height:1;font-size:var(--size-step-4);letter-spacing:var(--tracking-s)}.section :is(h1,h2,h3,blockquote){opacity:95%}.divider{display:block;block-size:3.5em;inline-size:100%;fill:var(--spot-color,var(--color-bg))}.divider>.divider{transform:translateY(-1px)}.site-footer{--cluster-horizontal-alignment:center}.site-footer a:not(.creator a){padding:var(--space-xs)}.site-footer .creator a:hover{color:transparent;background-image:var(--gradient-rainbow);background-size:100%;background-repeat:repeat;-webkit-background-clip:text;background-clip:text}.site-footer .creator a:hover svg{color:var(--color-primary)}.logo{--gutter:var(--space-xs);padding:var(--space-s) 0;font-size:var(--size-step-0);text-decoration:none}.skip-link{clip:rect(1px,1px,1px,1px);display:block;block-size:1px;overflow:hidden;position:absolute;inline-size:1px;top:1rem;left:1rem;z-index:999}.skip-link:focus{clip:auto;block-size:auto;overflow:visible;inline-size:auto;background-color:var(--color-text);color:var(--color-bg);padding:var(--space-xs) var(--space-s-m);border-radius:var(--border-radius-medium);line-height:1}.skip-link:not(:focus){border:0;clip:rect(0 0 0 0);block-size:auto;margin:0;overflow:hidden;padding:0;position:absolute;inline-size:1px;white-space:nowrap}.gradient-text{background-image:var(--gradient-conic);padding:.6rem 0;background-size:50%}.gradient-text,.gradient-text-linear{color:transparent;-webkit-background-clip:text;background-clip:text}.gradient-text-linear{background-image:var(--gradient-rainbow);background-size:100%;background-repeat:repeat}.theme-switch h2{font-size:var(--size-step-min-1);font-family:var(--font-base)}.theme-switch .button[aria-pressed=true]{--_color-contrast:var(--color-tertiary);--button-bg:var(--_color-contrast);--button-color:var(--color-dark);--button-hover-color:var(--color-dark);--button-border-color:var(--_color-contrast);--focus-color:var(--_color-contrast)}is-land:not(:defined) .theme-switch{display:none}}@layer tailwindUtilities{.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.my-m-l{margin-top:clamp(.875rem,.55rem + 1.63vw,1.9375rem);margin-bottom:clamp(.875rem,.55rem + 1.63vw,1.9375rem)}.my-s-m{margin-top:clamp(.625rem,.41rem + 1.06vw,1.3125rem);margin-bottom:clamp(.625rem,.41rem + 1.06vw,1.3125rem)}.mt-l-xl{margin-top:clamp(1.1875rem,.48rem + 3.56vw,3.5rem)}.mt-s-m{margin-top:clamp(.625rem,.41rem + 1.06vw,1.3125rem)}.mt-xl{margin-top:clamp(2.375rem,2.03rem + 1.73vw,3.5rem)}.mt-xs{margin-top:clamp(.3125rem,.27rem + .19vw,.4375rem)}.mt-xs-s{margin-top:clamp(.3125rem,.14rem + .87vw,.875rem)}.block{display:block}.inline{display:inline}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.resize{resize:both}.justify-center{justify-content:center}.bg-gray-100{background-color:#f8f8f8}.p-s-m{padding:clamp(.625rem,.41rem + 1.06vw,1.3125rem)}.text-center{text-align:center}.font-display{font-family:Redhat,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.text-step-0{font-size:clamp(1.1875rem,1.01rem + .87vw,1.75rem)}.text-step-1{font-size:clamp(1.4375rem,1.21rem + 1.15vw,2.1875rem)}
28.text-step-2{font-size:clamp(1.6875rem,1.36rem + 1.63vw,2.75rem)}.text-step-4{font-size:clamp(2.5rem,1.96rem + 2.69vw,4.25rem)}.text-step-min-1{font-size:clamp(1rem,.88rem + .58vw,1.375rem)}.capitalize{text-transform:capitalize}.italic{font-style:italic}.text-gray-900{color:#161616}.text-pink{color:#dd4462}.text-theme-light{color:var(--color-light)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.flow-space-m{--flow-space:clamp(.875rem, .74rem + .67vw, 1.3125rem)}.region-space-m{--region-space:clamp(.875rem, .74rem + .67vw, 1.3125rem)}.region-space-l{--region-space:clamp(1.1875rem, .96rem + 1.15vw, 1.9375rem)}.gutter-2xs{--gutter:clamp(.1875rem, .15rem + .19vw, .3125rem)}.gutter-xs{--gutter:clamp(.3125rem, .27rem + .19vw, .4375rem)}.gutter-s{--gutter:clamp(.625rem, .55rem + .38vw, .875rem)}.gutter-xs-s{--gutter:clamp(.3125rem, .14rem + .87vw, .875rem)}.spot-color-theme-bg{--spot-color:var(--color-bg)}.spot-color-theme-bg-accent{--spot-color:var(--color-bg-accent)}.spot-color-theme-primary{--spot-color:var(--color-primary)}.spot-color-theme-secondary{--spot-color:var(--color-secondary)}.grayscale{filter:grayscale(100%)}.heading-line{display:flex;align-items:flex-start;text-align:left}.heading-line:after{content:"";flex-grow:1;block-size:1px;background:var(--color-bg-accent-2);margin-block-start:auto;transform:translateY(-.25em);margin-left:var(--space-s)}.ontop{z-index:1;position:relative}.region{--region-space-fallback:var(--region-space, var(--space-l-xl));padding-block-start:var(--region-space-top,var(--region-space-fallback));padding-block-end:var(--region-space-bottom,var(--region-space-fallback))}.spin{animation:spin 30s linear infinite}@media (prefers-reduced-motion:no-preference){@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}}.visually-hidden{border:0;clip:rect(0 0 0 0);height:0;margin:0;overflow:hidden;padding:0;position:absolute;width:1px;white-space:nowrap}}@media (min-width:1000px){.md\:text-right{text-align:right}}</style><style>.details .control{--gutter:var(--space-xs-s);--cluster-horizontal-alignment:flex-end;border-block-start:var(--stroke);padding-block-start:var(--space-xs)}is-land:not(:defined) .details .control{display:none}.details details>*+*{margin-block-start:var(--flow-space,1em)}.details details[open]+details{margin-block-start:var(--space-2xl)}.details details>p:last-child{margin-block-end:var(--space-l)}.details summary{grid-column:content}.details summary,.details summary :is(h1,h2,h3){display:flex;align-items:center;line-height:1.2;gap:0 var(--space-m);cursor:pointer;list-style:none;font-weight:var(--font-bold);font-size:var(--size-step-2)}.details summary:focus-visible,.details summary:hover,.details[open]>summary{color:var(--color-primary)}.details summary:before{content:"+";display:block;color:var(--color-primary);font-family:var(--font-mono);font-size:var(--size-step-1);font-weight:var(--font-regular);line-height:.5;transform-origin:right bottom}.details details[open] summary:before{content:"â"}.details details>:is(h2,h3,h4){font-family:var(--font-body)}.details details>h2{font-size:var(--size-step-1)}.details details>h3{--flow-space:var(--space-xl-2xl);font-size:calc(1.2 * var(--size-step-0))}.details details>h4{color:var(--color-text-accent)}.details summary::-webkit-details-marker,.details summary::marker{content:"";display:none}.details summary+*{margin-block-start:var(--space-s-m)}.details details>*+*{margin-inline-start:var(--size-step-2)} 29custom-card{--gutter:var(--space-xs-s);background-color:var(--spot-color,var(--color-bg-accent));border:4px solid var(--color-bg-accent);color:var(--color-text);padding:var(--space-s-m);border-radius:var(--border-radius-medium);max-inline-size:unset;display:grid;grid-template-rows:[image] max-content [headline] max-content [meta] max-content [desc] auto [footer] max-content}custom-card>:empty{display:none}custom-card ::-moz-selection{color:var(--color-dark);background-color:var(--color-secondary)}custom-card ::selection{color:var(--color-dark);background-color:var(--color-secondary)}custom-card :is(h2,h3){--flow-space:var(--space-m);grid-row:headline}custom-card :is(h2,h3) a{text-decoration:none}custom-card>:is(picture,figure){grid-row:image;--flow-space:0}custom-card img{max-inline-size:var(--max-img-width,100%);block-size:auto;aspect-ratio:16/9;-o-object-fit:cover;object-fit:cover;-o-object-position:center;object-position:center}custom-card>.meta{grid-row:meta;font-size:var(--size-step-min-1)}custom-card>p{grid-row:desc}custom-card>footer{grid-row:footer;font-size:var(--size-step-min-2)}custom-card :is(a,p,h2,h3){overflow-wrap:break-word;word-wrap:break-word;word-break:break-word}custom-card[no-padding]{background-color:transparent;border:4px solid var(--color-bg-accent);padding:0;border-radius:none}custom-card[img-square] img{aspect-ratio:1}custom-card[clickable]:focus-within,custom-card[clickable]:hover{border:4px solid var(--color-primary)}custom-card[clickable]:focus-within a:focus{outline:none}custom-card[clickable]{position:relative}custom-card[clickable] a:before{bottom:0;content:"";left:0;position:absolute;right:0;top:0}</style><link rel="preload" href="/assets/fonts/redhat/red-hat-display-v7-latin-900.woff2" as="font" type="font/woff2" crossorigin><link rel="preload" href="/assets/fonts/atkinson/atkinson-hyperlegible-regular.woff2" as="font" type="font/woff2" crossorigin>
29<script src="/bundle/OZXIF9ZT_K.js" type="module"></script>
29<meta name="description" content="You can use this starter as a template for your blog and you are ready to go! But there are some adjustments you have to make."><meta name="theme-color" content="#2e2e2e" media="(prefers-color-scheme: dark)"><meta name="theme-color" content="#f8f8f8" media="(prefers-color-scheme: light)"><meta name="robots" content="index,follow"><meta name="googlebot" content="index,follow"><meta name="generator" content="Eleventy v3.1.6"><meta name="format-detection" content="telephone=no"><meta name="color-scheme" content="light dark"><link rel="canonical" href="https://eleventy-excellent.netlify.app/get-started/"><link rel="author" href="humans.txt"><link rel="me" href="https://front-end.social/@lene"><meta name="fediverse:creator" content="@[email protected]"><meta property="og:url" content="https://eleventy-excellent.netlify.app/get-started/"><meta property="og:type" content="website"><meta property="og:title" content="Get started"><meta property="og:image" content="https://eleventy-excellent.netlify.app/assets/images/template/opengraph-default.jpg"><meta property="og:image:alt" content="Visible content: An Eleventy starter with CUBE CSS, Every Layout, Design Tokens and Tailwind for utility classes. A workflow for building modern and resilient websites, introduced by Andy Bell's project buildexcellentwebsit.es"><meta property="og:image:width" content="1200"><meta property="og:image:height" content="630"><meta property="og:description" content="You can use this starter as a template for your blog and you are ready to go! But there are some adjustments you have to make."><meta property="og:site_name" content="Eleventy Excellent"><meta property="og:locale" content="en_EN"><meta property="article:author" content="Lene Saile"><link rel="alternate" type="application/atom+xml" title="Atom Feed: Eleventy Excellent" href="/feed.xml"><link rel="alternate" type="application/json" title="JSON Feed: Eleventy Excellent" href="/feed.json"><link rel="icon" href="/favicon.ico" sizes="any"><link rel="icon" href="/favicon.svg" type="image/svg+xml"><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="manifest" href="/site.webmanifest" crossorigin="use-credentials">
29<script type="speculationrules">{ 30 "prefetch": [ 31 { 32 "where": {"href_matches": "/*"}, 33 "eagerness": "moderate" 34 } 35 ] 36 }</script>
36</head><body class="page"><header class="wrapper"><a href="#main" class="skip-link">Skip to content</a><div class="repel ontop gutter-s"><a href="/" class="logo | cluster"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="#dd4462" viewBox="0 0 24 24" aria-hidden="true" focusable="false" tabindex="-1"><path fill-rule="evenodd" d="M10.788 3.21c.448-1.077 1.976-1.077 2.424 0l2.082 5.007 5.404.433c1.164.093 1.636 1.545.749 2.305l-4.117 3.527 1.257 5.273c.271 1.136-.964 2.033-1.96 1.425L12 18.354 7.373 21.18c-.996.608-2.231-.29-1.96-1.425l1.257-5.273-4.117-3.527c-.887-.76-.415-2.212.749-2.305l5.404-.433z" clip-rule="evenodd"></path></svg> <span class="font-display">Eleventy Excellent</span></a><nav id="mainnav" aria-label="Main" class="mainnav"><ul role="list" data-no-flash class="cluster"><li><a href="/about/">About</a></li><li><a href="/get-started/" aria-current="page" data-state="active">Docs</a></li><li><a href="/built-with/">Built with</a></li><li><a href="/blog/">Blog</a></li></ul></nav></div></header><main id="main" class="flow"><div style="--region-space-top: var(--space-xl-2xl)" class="region wrapper flow prose"><h1 class="gradient-text">Get started</h1><p>You can use this starter as a template for your blog and you are ready to go! But there are some <strong>adjustments you have to make</strong>.</p><ul><li><code>src/_data/meta.js</code> - sets different âhard codedâ text values all over the template, you can change the language, etc.</li><li>edit your personal details and social media in <code>src/_data/personal.yaml</code>.</li><li>Open <code>src/assets/css/global/blocks/external-link.css</code>. Replace âeleventy-excellent.netlify.appâ with your own domain. This is about the external link indicators, they are matched with your domain. If you donât want to use external link indicators, feel free to delete the whole style rule. You can also add the class <code>no-indicator</code> to any anchor element you want to except from this rule.</li></ul><p>The <a href="/blog/">blog posts</a> also explain / show some features that arenât covered here.</p><h2 id="docs-lol"><a href="#docs-lol" class="heading-anchor">âDocsâ lol</a></h2><div class="details flow"><ul aria-label="section controls" role="list" class="control | cluster"><li><button data-expand-all data-small-button class="button">expand all</button></li><li><button data-collapse-all data-small-button class="button">collapse all</button></li></ul><details id="card"><summary><h2>Card</h2></summary><p>Previous to version 3, the card component was a Nunjucks include. There are a number of things that had to be set before being able to use it, like the level of the heading or whether tags shall be displayed. <a href="https://www.11ty.dev/docs/languages/webc/">WebC</a> makes this easier, as you can now use the custom element and opt in to the different slots.</p><h3>Available slots:</h3><ul><li><code>image</code>: has <code>slot="image"</code> on the container (<code>picture</code> or <code>figure</code>) by default)</li><li><code>headline</code>: display the card's main title</li><li><code>date</code> and <code>tag</code>: Grouped within the classes <code>meta</code> and <code>cluster</code> for date and tagging information</li><li><code>content</code></li><li><code>footer</code>: for links or whatever footer information</li></ul><p>I added some <strong>variants</strong>, avaliable via attribute selectors:</p><ul><li><code>img-square</code>: Enforces a square aspect ratio for images</li><li><code>clickable</code>: Makes the whole card clickable</li><li><code>no-padding</code>: Removes padding and background modifications</li></ul><h3>Usage</h3><pre><code><custom-card> 37{% image "path-to-img", "alt-text" %} 38<span slot="date"></span> 39<span slot="tag" class="button"></span> 40<h2 slot="headline"></h2> 41<p slot="content"></p> 42<footer slot="footer"></footer> 43</custom-card> 44</code></pre><h3>Example</h3><div data-layout="50-50" class="grid"><custom-card class="flow no-indicator"> 45 <picture><source type="image/avif" srcset="/assets/images/asturias-3-650w.avif 650w, /assets/images/asturias-3-960w.avif 960w, /assets/images/asturias-3-1400w.avif 1400w" sizes="auto"><source type="image/webp" srcset="/assets/images/asturias-3-650w.webp 650w, /assets/images/asturias-3-960w.webp 960w, /assets/images/asturias-3-1400w.webp 1400w" sizes="auto"><source type="image/jpeg" srcset="/assets/images/asturias-3-650w.jpeg 650w, /assets/images/asturias-3-960w.jpeg 960w, /assets/images/asturias-3-1400w.jpeg 1400w" sizes="auto"><img src="/assets/images/asturias-3-1400w.jpeg" width="1400" height="930" alt="Close-up of a delicate white flower with a yellow center, surrounded by green leaves" loading="lazy" decoding="async"></picture> 46 <h4>Utopia</h4> 47 <div class="meta | cluster"><span>1516</span><span data-small-button class="button">Default</span></div> 48 <p>Among them, there is no sort of traffic, no knowledge of letters, no understanding of numbers, no name of magistrates, nor of politics, only of virtues; and they measure all things by barleycorns; their money, plate, and other ornaments they so diligently polish that no rust can stick to them.</p> 49 <footer><a href="#">Link in the footer</a></footer> 50 51</custom-card>
51<custom-card clickable img-square class="flow no-indicator"> 52 <picture><source type="image/avif" srcset="/assets/images/asturias-3-650w.avif 650w, /assets/images/asturias-3-960w.avif 960w, /assets/images/asturias-3-1400w.avif 1400w" sizes="auto"><source type="image/webp" srcset="/assets/images/asturias-3-650w.webp 650w, /assets/images/asturias-3-960w.webp 960w, /assets/images/asturias-3-1400w.webp 1400w" sizes="auto"><source type="image/jpeg" srcset="/assets/images/asturias-3-650w.jpeg 650w, /assets/images/asturias-3-960w.jpeg 960w, /assets/images/asturias-3-1400w.jpeg 1400w" sizes="auto"><img src="/assets/images/asturias-3-1400w.jpeg" width="1400" height="930" alt="Close-up of a delicate white flower with a yellow center, surrounded by green leaves" loading="lazy" decoding="async"></picture> 53 <h4>The order does not matter</h4> 54 <div class="meta | cluster"><span>18.02.1984</span><span data-small-button class="button">clickable</span><span data-small-button class="button">square image</span></div> 55 56 <footer><a href="#">Link in the footer makes whole card a link</a></footer> 57 58</custom-card><custom-card class="flow no-indicator"> 59 60 <h4>Just title and content</h4> 61 <div class="meta | cluster"></div> 62 <p>They have no lawyers among them, for they consider them as a sort of people whose profession it is to disguise matters and to wrest the laws [...].</p> 63 64 65</custom-card><custom-card no-padding class="flow no-indicator"> 66 <picture><source type="image/avif" srcset="/assets/images/asturias-3-650w.avif 650w, /assets/images/asturias-3-960w.avif 960w, /assets/images/asturias-3-1400w.avif 1400w" sizes="auto"><source type="image/webp" srcset="/assets/images/asturias-3-650w.webp 650w, /assets/images/asturias-3-960w.webp 960w, /assets/images/asturias-3-1400w.webp 1400w" sizes="auto"><source type="image/jpeg" srcset="/assets/images/asturias-3-650w.jpeg 650w, /assets/images/asturias-3-960w.jpeg 960w, /assets/images/asturias-3-1400w.jpeg 1400w" sizes="auto"><img src="/assets/images/asturias-3-1400w.jpeg" width="1400" height="930" alt="Close-up of a delicate white flower with a yellow center, surrounded by green leaves" loading="lazy" decoding="async"></picture> 67 <h4>This card has no padding</h4> 68 <div class="meta | cluster"></div> 69 <p>Red Hat's first logo appeared on an early invoice. It was a simple, bright red brimmed top hat placed above the words "Red Hat Software."</p> 70 71 72</custom-card></div></details><details id="config"><summary><h2>Config</h2></summary><p>I like to divide things into small thematic areas, it helps me orient myself better. Configurations are structured into separate modules in <code>src/_config</code> and are then imported into the main configuration file.</p><ul><li><strong>collections.js</strong>: Manages Eleventy collections such as posts and tags: <a href="https://www.11ty.dev/docs/collections/" rel="noopener">https://www.11ty.dev/docs/collections/</a></li><li><strong>events.js</strong>: For code that should run at certain times during the compiling process: <a href="https://www.11ty.dev/docs/events/" rel="noopener">https://www.11ty.dev/docs/events/</a></li><li><strong>filters.js</strong>: Used within templating syntax to transform data into a more presentable format: <a href="https://www.11ty.dev/docs/filters/" rel="noopener">https://www.11ty.dev/docs/filters/</a></li><li><strong>plugins.js</strong>: Everything I or Eleventy considers to be a plugin: <a href="https://www.11ty.dev/docs/plugins/" rel="noopener">https://www.11ty.dev/docs/plugins/</a></li><li><strong>shortcodes.js</strong>: Defines shortcodes for reusable content: <a href="https://www.11ty.dev/docs/shortcodes/" rel="noopener">https://www.11ty.dev/docs/shortcodes/</a></li></ul><p>Each configuration category (filters, plugins, shortcodes, etc.) is modularized. For example, <code>dates.js</code> within the <code>filters</code> folder contains date-related filters.</p><pre class="language-js"><code class="language-js"><span class="token keyword">import</span> dayjs <span class="token keyword">from</span> <span class="token string">'dayjs'</span><span class="token punctuation">;</span> 73<p><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">toISOString</span> <span class="token operator">=</span> <span class="token parameter">dateString</span> <span class="token operator">=></span> <span class="token function">dayjs</span><span class="token punctuation">(</span>dateString<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br> 74<span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">formatDate</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">date<span class="token punctuation">,</span> format</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">dayjs</span><span class="token punctuation">(</span>date<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span>
74format<span class="token punctuation">)</span><span class="token punctuation">;</span></p></code></pre><p></p><p>These individual modules are then imported and consolidated in a central <code>filters.js</code> file, which exports all the filters as a single default object.</p><pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span>toISOString<span class="token punctuation">,</span> formatDate<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./filters/dates.js'</span><span class="token punctuation">;</span> 75<span class="token comment">// more imports</span> 76<p><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">{</span><br> 77toISOString<span class="token punctuation">,</span><br> 78formatDate<span class="token punctuation">,</span><br> 79<span class="token comment">// more exports</span><br> 80<span class="token punctuation">}</span><span class="token punctuation">;</span><br> 81</p></code></pre><p></p><h3 id="integration-in-eleventy-config"><a href="#integration-in-eleventy-config" class="heading-anchor">Integration in Eleventy Config</a></h3><p>In the main Eleventy configuration file (<code>eleventy.config.js</code>), these modules are imported:</p><pre class="language-js"><code class="language-js"><span class="token keyword">import</span> filters <span class="token keyword">from</span> <span class="token string">'./src/_config/filters.js'</span><span class="token punctuation">;</span> 82<span class="token keyword">import</span> shortcodes <span class="token keyword">from</span> <span class="token string">'./src/_config/shortcodes.js'</span><span class="token punctuation">;</span></code></pre><p>They are then used to register filters and shortcodes with Eleventy, using this nice concise syntax:</p><pre class="language-js"><code class="language-js">eleventyConfig<span class="token punctuation">.</span><span class="token function">addFilter</span><span class="token punctuation">(</span><span class="token string">'toIsoString'</span><span class="token punctuation">,</span> filters<span class="token punctuation">.</span>toISOString<span class="token punctuation">)</span><span class="token punctuation">;</span> 83eleventyConfig<span class="token punctuation">.</span><span class="token function">addFilter</span><span class="token punctuation">(</span><span class="token string">
83'formatDate'</span><span class="token punctuation">,</span> filters<span class="token punctuation">.</span>formatDate<span class="token punctuation">)</span><span class="token punctuation">;</span> 84<span class="token comment">// More filters...</span> 85eleventyConfig<span class="token punctuation">.</span><span class="token function">addShortcode</span><span class="token punctuation">(</span><span class="token string">'svg'</span><span class="token punctuation">,</span> shortcodes<span class="token punctuation">.</span>svgPositionalShortcode<span class="token punctuation">)</span><span class="token punctuation">;</span> 86eleventyConfig<span class="token punctuation">.</span><span class="token function">addShortcode</span><span class="token punctuation">(</span><span class="token string">'svgKeys'</span><span class="token punctuation">,</span> shortcodes<span class="token punctuation">.</span>svgKeysShortcode<span class="token punctuation">)</span><span class="token punctuation">;</span> 87<span class="token comment">// More shortcodes...</span></code></pre><p>This method hopefully keeps the Eleventy config clean and focused, only concerning itself with the registration of functionalities, while the logic and definition remain abstracted in their respective modules.</p><p>Some time ago I wrote a blog post about <a href="https://www.lenesaile.com/en/blog/organizing-the-eleventy-config-file/" rel="noopener">how to organize the Eleventy configuration file</a> where I go a little bit deeper into this topic.</p></details><details id="css"><summary><h2>CSS</h2></summary><p>Add and delete your globally available custom block stylesheets in <code>src/assets/css/global/blocks/*.css</code>.</p><p>The methodology used is <a href="https://cube.fyi/" rel="noopener">CUBE CSS.</a></p><p>The CSS system of this starter was invented by Andy Bell. If you want to know exactly how it all works, and have a look at the (further elaborated) original, <a href="https://piccalil.li/blog/a-css-project-boilerplate/" rel="noopener">read this article on piccalil.li</a>.</p><h3 id="inline-css-and-bundles"><a href="#inline-css-and-bundles" class="heading-anchor">Inline CSS and bundles</a></h3><p>The main CSS file is now inline in production to improve performance, see <code>src/_includes/head/css-inline.njk</code>.</p><p>You can add per-page or component bundles of CSS. Instead of adding your CSS file to the <code>src/assets/css/global/blocks/</code> directory, you can place them in <code>src/assets/css/local/</code>. All CSS files in there will be stored alongside <code>global.css</code> in <code>src/_includes/css/</code>. You can now include them in the âlocalâ bundle only on pages or components where you need them:</p><pre class="language-jinja2"><code class="language-jinja2"><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">css</span> <span class="token string">"local"</span> <span class="token delimiter punctuation">%}</span></span> 88 <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">include</span> <span class="token string">"css/your-stylesheet.css"</span> <span class="token delimiter punctuation">%}</span></span> 89<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">endcss</span> <span class="token delimiter punctuation">%}</span></span></code></pre><h3 id="component-css"><a href="#component-css" class="heading-anchor">Component CSS</a></h3><p>All CSS files placed in <code>src/assets/css/components/</code> will be sent to the output folder, where components can reference them: <code>/assets/css/components/*.css</code>.</p><h3 id="debugging-css"><a href="#debugging-css" class="heading-anchor">Debugging CSS</a></h3><p>In <code>src/assets/css/global.css</code> you can decomment <code>@import-glob 'tests/*.css';</code> to include CSS for debugging.</p><p>It makes visible when your code<a href="https://github.com/11ty/is-land" rel="noopener"> wrapped in <code><is-land></code> elements</a> is being hydrated, where things might overflow and many other warnings and errors <a href="https://heydonworks.com/article/testing-html-with-modern-css/" rel="noopener">that Heydon Pickering came up with</a>.</p><h3 id="cascade-layers">
89<a href="#cascade-layers" class="heading-anchor">Cascade layers</a></h3><p>We now use cascade layers! Up until now, I used the <code>:where()</code> pseudo-class to create low specificity selectors for the reset and compositions. <a href="https://mayank.co/blog/css-reset-layer/" rel="noopener">Mayank inspired me</a> to change to cascade layers. We have two major bundles of CSS: everything included in âglobalâ In <code>src/assets/css/global/global.css</code> is now organized in cascade layers. The âlocalâ bundle is for per-page or component CSS, and does not use cascade layers - it has thus a higher specificity, independent of any selector specificity in the global CSS.</p><h3 id="tailwind-css"><a href="#tailwind-css" class="heading-anchor">Tailwind CSS</a></h3><p>You will find that Tailwind CSS does not work as you might expect here. I explain this in <a href="/blog/what-is-tailwind-css-doing-here/">one of the blog posts</a>.</p><p>If you are working with <strong>VS Code</strong> I recommend installing the <a href="https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss" rel="noopener">Tailwind CSS IntelliSense</a> addon, as it works also for the custom utility classes.</p><p>If the IntelliSense does not seem to work in Nunjucks files, try adding this to your VS Code settings:</p><pre class="language-json"><code class="language-json"> <span class="token property">"tailwindCSS.includeLanguages"</span><span class="token operator">:</span> <span class="token punctuation">{</span> 90 <span class="token property">"jinja-html"</span><span class="token operator">:</span> <span class="token string">"html"</span><span class="token punctuation">,</span> 91 <span class="token property">"jinja"</span><span class="token operator">:</span> <span class="token string">"html"</span><span class="token punctuation">,</span> 92 <span class="token property">"njk"</span><span class="token operator">:</span> <span class="token string">"html"</span> 93 <span class="token punctuation">}</span><span class="token punctuation">,</span></code></pre></details><details id="design-tokens"><summary><h2>Design tokens</h2></summary><p>Edit all your preferences (colors, fluid text sizes etc.) in <code>src/_data/designTokens/*.json</code>.</p><p>Additional colors, variants and gradients for custom properties are automatically created in <code>src/assets/css/global/base/variables.css</code> based on the colors set in <code>colors.json</code>.</p><p>In the <a href="/styleguide/">style guide</a> you can see how everything turns out.</p><h3 id="special-case-colors"><a href="#special-case-colors" class="heading-anchor">Special case: colors</a></h3><p>As of version 4.0, you can create colors dynamically. Run <code>npm run colors</code> after setting your color values in <code>src/_data/designTokens/colorsBase.json</code>. This will create / overwrite the required <code>colors.json</code> file in the same directory. These colors become custom properties (e.g. <code>--color-gray-100</code>) and utility classes similar to the Tailwind CSS syntax (for example <code>bg-gray-100</code>, <code>text-gray-900</code>).</p><p>If you want to adjust how the colors turn out, edit <code>src/_config/setup/create-colors.js</code>.</p><p>Colors placed under <code>shades_neutral</code> or <code>shades_vibrant</code> are converted into scalable palettes. <code>shades_neutral</code> is better for grayish / monochromatic colors, while <code>shades_vibrant</code> is better for colorful palettes. Colors listed under <code>standalone</code> and <code>light_dark</code> are left as they are, <code>light_dark</code> items output a second âsubduedâ version optimized for dark themes.</p><pre class="language-js"><code class="language-js"><span class="token comment">// this creates a palette with shades of green, 100 to 900</span> 94 <span class="token string-property property">"shades_vibrant"</span><span class="token operator">:</span> <span class="token punctuation">[</span> 95 <span class="token punctuation">{</span> 96 <span class="token string-property property">"name"</span><span class="token operator">:</span> <span class="token string">"green"</span><span class="token punctuation">,</span>
97 <span class="token string-property property">"value"</span><span class="token operator">:</span> <span class="token string">"#008000"</span> 98 <span class="token punctuation">}</span> 99 <span class="token punctuation">]</span><span class="token punctuation">,</span></code></pre><p><strong class="text-pink">Important:</strong> If you change the color names, you must edit <code>src/assets/css/global/base/variables.css</code> with your color names. The rest of the CSS files should only reference custom properties set in <code>variables.css</code>.</p></details><details id="details"><summary><h2>Details</h2></summary><p>The <code><custom-details></code> WebC component has a corresponding Nunjucks include.<br>It uses the <code><details></code> and <code><summary></code> elements to create a collapsible section and enhances them aesthetically and functionally.</p><p>The JavaScript for the <code><custom-details></code> component adds functionality to buttons to expand and collapse the sections with one action. When JavaScript is disabled, the sections are still accessible and collapsible, but the extra buttons are hidden.</p><p>On page load, it checks if a hash corresponding to a details ID exists in the URL. If such an ID is found, the corresponding details section is programmatically opened, allowing direct navigation to an open section from a shared URL.</p><p>The sorting is set by default on âalphabeticâ, but you can also pass in âshuffleâ or âreverseâ as a parameter (directly in the <code>details.njk</code> partial).</p><h3 id="usage"><a href="#usage" class="heading-anchor">Usage</a></h3><pre class="language-jinja2"><code class="language-jinja2"><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">set</span> <span class="token variable">itemList</span> <span class="token operator">=</span> <span class="token variable">collections</span><span class="token punctuation">.</span><span class="token variable">docs</span> <span class="token delimiter punctuation">%}</span></span> 100<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">set</span> <span class="token variable">headingLevel</span> <span class="token operator">=</span> <span class="token string">"h2"</span> <span class="token delimiter punctuation">%}</span></span> <span class="token jinja2 language-jinja2"><span class="token comment">{# optional, defaults to false #}</span></span> 101<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">include</span> <span class="token string">'partials/details.njk'</span> <span class="token delimiter punctuation">%}</span></span></code></pre><h3 id="example"><a href="#example" class="heading-anchor">Example</a></h3><p>You are in the middle of a custom details component!</p></details><details id="easteregg"><summary><h2>Easteregg</h2></summary><p>The <code><custom-easteregg></code> component is by default in the base layout in <code>src/_layouts/base.njk</code>. Just delete the two lines if you donât want to use it. The component is<br>designed to trigger a confetti effect when a user types a specific keyword sequence. It uses the <code>canvas-confetti</code> package (bundled into the component at build time) to render custom-shaped particles based on user input.</p><h3 id="defaults"><a href="#defaults" class="heading-anchor">Defaults:</a></h3><ul><li>Keywords: <code>"eleventy"</code>, <code>"excellent"</code></li><li>Shape: <code>"âï¸"</code></li><li>Particle Count: <code>30</code></li></ul><h3 id="customizable-attributes"><a href="#customizable-attributes" class="heading-anchor">Customizable Attributes:</a></h3><ul><li><code>keyword</code>: custom keyword</li><li><code>shape</code>: custom shape for the confetti particles using emojis or text</li><li><code>particle-count</code>: number of particles to release during the effect</li></ul><pre class="language-js"><code class="language-js"><span class="token operator"><</span>script type<span class="token operator">=</span><span class="token string">"module"</span> src<span class="token operator">=</span><span class="token string">"/assets/scripts/components/custom-easteregg.js"</span><span class="token operator">></span><span class="token operator"><</span><span class="token operator">/</span>script<span class="token operator">></span> 102<span class="token operator"><</span>custom<span class="token operator">-</span>easteregg keyword<span class="token operator">=</span><span class="token string">"yay"</span> shape<span class="token operator">=</span><span class="token string">"ð"</span> particle<span class="token operator">-</span>count<span class="token operator">=</span><span class="token string">"50"</span><span class="token operator">></span><span class="token operator"><</span><span class="token operator">/</span>custom<span class="token operator">-</span>easteregg<span class="token operator">></span></code></pre></details>
102<details id="favicons"><summary><h2>Favicons</h2></summary><p>All ânecessaryâ favicons are in <code>src/assets/images/favicon</code>, and copied over to the root of the output folder.</p><p>I chose the sizes based on the recommendations from the <a href="https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs" rel="noopener">How to Favicon article on evilmartians.com.</a></p><p>You can place them in that directory manually, or use the script to automate the process:</p><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> run favicons</code></pre><p>In this case define the SVG icon on which all formats are based on in <code>meta.js</code>:</p><pre class="language-js"><code class="language-js"><span class="token keyword">export</span> <span class="token keyword">const</span> pathToSvgLogo <span class="token operator">=</span> <span class="token string">'src/assets/svg/misc/logo.svg'</span><span class="token punctuation">;</span> <span class="token comment">// used for favicon generation</span></code></pre><p>Regardless of whether you generate the icons automatically or create them manually, it is best to keep the names so as not to break any reference to them. You can also use raster images instead of SVG.</p></details><details id="fonts"><summary><h2>Fonts</h2></summary><p>This starter uses two custom fonts, Red Hat Display and Atkinson Hyperlegible. You can add or delete fonts in <code>src/assets/fonts</code>.</p><p>I often create font subsets using the <a href="https://www.fontsquirrel.com/tools/webfont-generator" rel="noopener">Fontsquirrel Webfont Generator</a>.</p><p>Next, edit <code>src/assets/css/global/base/fonts.css</code>.</p><p>Add your new font aliases in <code>src/_data/designTokens/fonts.json</code>.</p><p>Finally, in <code>src/_includes/head/preloads.njk</code> edit the font preloads.</p></details><details id="images"><summary><h2>Images</h2></summary><p>Using the <a href="https://www.11ty.dev/docs/plugins/image/" rel="noopener">Eleventy Image</a> plugin, there are three ways to handle image optimization: HTML Transform, Markdown syntax, and Nunjucks shortcodes. <a href="/blog/post-with-an-image/">See the dedicated blog post to dive (much) deeper.</a></p><p>Have a look at the <a href="https://www.11ty.dev/docs/plugins/image/#attribute-overrides" rel="noopener">Attribute Overrides</a> for the HTML Transform methods (1 and 2) for per instance overrides. Adding <code>eleventy:ignore</code> to an <code><img></code> element for example, skips this image.</p><h3 id="1-html-transform"><a href="#1-html-transform" class="heading-anchor">1. HTML Transform</a></h3><p>The HTML Transform automatically processes <code><img></code> and <code><picture></code> elements in your HTML files as a post-processing step during the build.</p><pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>./path/to/image.jpg<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>alt text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span></code></pre><h3 id="2-markdown-syntax"><a href="#2-markdown-syntax" class="heading-anchor">2. Markdown Syntax</a></h3><p>The Markdown syntax creates the <code><img></code> element that the <em>HTML Transform plugin</em> is looking for, and then transforms it to the <code><picture></code> element (if more than one format is set).</p><pre class="language-markdown"><code class="language-markdown"><span class="token url"><span class="token operator">!</span>[<span class="token content">alt text</span>](<span class="token url">/path/to/image.jpg</span>)</span></code></pre><h3 id="3-nunjucks-shortcodes"><a href="#3-nunjucks-shortcodes" class="heading-anchor">3. Nunjucks Shortcodes</a></h3><p>In Nunjucks templates you can also use shortcodes (<code>image</code> and <code>imageKeys</code>).</p><pre class="language-jinja2"><code class="language-jinja2"><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">image</span> <span class="token string">'/path/to/image.jpg'</span><span class="token punctuation">,</span> <span class="token string">'alt text'</span> <span class="token delimiter punctuation">%}
102</span></span> 103<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">imageKeys</span> <span class="token punctuation">{</span> 104 <span class="token string">"alt"</span><span class="token punctuation">:</span> <span class="token string">"alt text"</span><span class="token punctuation">,</span> 105 <span class="token string">"src"</span><span class="token punctuation">:</span> <span class="token string">"/path/to/image.jpg"</span> 106<span class="token punctuation">}</span> <span class="token delimiter punctuation">%}</span></span></code></pre></details><details id="javascript"><summary><h2>JavaScript</h2></summary><p>This starter has <strong>no real JS dependency</strong>. If JavaScript is not available, components that rely on it â like the theme switcher â will be hidden. If you opted in for the drawer menu, pills will be shown instead.</p><p>There are two kinds of bundles for JavaScript in this starter, see <code>src/_includes/head/js-inline.njk</code> and <code>src/_includes/head/js-defer.njk</code>.<br>By default, I include Eleventyâs <a href="https://github.com/11ty/is-land" rel="noopener">is-land</a> framework and the theme toggle inline.</p><p>You can include more scripts like so:</p><pre class="language-jinja2"><code class="language-jinja2"><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">js</span> <span class="token string">"inline"</span> <span class="token delimiter punctuation">%}</span></span> 107 <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">include</span> <span class="token string">"scripts/your-inline-script.js"</span> <span class="token delimiter punctuation">%}</span></span> 108<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">endjs</span> <span class="token delimiter punctuation">%}</span></span></code></pre><p>Same goes for scripts that should be defered:</p><pre class="language-jinja2"><code class="language-jinja2"><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">js</span> <span class="token string">"defer"</span> <span class="token delimiter punctuation">%}</span></span> 109 <span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">include</span> <span class="token string">"scripts/your-defered-script.js"</span> <span class="token delimiter punctuation">%}</span></span> 110<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">endjs</span> <span class="token delimiter punctuation">%}</span></span></code></pre><p>Scripts stored in <code>src/assets/scripts/components/</code> are sent to the output folder, while scripts in <code>src/assets/scripts/bundle/</code> are sent to <code>src/_includes/scripts/</code>, from where you can include them in the respective bundle.</p><p>Some components are enhanced with JavaScript.</p></details><details id="masonry"><summary><h2>Masonry</h2></summary><p>Masonry layout is not yet a native part of CSS grid. There is a debate if using <code>grid-template-rows: masonry;</code> is actually the best way to implement it.</p><p>It should be used carefully so we donât create confusion with the tabbing order. In version 3 of the starter I made the masonry layout a web component, and no longer a opt-in feature (was: <code>masonry: true</code> in the front matter).</p><p><code><custom-masonry></code> is designed to function as a masonry grid by dynamically adjusting item positions based on the available column space and the size of its content. The necessary JavaScript (<code>custom-masonry.js</code>) is loaded only once per component usage due to the <code>data-island="once"</code> attribute.<br>Optional: pass in <code>layout="50-50"</code> to set a 50% width for each column.</p><p>If no JavaScript is available, the grid will fall back to the regular grid layout defined in <code>src/assets/css/global/compositions/grid.css</code>.</p><pre class="language-js"><code class="language-js"><span class="token operator"><</span>custom<span class="token operator">-</span>masonry<span class="token operator">></span> <span class="token punctuation">(</span>children<span class="token punctuation">)</span> <span class="token operator"><</span><span class="token operator">/</span>custom<span class="token operator">-</span>masonry<span class="token operator">></span> 111<span class="token operator"><</span>custom<span class="token operator">-</span>masonry layout<span class="token operator">=</span><span class="token string">"50-50"</span><span class="token operator">></span> <span class="token punctuation">(</span>children<span class="token punctuation">)</span> <span class="token operator"><</span><span class="token operator">/</span>custom<span class="token operator">-</span>masonry<span class="token operator">></span></code></pre><div><is-land on:visible> 112 <custom-masonry data-rows="masonry" class="grid"> 113 <div style="background-color: var(--color-primary); aspect-ratio: 3/2"></div><div></div><div style="background-color: var(--color-tertiary); aspect-ratio: 4/5"></div><div style="background-color: var(--color-primary)"></div><div></div><div style="background-color: var(--color-secondary); aspect-ratio: 5/4"></div><div></div><div style="background-color: var(--color-secondary)"></div><div style="background-color: var(--color-primary); aspect-ratio: 16/9"></div><div></div> 114 115 <template data-island="once"> 116
116<script src="/assets/scripts/components/custom-masonry.js" type="module"></script>
116 117 </template> 118 </custom-masonry> 119</is-land> 120</div><style>custom-masonry div { 121 inline-size: min(30rem, 100%); 122 aspect-ratio: 1; 123 background-color: var(--color-text); 124 }</style></details><details id="navigation"><summary><h2>Navigation</h2></summary><p>Edit your navigation items in <code>src/_data/navigation.js</code>.</p><p>You have two options for mobile navigation: by default, the navigation on small displays is converted to small pills that wrap. This does not require any additional JavaScript.</p><h3 id="drawer-menu"><a href="#drawer-menu" class="heading-anchor">Drawer Menu</a></h3><p>You can activate a drawer menu for mobile in <code>src/_data/meta.js</code>:</p><pre class="language-js"><code class="language-js"><span class="token literal-property property">navigation</span><span class="token operator">:</span> <span class="token punctuation">{</span> 125 <span class="token comment">// other settings</span> 126 <span class="token literal-property property">drawerNav</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> 127 <span class="token punctuation">}</span><span class="token punctuation">,</span></code></pre><p><code>drawerNav</code> activates the navigation drawer, <a href="https://web.dev/articles/website-navigation" rel="noopener">built according to Manuel MatuzoviÄâs article on web.dev.</a></p><p>Adjust your menu breakpoint in <code>src/_data/designTokens/viewports.json</code></p><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> 128 <span class="token comment">// ...</span> 129 <span class="token property">"navigation"</span><span class="token operator">:</span> <span class="token number">662</span><span class="token punctuation">,</span> 130 <span class="token comment">// ...</span> 131<span class="token punctuation">}</span></code></pre><h3 id="submenu"><a href="#submenu" class="heading-anchor">Submenu</a></h3><p>You can activate submenus in <code>src/_data/meta.js</code>:</p><pre class="language-js"><code class="language-js"><span class="token literal-property property">navigation</span><span class="token operator">:</span> <span class="token punctuation">{</span> 132 <span class="token comment">// other settings</span> 133 <span class="token literal-property property">subMenu</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> 134 <span class="token punctuation">}</span><span class="token punctuation">,</span></code></pre><p>This includes the JavaScript for the submenu functionality. Add your submenu items to <code>src/_data/navigation.js</code> using this structure:</p><pre class="language-js"><code class="language-js"> <span class="token punctuation">{</span> 135 <span class="token literal-property property">text</span><span class="token operator">:</span> <span class="token string">'Unlinked parent'</span><span class="token punctuation">,</span> 136 <span class="token literal-property property">url</span><span class="token operator">:</span> <span class="token string">'#'</span><span class="token punctuation">,</span> 137 <span class="token literal-property property">submenu</span><span class="token operator">:</span> <span class="token punctuation">[</span> 138 <span class="token punctuation">{</span> 139 <span class="token literal-property property">text</span><span class="token operator">:</span> <span class="token string">'Sub Item'</span><span class="token punctuation">,</span> 140 <span class="token literal-property property">url</span><span class="token operator">:</span> <span class="token string">'/sub-item/'</span> 141 <span class="token punctuation">}</span><span class="token punctuation">,</span> 142 <span class="token operator">...</span> more items 143 <span class="token punctuation">]</span> 144<span class="token punctuation">}</span><span class="token punctuation">,</span></code></pre></details><details id="open-graph-images"><summary><h2>Open Graph images</h2></summary><p>You can see a <a href="/blog/open-graph-images/">preview of the OG images in a blog post</a>.</p><p>They are referenced in <code>meta-info.njk</code>:</p><pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>meta</span> 145 <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>og:image<span class="token punctuation">"</span></span> 146 <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>{{ meta.url }} 147 {% if (layout == 'post') %}/assets/og-images/{{ title | slugify }}-preview.jpeg 148 {% else %}{{ meta.opengraph_default }} 149 {% endif %}<span class="token punctuation">"</span></span> 150<span class="token punctuation">/></span></span></code></pre><p>To change the look and behaviour of those images and replace the SVG background edit <code>src/common/og-images.njk</code>.</p><p>The implementation is based on <a href="https://bnijenhuis.nl/notes/automatically-generate-open-graph-images-in-eleventy/" rel="noopener">Bernard Nijenhuis article.</a></p><p>If you want to be inspired, have a look at <a href="https://lea.codes/posts/2023-04-25-pseudorandom-numbers-in-eleventy/" rel="noopener">what Lea is doing here.</a></p><p>Consider that the domain is a hard coded value in the front matter.</p><p><strong>Important:</strong> I have relocated the creation of the images in the development process, so that the font only needs to be installed on your own system. The images are located in <code>src/assets/og-images</code> and are committed.</p><p>This is fine as long as you only work locally with Markdown, and the font is always installed on your system. If you work with a CMS you must add the font to where the site is built. Some CMS let you add fonts into the <code>prebuild</code> config. Otherwise it usually falls down to the <em>Ubuntu</em> Font Family. You still have to adjust the script <code>src/_config/events/svg-to-jpeg.js</code> to something like:</p><pre class="language-js"><code class="language-js"><span class="token keyword">
150import</span> fs <span class="token keyword">from</span> <span class="token string">'fs'</span><span class="token punctuation">;</span> 151<span class="token keyword">import</span> Image <span class="token keyword">from</span> <span class="token string">'@11ty/eleventy-img'</span><span class="token punctuation">;</span> 152<span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">svgToJpeg</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 153 <span class="token keyword">const</span> ogImagesDir <span class="token operator">=</span> <span class="token string">'dist/assets/og-images/'</span><span class="token punctuation">;</span> 154 fs<span class="token punctuation">.</span><span class="token function">readdir</span><span class="token punctuation">(</span>ogImagesDir<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">err<span class="token punctuation">,</span> files</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> 155 <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token operator">!</span>files <span class="token operator">&&</span> files<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 156 files<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token parameter">fileName</span> <span class="token operator">=></span> <span class="token punctuation">{</span> 157 <span class="token keyword">if</span> <span class="token punctuation">(</span>fileName<span class="token punctuation">.</span><span class="token function">endsWith</span><span class="token punctuation">(</span><span class="token string">'.svg'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 158 <span class="token keyword">let</span> imageUrl <span class="token operator">=</span> ogImagesDir <span class="token operator">+</span> fileName<span class="token punctuation">;</span> 159 <span class="token function">Image</span><span class="token punctuation">(</span>imageUrl<span class="token punctuation">,</span> <span class="token punctuation">{</span> 160 <span class="token literal-property property">formats</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'jpeg'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> 161 <span class="token literal-property property">outputDir</span><span class="token operator">:</span> <span class="token string">'./'</span> <span class="token operator">+</span> ogImagesDir<span class="token punctuation">,</span> 162 <span class="token function-variable function">filenameFormat</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">id<span class="token punctuation">,</span> src<span class="token punctuation">,</span> width<span class="token punctuation">,</span> format<span class="token punctuation">,</span> options</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> 163 <span class="token keyword">let</span> outputFileName <span class="token operator">=</span> fileName<span class="token punctuation">.</span><span class="token function">substring</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> fileName<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
164 <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>outputFileName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">.</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>format<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> 165 <span class="token punctuation">}</span> 166 <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 167 <span class="token punctuation">}</span> 168 <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 169 <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> 170 console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'â No social images found'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 171 <span class="token punctuation">}</span> 172 <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 173<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre><h3 id="regenerating-og-images"><a href="#regenerating-og-images" class="heading-anchor">Regenerating OG images</a></h3><p>As you make changes, possibly adjust the title of your post or delete it, the images add up in <code>src/assets/og-images</code>. To delete this folder and regenerate all images, you can run <code>npm run clean:og</code>.</p><p>Let me know if you encounter any problems.</p></details><details id="pagination"><summary><h2>Pagination</h2></summary><p>The blog posts use <a href="https://www.11ty.dev/docs/pagination/" rel="noopener">Eleventyâs pagination feature</a>
173. The logic for this can be found in the partial <code>src/_includes/partials/pagination.njk</code>. The blog page <code>src/pages/blog.njk</code> includes it and defines how many entries should be on a page.</p><p>If you do not want any pagination at all, it is easiest to set a very high number for the pagination size, for example:</p><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">pagination</span><span class="token punctuation">:</span> 174 <span class="token key atrule">data</span><span class="token punctuation">:</span> collections.allPosts 175 <span class="token key atrule">size</span><span class="token punctuation">:</span> <span class="token number">10000</span></code></pre><p>In <code>src/_data/meta.js</code> you can set some values for the visible content (previous / next buttons) and the aria labels.</p><p>You can also <strong>hide the number fields</strong> between the previous and next buttons by setting <code>paginationNumbers</code> to <code>false</code>.</p><pre class="language-js"><code class="language-js"><span class="token literal-property property">blog</span><span class="token operator">:</span> <span class="token punctuation">{</span> 176 <span class="token comment">// other adjustments</span> 177 <span class="token literal-property property">paginationLabel</span><span class="token operator">:</span> <span class="token string">'Blog'</span><span class="token punctuation">,</span> 178 <span class="token literal-property property">paginationPage</span><span class="token operator">:</span> <span class="token string">'Page'</span><span class="token punctuation">,</span> 179 <span class="token literal-property property">paginationPrevious</span><span class="token operator">:</span> <span class="token string">'Previous'</span><span class="token punctuation">,</span> 180 <span class="token literal-property property">paginationNext</span><span class="token operator">:</span> <span class="token string">'Next'</span><span class="token punctuation">,</span> 181 <span class="token literal-property property">paginationNumbers</span><span class="token operator">:</span> <span class="token boolean">true</span> 182<span class="token punctuation">}</span></code></pre><p>If you want to change the collection that is paginated (by default <code>collections.allPosts</code>), you must do so in two places: the front matter of the template, <code>src/pages/blog.njk</code>:</p><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">pagination</span><span class="token punctuation">:</span> 183 <span class="token key atrule">data</span><span class="token punctuation">:</span> collections.allPosts</code></pre><p>and where the pagination component is included, further down in <code>src/pages/blog.njk</code>:</p><pre class="language-jinja2"><code class="language-jinja2"><span class="token comment"><!-- set collection to paginate --></span> 184<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">set</span> <span class="token variable">collectionToPaginate</span> <span class="token operator">=</span> <span class="token variable">collections</span><span class="token punctuation">.</span><span class="token variable">posts</span> <span class="token delimiter punctuation">%}</span></span> 185<span class="token comment"><!-- if the number of items in the collection is greater than the number of items shown on one page --></span> 186<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">if</span> <span class="token variable">collectionToPaginate</span><span class="token punctuation">.</span><span class="token variable">length</span> <span class="token operator">></span> <span class="token variable">pagination</span><span class="token punctuation">.</span><span class="token variable">size</span> <span class="token delimiter punctuation">%}</span></span> 187<span class="token comment"><!-- include pagination --></span> 188<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">include</span> <span class="token string">'partials/pagination.njk'</span> <span class="token delimiter punctuation">%}
188</span></span> 189<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">endif</span> <span class="token delimiter punctuation">%}</span></span></code></pre></details><details id="platforms-icons"><summary><h2>Platforms (icons)</h2></summary><p>Find and set your platform icons in <code>src/assets/svg</code>, in the âplatformâ directory.</p><p>In <code>src/_data/personal.yaml</code> you can edit the platforms. The key should be the same as the name of the icon. For example: <code>mastodon: 'https://front-end.social/@lene'</code> and <code>src/assets/svg/platform/mastodon.svg</code>.</p><p><a href="https://simpleicons.org/" rel="noopener">https://simpleicons.org/</a> features a great variety of free SVG icons for popular platforms.</p></details><details id="robotstxt-and-llmstxt"><summary><h2>robots.txt and llms.txt</h2></summary><p><code>robots.txt</code> (<code>src/common/robots.njk</code>) tries to tell crawlers which URLs they may fetch. We write it from <code>src/common/robots.njk</code>.</p><h3 id="always-included"><a href="#always-included" class="heading-anchor">Always included</a></h3><ul><li><code>User-agent: *</code> with <code>Disallow: /404.html</code></li><li><code>Sitemap</code></li></ul><h3 id="optional-crawl-blocks"><a href="#optional-crawl-blocks" class="heading-anchor">Optional crawl blocks</a></h3><p>In <code>src/_data/meta.js</code>, under <code>robots</code>, you can allow training / AI-search scrapers (<code>allowAiCrawlers</code>) and on-demand browsing agents (<code>allowAiAgents</code>). Both default to blocked.</p><h3 id="llmstxt"><a href="#llmstxt" class="heading-anchor">llms.txt</a></h3><p>Template: <code>src/common/llms.njk</code>. Turn it on with <code>generateLlmsTxt</code> (off by default), then edit the sections to match what you want listed.</p><h3 id="testing"><a href="#testing" class="heading-anchor">Testing</a></h3><p>Chromeâs Lighthouse can include an <a href="https://developer.chrome.com/docs/lighthouse/agentic-browsing/scoring" rel="noopener">Agentic browsing</a> category (newer Chrome builds). It looks for things like a valid llms.txt, layout stability, and a useful accessibility tree. This starter already prioritizes accessibility, enabling llms.txt is optional.</p></details><details id="schema"><summary><h2>Schema</h2></summary><p>Schema markup provides additional context for search engines and screen readers. The main schema template is included in the <code><head></code> via <code>src/_includes/head/schema.njk</code>. New schemas should be placed in <code>src/_includes/schemas/</code>.</p><p>To use the âBlogPostingâ schema, set the schema key in the front matter:</p><pre class="language-yaml"><code class="language-yaml"><span class="token punctuation">---</span> 190<span class="token key atrule">schema</span><span class="token punctuation">:</span> BlogPosting 191<span class="token punctuation">---</span></code></pre><p>To use an Event schema for example, create a template at <code>src/_includes/schemas/Event.njk</code>, with something similar to:</p><pre class="language-jinja2"><code class="language-jinja2"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>application/ld+json<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"> 192 <span class="token punctuation">{</span> 193 <span class="token string-property property">"@context"</span><span class="token operator">:</span> <span class="token string">"http://schema.org"</span><span class="token punctuation">,</span> 194 <span class="token string-property property">"@type"</span><span class="token operator">:</span> <span class="token string">"Event"</span><span class="token punctuation">,</span> 195 <span class="token string-property property">"location"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
196 <span class="token string-property property">"@type"</span><span class="token operator">:</span> <span class="token string">"Place"</span><span class="token punctuation">,</span> 197 <span class="token string-property property">"address"</span><span class="token operator">:</span> <span class="token punctuation">{</span> 198 <span class="token string-property property">"@type"</span><span class="token operator">:</span> <span class="token string">"PostalAddress"</span><span class="token punctuation">,</span> 199 <span class="token string-property property">"addressLocality"</span><span class="token operator">:</span> <span class="token string">"<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">event</span><span class="token punctuation">.</span><span class="token variable">data</span><span class="token punctuation">.</span><span class="token variable">place</span><span class="token punctuation">.</span><span class="token variable">city</span> <span class="token delimiter punctuation">}}</span></span>"</span><span class="token punctuation">,</span> 200 <span class="token string-property property">"postalCode"</span><span class="token operator">:</span> <span class="token string">"<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">event</span><span class="token punctuation">.</span><span class="token variable">data</span><span class="token punctuation">.</span><span class="token variable">place</span><span class="token punctuation">.</span><span class="token variable">plz</span> <span class="token delimiter punctuation">}}</span></span>"</span><span class="token punctuation">,</span> 201 <span class="token string-property property">"streetAddress"</span><span class="token operator">:</span> <span class="token string">"<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">event</span><span class="token punctuation">.</span><span class="token variable">data</span><span class="token punctuation">.</span><span class="token variable">place</span><span class="token punctuation">.</span><span class="token variable">street</span> <span class="token delimiter punctuation">}}</span></span>"</span> 202 <span class="token punctuation">}</span><span class="token punctuation">,</span> 203 <span class="token string-property property">"name"</span><span class="token operator">:</span> <span class="token string">"<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">event</span><span class="token punctuation">.</span><span class="token variable">data</span><span class="token punctuation">.</span><span class="token variable">place</span><span class="token punctuation">.</span><span class="token variable">name</span> <span class="token delimiter punctuation">}}</span></span>"</span> 204 <span class="token punctuation">}</span><span class="token punctuation">,</span> 205 <span class="token string-property property">"name"</span><span class="token operator">:</span> <span class="token string">"<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">event</span><span class="token punctuation">.</span><span class="token variable">data</span><span class="token punctuation">.</span><span class="token variable">title</span> <span class="token delimiter punctuation">}}</span></span>"</span><span class="token punctuation">,</span> 206 <span class="token string-property property">"description"</span><span class="token operator">:</span> <span class="token string">"<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">event</span><span class="token punctuation">.</span><span class="token variable">data</span><span class="token punctuation">.</span><span class="token variable">description</span> <span class="token delimiter punctuation">}}</span></span>"</span><span class="token punctuation">,</span> 207 <span class="token string-property property">"startDate"</span><span class="token operator">:</span> <span class="token string">"<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">event</span><span class="token punctuation">.</span><span class="token variable">data</span><span class="token punctuation">.</span><span class="token variable">date</span> <span class="token delimiter punctuation">}}</span></span>"</span><span class="token punctuation">,</span> 208 <span class="token string-property property">"performer"</span><span class="token operator">:</span> <span class="token string">"<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{{</span> <span class="token variable">event</span><span class="token punctuation">.</span><span class="token variable">data</span><span class="token punctuation">.</span><span class="token variable">artist</span> <span class="token delimiter punctuation">}}</span></span>"</span> 209 <span class="token punctuation">}</span> 210</span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span></code></pre><p>And reference it in the front matter:</p><pre class="language-yaml"><code class="language-yaml"><span class="token punctuation">---</span> 211<span class="token key atrule">schema</span><span class="token punctuation">:</span> Event 212<span class="token punctuation">---</span></code></pre></details>
212<details id="svg"><summary><h2>SVG</h2></summary><p>All SVG icons used in the starter are in <code>src/assets/svg</code>. There is a directory dedicated to the dividers, the platform icons and a general folder called âmiscâ.</p><h3 id="shortcodes"><a href="#shortcodes" class="heading-anchor">Shortcodes</a></h3><p>The <code>svg.js</code> shortcodes, introduced in version 3, allow for the seamless inclusion of SVG files. Located in <code>src/_config/shortcodes/svg.js</code>, they require only the folder and file name of the SVG, omitting the file extension. By default, SVGs are treated as decorative (<code>aria-hidden="true"</code>, <code>focusable="false"</code>, <code>tabindex="-1"</code>). Pass a <code>title</code> for informative icons (<code>role="img"</code>, <code><title></code>, <code>aria-labelledby</code>). The SVGs should be stored in the <code>src/assets/svg</code> directory, and referenced using the format <code>"folder/svg-name"</code>.</p><p>There are two shortcodes: <code>svg</code> (positional parameters) and <code>svgKeys</code> (named parameters). Prefer a decorative SVG next to visible or <code>visually-hidden</code> text inside links and buttons. Use <code>title</code> for standalone icons that need their own accessible name.</p><pre class="language-jinja2"><code class="language-jinja2"><span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">svg</span> <span class="token string">"path"</span><span class="token punctuation">,</span> <span class="token string">"title"</span><span class="token punctuation">,</span> <span class="token string">"class-name"</span><span class="token punctuation">,</span> <span class="token string">"inline-style"</span> <span class="token delimiter punctuation">%}</span></span> 213<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">svg</span> <span class="token string">"misc/star"</span><span class="token punctuation">,</span> <span class="token string">"A yellow star icon"</span><span class="token punctuation">,</span> <span class="token string">"spin"</span><span class="token punctuation">,</span> <span class="token string">"block-size: 4ex; fill: var(--color-tertiary);"</span> <span class="token delimiter punctuation">%}</span></span> 214<span class="token jinja2 language-jinja2"><span class="token delimiter punctuation">{%</span> <span class="token tag keyword">svgKeys</span> <span class="token punctuation">{</span> 215 <span class="token string">"src"</span><span class="token punctuation">:</span> <span class="token string">"misc/star"</span><span class="token punctuation">,</span> 216 <span class="token string">"title"</span><span class="token punctuation">:</span> <span class="token string">"A blue star icon"</span><span class="token punctuation">,</span> 217 <span class="token string">"className"</span><span class="token punctuation">:</span> <span class="token string">"spin"</span><span class="token punctuation">,</span> 218 <span class="token string">"styleName"</span><span class="token punctuation">:</span> <span class="token string">"block-size: 4ex; fill: var(--color-secondary);"</span> 219<span class="token punctuation">}</span> <span class="token delimiter punctuation">%}</span></span></code></pre><p><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 24 24" role="img" aria-labelledby="svg-title-a-yellow-star-icon" style="block-size: 4ex; fill: var(--color-tertiary)" class="spin"><title id="svg-title-a-yellow-star-icon">A yellow star icon</title><path fill-rule="evenodd" d="M10.788 3.21c.448-1.077 1.976-1.077 2.424 0l2.082 5.007 5.404.433c1.164.093 1.636 1.545.749 2.305l-4.117 3.527 1.257 5.273c.271 1.136-.964 2.033-1.96 1.425L12 18.354 7.373 21.18c-.996.608-2.231-.29-1.96-1.425l1.257-5.273-4.117-3.527c-.887-.76-.415-2.212.749-2.305l5.404-.433z" clip-rule="evenodd"></path></svg></p><p><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 24 24" role="img" aria-labelledby="svg-title-a-blue-star-icon" style="block-size: 4ex; fill: var(--color-secondary)" class="spin"><title id="svg-title-a-blue-star-icon">A blue star icon</title><path fill-rule="evenodd" d="M10.788 3.21c.448-1.077 1.976-1.077 2.424 0l2.082 5.007 5.404.433c1.164.093 1.636 1.545.749 2.305l-4.117 3.527 1.257 5.273c.271 1.136-.964 2.033-1.96 1.425L12 18.354 7.373 21.18c-.996.608-2.231-.29-1.96-1.425l1.257-5.273-4.117-3.527c-.887-.76-.415-2.212.749-2.305l5.404-.433z" clip-rule="evenodd"></path></svg></p><p>The star icon resolves to something like:</p><pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>svg</span> <span class="token attr-name">â¦</span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>img<span class="token punctuation">"</span></span> <span class="token attr-name">aria-labelledby</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>svg-title-a-yellow-star-icon<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>spin<span class="token punctuation">"</span></span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css">â¦</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>svg-title-a-yellow-star-icon<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>A yellow star icon<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>path</span><span class="token punctuation">></span></span>â¦<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>path</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>svg</span><span class="token punctuation">></span></span></code></pre></details>
219<details id="tags"><summary><h2>Tags</h2></summary><p>This was probably the most opinionated decision: tags have been integrated since version 2.0.</p><p>The tags are placed in the front matter of the posts, using the syntax</p><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">tags</span><span class="token punctuation">:</span> 220 <span class="token punctuation">-</span> image 221 <span class="token punctuation">-</span> feature</code></pre><p>or</p><pre class="language-md"><code class="language-md">tags: ['markdown', 'feature']</code></pre><p>If you generally do not want any tags to show, it is probably easiest to not create any at all.</p></details><details id="template-languages"><summary><h2>Template Languages</h2></summary><p>We use the following <a href="https://www.11ty.dev/docs/languages/" rel="noopener">template languages</a>:</p><ul><li><a href="https://www.11ty.dev/docs/languages/nunjucks/" rel="noopener">Nunjucks</a></li><li><a href="https://www.11ty.dev/docs/languages/markdown/" rel="noopener">Markdown</a></li><li><a href="https://www.11ty.dev/docs/languages/webc/" rel="noopener">WebC</a></li></ul><h3 id="nunjucks"><a href="#nunjucks" class="heading-anchor">Nunjucks</a></h3><p>Nunjucks Docs: <a href="https://mozilla.github.io/nunjucks/templating.html" rel="noopener">https://mozilla.github.io/nunjucks/templating.html</a></p><p><a href="https://github.com/HookyQR/prettier-plugin-jinja-template" rel="noopener">prettier-plugin-jinja-template</a> is used for Nunjucks code formatting. It is configured in <code>.prettierrc</code>.</p><p>I use <a href="https://github.com/samuelcolvin/jinjahtml-vscode" rel="noopener">Better Jinja</a> for syntax highlighting. After installing it for your code editor, you can configure the file associations in your settings:</p><pre class="language-plaintext"><code class="language-plaintext">{ 222 "files.associations": { 223 "*.njk": "jinja-html" 224 } 225}</code></pre><p>Nunjucks was inspired by Jinja2, which is a Python templating engine. The syntax is very similar. I did not find good syntax-highlighting and formatting solutions specifically made for Nunjucks, but everything Jinja2 works just fine.</p><h3 id="markdown"><a href="#markdown" class="heading-anchor">Markdown</a></h3><p>Eleventy uses <a href="https://github.com/markdown-it/markdown-it" rel="noopener">markdown-it</a> for markdown files (.md). The custom configuration for this starter is in <code>src/_config/plugins/markdown.js</code>.</p><h3 id="webc"><a href="#webc" class="heading-anchor">WebC</a></h3><p><a href="https://github.com/11ty/webc" rel="noopener">WebC</a> is standalone HTML serializer for generating markup for Web Components. We use the <a href="https://github.com/11ty/eleventy-plugin-webc" rel="noopener">WebC plugin</a> for Eleventy.</p><p>We combine WebC components with Nunjucks partials:</p><pre class="language-bash"><code class="language-bash">src/_includes/webc/custom-card.webc <span class="token comment"># WebC component definition</span> 226src/_includes/partials/card-blog.njk <span class="token comment"># Nunjucks partial with logic</span> 227src/_includes/partials/card-tag.njk <span class="token comment"># Another partial using the same component</span></code></pre></details><details id="tests"><summary><h2>Tests</h2></summary><p>You can run local automated accessibility tests with <a href="https://github.com/pa11y/pa11y-ci" rel="noopener">pa11y-ci</a>.</p><p><code>src/common/pa11y.njk</code> generates a <em>pa11y-ci</em> config file for all pages in the sitemap by default. You can also define custom paths to test in <code>src/_data/meta.js</code>, within <code>tests.pa11y.customPaths</code>.</p><p>To run the tests, use the following command:</p><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> run test:a11y</code></pre><p>This command will:</p><ol><li>Build the site in test environment</li><li>Start a preview server on <code>localhost:8080</code></li><li>Run <code>pa11y-ci</code> accessibility tests against all pages in the sitemap, or the custom paths.</li></ol><h3 id="ignoring-rules"><a href="#ignoring-rules" class="heading-anchor">Ignoring Rules</a></h3><p>To ignore specific rules, you can add them to the front matter of a template:</p><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">pa11yIgnore</span><span class="token punctuation">:</span> 228 <span class="token punctuation">-</span> <span class="token string">"WCAG2AA.Principle1.Guideline1_4.1_4_3.G145.Fail"</span> 229 <span class="token punctuation">-</span> <span class="token string">"WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail"</span></code></pre><p>or globally in <code>src/_data/meta.js</code>, within <code>tests.pa11y.globalIgnore</code>.</p></details>
229<details id="theme-dark-/-light-mode"><summary><h2>Theme (dark / light mode)</h2></summary><p>With version 2.0 I introduced dark and light mode. This is not intended to be a gimmick, but to support the accessibility of the site. And I tend to read blog posts at night, and if there is no dark mode then I leave the tab for later, and we all know how that ends.</p><p>Dark and light mode respects the user agent or operating system settings using the <code>prefers-color-scheme</code> CSS media feature. But there is also a switch in the <code><footer></code>.</p><p>If you want to change something here, for example replace the button with a sun/moon icon, make sure to preserve accessibility.</p><p>The accessible name (âTheme darkâ or âTheme lightâ) is put together by <code>aria-labelledby="theme-switcher-label"</code> resolving to âThemeâ and the <code><button></code>s âlightâ and âdarkâ.</p><p>If your site is in another language, you can adjust those values in <code>_data/meta.js</code>, for both the content of the <code><button></code>s and their <code>aria-label</code>.</p><p>If you remove the âlightâ and âdarkâ <code><button></code> and you donât use the heading with the ID <code>theme-switcher-label</code>, the value for the accessible name must be obtained differently.</p><p>If you find any accessibility issues, please let me know.</p></details><details id="video"><summary><h2>Video</h2></summary><h3 id="youtube"><a href="#youtube" class="heading-anchor">YouTube</a></h3><p><code><custom-youtube></code> is a wrapper around <a href="https://github.com/paulirish/lite-youtube-embed" rel="noopener">Lite YouTube Embed</a>, optimizing video playback for performance.</p><p><code>@slug</code> video ID<br><code>@start</code> (optional) start time in seconds<br><code>@label</code> Used for accessibility and the <code><custom-youtube-link></code> fallback<br><code>@poster</code> (optional) custom poster image URL (with <a href="https://v1.opengraph.11ty.dev/" rel="noopener">https://v1.opengraph.11ty.dev/</a>)<br><code>@posterSize</code> (optional, default: âautoâ) size passed in to custom poster<br><code>@jsapi</code> (optional, default: âundefinedâ) Enables <a href="https://github.com/paulirish/lite-youtube-embed?tab=readme-ov-file#access-the-youtube-iframe-player-api" rel="noopener">JavaScript API support</a>.</p><pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>custom-youtube</span> 230 <span class="token attr-name">@slug</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>4jY5Zn_pz0M<span class="token punctuation">"</span></span> 231 <span class="token attr-name">@label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Alberto Ballesteros - Si veo que me animo<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> 232<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>custom-youtube</span><span class="token punctuation">></span></span></code></pre><div><custom-youtube class="flow"><!-- component composition by: https://github.com/zachleat/zachleat.com --> 233<style> 234 /* Hide without JS */ 235 is-land:not(:defined).video-wrapper { 236 display: none; 237 } 238</style> 239 240 <is-land on:visible class="video-wrapper"> 241 <lite-youtube videoid="4jY5Zn_pz0M" js-api playlabel="Play: Alberto Ballesteros - Si veo que me animo" style="background-image: var(--yt-poster-img-url); --yt-poster-img-url-lazy: url('https://v1.opengraph.11ty.dev/https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3D4jY5Zn_pz0M/auto/jpeg/')"></lite-youtube> 242 243 <template data-island="once"> 244 <style> 245 lite-youtube { 246 max-inline-size: 100% !important; 247 background-size: cover; 248 } 249 250 is-land lite-youtube { 251 background-color: #eee; 252 border-radius: 0.2em; 253 background-size: cover; 254 } 255 is-land[ready] lite-youtube { 256 /* gotta set in `style` to override the 480w image from lite-youtube */ 257 --yt-poster-img-url: var(--yt-poster-img-url-lazy); 258 } 259 260 .video-wrapper { 261 aspect-ratio: 16 / 9; 262 width: 100%; 263 } 264 265 is-land.video-wrapper { 266 display: block; 267 } 268 </style> 269 <link rel="stylesheet" href="/assets/components/lite-yt-embed.css" /> 270
270<script type="module" src="/assets/components/lite-yt-embed.js"></script>
270 271 </template> 272 </is-land> 273 274<custom-youtube-link label="Alberto Ballesteros - Si veo que me animo" href="https://youtube.com/watch?v=4jY5Zn_pz0M"><style> 275 custom-youtube-link { 276 display: flex; 277 align-items: flex-start; 278 gap: var(--space-xs); 279 font-size: var(--size-step-min-1); 280 } 281 custom-youtube-link svg { 282 font-size: var(--size-step-0); 283 margin-block-start: 0.1em; 284 } 285</style> 286 287<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ariahidden="true"><path d="M2.5 17a24.12 24.12 0 0 1 0-10 2 2 0 0 1 1.4-1.4 49.56 49.56 0 0 1 16.2 0A2 2 0 0 1 21.5 7a24.12 24.12 0 0 1 0 10 2 2 0 0 1-1.4 1.4 49.55 49.55 0 0 1-16.2 0A2 2 0 0 1 2.5 17"></path><path d="m10 15 5-3-5-3z"></path></svg> 288 289<a href="https://youtube.com/watch?v=4jY5Zn_pz0M">Alberto Ballesteros - Si veo que me animo</a> 290 291<!-- Inspired by https://github.com/zachleat/zachleat.com --> 292</custom-youtube-link> 293</custom-youtube></div><h3 id="peertube"><a href="#peertube" class="heading-anchor">PeerTube</a></h3><p><code><custom-peertube></code> is a wrapper around <a href="https://joinpeertube.org/" rel="noopener">PeerTube</a>âs embed system.</p><p><code>@instance</code> PeerTube domain hosting the video<br><code>@embed-slug</code> â The unique ID used in the embed URL.<br><code>@start</code> (optional) start time<br><code>@slug</code> unique video identifier for direct links in the <code><custom-peertube-link></code> fallback<br><code>@label</code> Used for accessibility and the <code><custom-peertube-link></code> fallback.</p><pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>custom-peertube</span> 294 <span class="token attr-name">@instance</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>peertube.tv<span class="token punctuation">"</span></span> 295 <span class="token attr-name">@slug</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>s2oueWPpbhhUrWM6tewHYf<span class="token punctuation">"</span></span> 296 <span class="token attr-name">@embed-slug</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>d2bf34fc-6cee-455e-86dc-b7e8979fd1a2<span class="token punctuation">"</span></span> 297 <span class="token attr-name">@label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>Morgan the Organ on Tour<span class="token punctuation">'</span></span><span class="token punctuation">></span></span> 298<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>custom-peertube</span><span class="token punctuation">></span></span></code></pre><div><custom-peertube class="flow"> 299 300<style> 301 /* Hide without JS */ 302 is-land:not(:defined).video-wrapper { 303 display: none; 304 } 305 306 is-land.video-wrapper { 307 display: block; 308 } 309 310 custom-peertube iframe { 311 position: absolute; 312 inset: 0px; 313 width: 100%; 314 height: 100%; 315 } 316 317 custom-peertube .video-wrapper { 318 overflow: hidden; 319 position: relative; 320 aspect-ratio: 16 / 9; 321 width: 100%; 322 border-radius: 0.2em; 323 } 324</style> 325 326 327 <is-land on:visible class="video-wrapper relative"> 328 <iframe title="Morgan the Organ on Tour" src="https://peertube.tv/videos/embed/d2bf34fc-6cee-455e-86dc-b7e8979fd1a2?title=0&warningTitle=0" frameborder="0" allowfullscreen sandbox="allow-same-origin allow-scripts allow-popups allow-forms"> 329 </iframe> 330 </is-land> 331 332 333<custom-peertube-link label="Morgan the Organ on Tour" href="https://peertube.tv/w/s2oueWPpbhhUrWM6tewHYf"><style> 334 custom-peertube-link { 335 display: flex; 336 align-items: flex-start; 337 gap: var(--space-xs); 338 font-size: var(--size-step-min-1); 339 } 340 custom-peertube-link svg { 341 font-size: var(--size-step-0); 342 margin-block-start: 0.1em; 343 } 344</style> 345 346<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor" width="1em" height="1em" ariahidden="true"><title>PeerTube</title><path d="M12 6.545v10.91L20.727 12M3.273 12v12L12 17.455M3.273 0v12L12 6.545"></path></svg> 347 348<a href="https://peertube.tv/w/s2oueWPpbhhUrWM6tewHYf">Morgan the Organ on Tour</a> 349 350<!-- Inspired by https://github.com/zachleat/zachleat.com --> 351</custom-peertube-link> 352</custom-peertube></div></details>
352<details id="what-can-be-deleted"><summary><h2>What can be deleted</h2></summary><p><strong>Everything, of course</strong><br>This is still meant to be a starter, even though it grew to be more like a template.<br>If you want to keep the defaults, but get rid of the example content, delete the following files and archives:</p><ul><li><code>github.js</code> in <code>src/_data/</code></li><li><code>builtwith.json</code> in <code>src/_data/</code></li><li>all files in <code>src/posts</code></li><li>the directory and all files in <code>src/docs</code></li><li>all pages in <code>src/pages</code>, though you might want to keep <code>index.njk</code> as a starting point</li><li>You can delete <code>screenshots</code>, <code>blog</code> and <code>gallery</code> in <code>src/assets/images</code>.<br>Keep the <code>favicon</code> and <code>template</code> folders though.</li></ul><p>If you donât want to feature any code examples, you may delete the whole stylesheet for syntax highlighting: <code>src/assets/css/global/blocks/code.css</code>.<br>In general, any CSS block in there is optional.</p></details></div></div></main><footer class="site-footer mt-l-xl p-s-m"><div class="wrapper"><div class="cluster gutter-xs"><nav aria-label="Complementary" class="cluster text-step-0">© 2026 <a href="/"><span class="font-display">Eleventy Excellent</span> <small>4.8.0</small> </a><a href="/styleguide/">Style guide</a> <a href="/imprint/">Imprint</a> <a href="/privacy/">Privacy</a> <a href="/accessibility/">Accessibility</a></nav><nav aria-label="Platforms" class="cluster"><a href="/feed.xml" rel="alternate" type="application/atom+xml" data-icon class="button"><span class="visually-hidden">Atom Feed</span> <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true" focusable="false" tabindex="-1"><path d="M4 11a9 9 0 0 1 9 9M4 4a16 16 0 0 1 16 16"></path><circle cx="5" cy="19" r="1"></circle></svg> </a><a data-icon href="https://github.com/madrilene/eleventy-excellent" rel="me" class="no-indicator button"><span class="visually-hidden">Github</span> <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true" focusable="false" tabindex="-1"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65S8.93 17.38 9 18v4"></path><path d="M9 18c-4.51 2-5-2-7-2"></path></svg> </a><a data-icon href="https://front-end.social/@lene" rel="me" class="no-indicator button"><span class="visually-hidden">Mastodon</span> <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true" focusable="false" tabindex="-1"><path d="M21.887 5.996c-.308-2.314-2.298-4.14-4.655-4.492-.399-.059-1.906-.278-5.398-.278h-.026c-3.495 0-4.243.219-4.642.278-2.295.345-4.387 1.982-4.897 4.326-.242 1.153-.268 2.433-.222 3.606.065 1.684.078 3.361.228 5.039a24 24 0 0 0 .543 3.304c.484 2.009 2.439 3.68 4.355 4.359 2.049.71 4.256.829 6.368.342q.347-.085.69-.193c.513-.165 1.114-.351 1.559-.676q.009-.006.013-.016.006-.01.007-.024v-1.624s0-.013-.007-.02q-.002-.01-.013-.016l-.02-.01h-.019a17.4 17.4 0 0 1-4.132.49c-2.397 0-3.041-1.153-3.224-1.63a5.3 5.3 0 0 1-.281-1.287q0-.01.003-.019.002-.012.013-.017l.02-.01h.023c1.33.325 2.697.491 4.067.491.33 0 .657 0 .987-.01 1.376-.04 2.828-.11 4.184-.378.033-.007.069-.013.098-.02 2.138-.418 4.172-1.724 4.378-5.032.006-.129.026-1.365.026-1.498 0-.461.147-3.258-.023-4.979z"></path><path fill="currentColor" stroke-width="1" d="M6.061 7.288c0-.676.533-1.219 1.193-1.219.661 0 1.193.547 1.193 1.219 0 .673-.532 1.22-1.193 1.22-.66 0-1.193-.547-1.193-1.22Z"></path></svg> </a><a data-icon href="https://bsky.app/profile/lenesaile.com" rel="me" class="no-indicator button"><span class="visually-hidden">Bluesky</span> <svg xmlns="http://www.w3.org/2000/svg" width="1em" height=".9em" fill="var(--color-bg)" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true" focusable="false" tabindex="-1"><path d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908
3520 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364q.204-.03.415-.056-.207.033-.415.056c-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a9 9 0 0 1-.415-.056q.21.026.415.056c2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z"></path></svg> </a><a data-icon href="https://ko-fi.com/lenesaile" rel="me" class="no-indicator button"><span class="visually-hidden">Kofi</span> <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true" focusable="false" tabindex="-1"><path stroke-linecap="round" stroke-linejoin="round" d="M18 3h1c1.06 0 2.078.527 2.828 1.464C22.578 5.402 23 6.674 23 8s-.421 2.598-1.172 3.536C21.078 12.473 20.061 13 19 13h-1M1 3h17v12.461c0 1.47-.512 2.878-1.423 3.917-.91 1.038-2.146 1.622-3.434 1.622H5.857c-1.288 0-2.523-.584-3.434-1.622S1 16.93 1 15.46z"></path><path fill="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M13.288 8.707a2.43 2.43 0 0 0-2.65-.52c-.3.12-.57.3-.8.53l-.34.34-.35-.34a2.43 2.43 0 0 0-3.44 0c-.95.939-1 2.527.2 3.736L9.497 16l3.6-3.547c1.2-1.208 1.14-2.797.19-3.736v-.01Z"></path></svg></a></nav></div><is-land on:idle><div role="region" aria-labelledby="theme-switcher-label" data-theme-switcher style="--cluster-horizontal-alignment: center" class="theme-switch | cluster gutter-s mt-xs"><h2 id="theme-switcher-label">Theme</h2><button id="light-theme-toggle" data-theme="light" data-ghost-button data-small-button class="button"><span>light</span></button> <button id="dark-theme-toggle" data-theme="dark" data-ghost-button data-small-button class="button"><span>dark</span></button></div></is-land><aside class="creator | cluster gutter-2xs text-step-min-1 mt-xl">Made with <span class="visually-hidden">love</span> <svg width="1em" height="1em" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true" focusable="false" tabindex="-1"><path d="M7 3C4.239 3 2 5.216 2 7.95c0 2.207.875 7.445 9.488 12.74a.99.99 0 0 0 1.024 0C21.125 15.395 22 10.157 22 7.95 22 5.216 19.761 3 17 3s-5 3-5 3-2.239-3-5-3"></path></svg> and <a href="https://www.11ty.dev/" class="no-indicator"><span class="visually-hidden">Eleventy</span> <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="none" viewBox="0 0 29 29" aria-hidden="true" focusable="false" tabindex="-1"><path fill="currentColor" d="M9.156 22.425q-.324 0-.324-.878V7.08q0-.419-.168-.394l-1.035.263q-.284.026-.284-.576V5.116q0-.523.31-.588l3.415-.89a.5.5 0 0 1 .116-.012q.284 0 .283.615V21.55q0 .878-.336.877zm8.846.027q-.518 0-.899-.073a2.4 2.4 0 0 1-.795-.328 2 2 0 0 1-.68-.7q-.265-.446-.432-1.245t-.168-1.899v-6.714q0-.263-.142-.263h-.802q-.324 0-.324-.627v-.708q0-.64.324-.641h.803q.14 0 .141-.353l.233-5.093q.039-.693.336-.693h1.295q.324 0 .324.693v5.093q-.001.354.156.353h1.65q.324 0 .324.641v.708q0 .627-.324.627h-1.655q-.09 0-.122.046-.032.045-.031.217v6.756q0 .759.096 1.264.097.504.283.739.188.235.375.313.188.08.459.078h.917q.349.001.348.484v.785q0 .537-.374.537zm4.964 1.57q.296 0 .516-.49.22-.493.221-1.396 0-.211-.26-1.454l-2.536-10.67a2 2 0 0 1-.026-.29q0-.47.195-.47h1.566q.18 0 .297.123.12.124.18.503l1.681 9.008q.051.157.065.158.052-.002.053-.144l1.319-8.983q.052-.392.16-.53a.34.34 0 0 1 .28-.138h1.085q.22 0 .22.51 0 .118-.026.302l-2.303 12.138q-.271 1.506-.595 2.305-.324.798-.726 1.086a1.85 1.85 0 0 1-1.061.302h-.13q-1.048 0-1.422-.564-.104-.118-.103-.603 0-.97.31-.97.026 0 .466.131.445.138.574.136M2.648 22.45q-.324.001-.324-.877V7.105q0-.42-.169-.394l-1.035.262Q.836 7 .837 6.397V5.14q0-.521.31-.588l3.417-.892a.5.5 0 0 1 .116-.012q.284 0 .283.615v17.309q0 .878-.336.877z"></path></svg> </a>by <a href="https://www.lenesaile.com">Lene Saile</a></aside></div></footer>
352<script type="module" src="/assets/scripts/components/custom-easteregg.js"></script>
352<custom-easteregg></custom-easteregg></body> 353</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.