diff --git a/package-lock.json b/package-lock.json index 02099435..e36fb622 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,10 @@ "name": "jbuk-frontend", "version": "0.0.1", "license": "CC0-1.0", + "dependencies": { + "@11ty/is-land": "^4.0.0", + "@zachleat/snow-fall": "^1.0.2" + }, "devDependencies": { "@babel/core": "^7.23.6", "@babel/preset-env": "^7.23.6", @@ -35,6 +39,15 @@ "webpack-cli": "^5.1.4" } }, + "node_modules/@11ty/is-land": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@11ty/is-land/-/is-land-4.0.0.tgz", + "integrity": "sha512-RxbjF2+FzSu3rerHrWLRsvsPX2YM47RwXpdWCCzLhwRSsz5sJe9TnK7mphEld1gZnp2GeD5ByvhqjIc4CqidsQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -2591,6 +2604,11 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "node_modules/@zachleat/snow-fall": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@zachleat/snow-fall/-/snow-fall-1.0.2.tgz", + "integrity": "sha512-nyNeliowryq+roSMktyV3o14DduSuU4BvBzruVSPV6e8U8Eid2zNzSj1AzCQByPId7Q4MrIP2QWL2UHeHGfmcA==" + }, "node_modules/acorn": { "version": "8.9.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", @@ -7566,6 +7584,11 @@ } }, "dependencies": { + "@11ty/is-land": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@11ty/is-land/-/is-land-4.0.0.tgz", + "integrity": "sha512-RxbjF2+FzSu3rerHrWLRsvsPX2YM47RwXpdWCCzLhwRSsz5sJe9TnK7mphEld1gZnp2GeD5ByvhqjIc4CqidsQ==" + }, "@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -9353,6 +9376,11 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "@zachleat/snow-fall": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@zachleat/snow-fall/-/snow-fall-1.0.2.tgz", + "integrity": "sha512-nyNeliowryq+roSMktyV3o14DduSuU4BvBzruVSPV6e8U8Eid2zNzSj1AzCQByPId7Q4MrIP2QWL2UHeHGfmcA==" + }, "acorn": { "version": "8.9.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", diff --git a/package.json b/package.json index 68337dbc..e21d362a 100644 --- a/package.json +++ b/package.json @@ -39,5 +39,9 @@ "> 1%", "not IE 11", "not IE_Mob 11" - ] + ], + "dependencies": { + "@11ty/is-land": "^4.0.0", + "@zachleat/snow-fall": "^1.0.2" + } } diff --git a/public/assets/app.css b/public/assets/app.css index c935785f..fc94aaab 100644 --- a/public/assets/app.css +++ b/public/assets/app.css @@ -1 +1 @@ -:root{--font-family-headings:"Archer SSm A","Archer SSm B",serif;--font-family-body:"Verlag A","Verlag B",sans-serif;--font-family-monospace:"Operator Mono SSm A","Operator Mono SSm B",monospace;--font-size-sm:0.75rem;--font-size-base:1rem;--font-size-md:1.25rem;--font-size-lg:1.5rem;--font-size-xl:1.75rem;--font-size-xxl:2rem;--font-size-xxxl:2.25rem;--color-primary:#334700;--color-secondary:#e3ffb7;--color-link:#00649e;--color-link-visited:#bc7aff;--color-primary-shadow:rgba(16,25,0,.4)}@supports (color:color(display-p3 0 0 0)){:root{--color-primary:color(display-p3 0.21567 0.27838 0.03615)}}@supports (color:oklab(0% 0 0)){:root{--color-primary:oklch(36.8% 0.1 125.505deg)}}@supports (color:color(display-p3 0 0 0)){:root{--color-secondary:color(display-p3 0.91016 0.99842 0.74082)}}@supports (color:oklab(0% 0 0)){:root{--color-secondary:oklch(96.3% 0.1 125.505deg)}}@supports (color:color(display-p3 0 0 0)){:root{--color-link:color(display-p3 0.01045 0.38351 0.63618)}}@supports (color:oklab(0% 0 0)){:root{--color-link:oklch(48.09% 0.146 241.41deg)}}@supports (color:color(display-p3 0 0 0)){:root{--color-link-visited:color(display-p3 0.70467 0.47549 0.99958)}}@supports (color:oklab(0% 0 0)){:root{--color-link-visited:oklch(70.44% 0.21 304.41deg)}}@supports (color:color(display-p3 0 0 0)){:root{--color-primary-shadow:color(display-p3 0.06762 0.09646 0.00441/0.4)}}@supports (color:oklab(0% 0 0)){:root{--color-primary-shadow:oklch(19.56% 0.054 125.505deg/40%)}}body{background-color:var(--color-secondary);color:var(--color-primary);font-family:var(--font-family-body);font-size:var(--font-size-md)}code{font-family:var(--font-family-monospace)}h1,h2,h3,h4,h5,h6{font-family:var(--font-family-headings)}.grid{display:grid;grid-template-columns:5vw 1fr 5vw;grid-template-rows:-webkit-min-content 1fr -webkit-min-content;grid-template-rows:min-content 1fr min-content;row-gap:1rem}#site-header{grid-column:2/3;grid-row:1/2}main{grid-column:2/3;grid-row:2/3}.h-feed{-webkit-box-orient:vertical;-webkit-box-direction:normal;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;gap:2rem}.h-entry h1:first-of-type,.h-entry p:first-of-type{-webkit-margin-before:0;margin-block-start:0}.pagination{-webkit-margin-before:1rem;margin-block-start:1rem}footer{grid-column:2/3;grid-row:3/4}footer .iwc-logo{max-width:85vw}footer .footer-actions{-webkit-box-orient:horizontal;-webkit-box-direction:normal;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;gap:1rem}a{color:var(--color-link)}a:visited{color:var(--color-link-visited)}#site-header a:visited,a.auth:visited{color:var(--color-link)}.hljs{border-radius:.5rem}.h-card .hovercard{-webkit-box-orient:vertical;-webkit-box-direction:normal;background-color:var(--color-secondary);border-radius:1rem;-webkit-box-shadow:0 .5rem .5rem .5rem var(--color-primary-shadow);box-shadow:0 .5rem .5rem .5rem var(--color-primary-shadow);display:none;-ms-flex-direction:column;flex-direction:column;gap:.5rem;opacity:0;padding:1rem;position:absolute;-webkit-transition:opacity .5s ease-in-out;transition:opacity .5s ease-in-out;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;z-index:100}.h-card .hovercard .u-photo{max-width:6rem}.h-card .hovercard .social-icon{height:1rem;width:1rem}.h-card:hover .hovercard{display:-webkit-box;display:-ms-flexbox;display:flex;opacity:1}.h-entry{-webkit-border-start:1px solid var(--color-primary);-webkit-padding-start:.5rem;border-inline-start:1px solid var(--color-primary);padding-inline-start:.5rem}.h-entry .reply-to{font-style:italic}.h-entry .post-info a{text-decoration:none}.h-entry .note-metadata{-webkit-box-orient:horizontal;-webkit-box-direction:normal;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;gap:1rem}.h-entry .note-metadata .likes,.h-entry .note-metadata .replies,.h-entry .note-metadata .reposts{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-direction:row;flex-direction:row;gap:.5rem}.h-entry .note-metadata .syndication-links{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap}.h-entry .note-metadata .syndication-links a{text-decoration:none}.h-entry .note-metadata .syndication-links a svg{height:1rem;width:1rem}.feather{stroke:currentcolor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;height:24px;width:24px}.sr-only{clip:rect(0 0 0 0);-webkit-clip-path:inset(50%);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}main>.u-comment{-webkit-margin-before:2rem;-webkit-margin-start:2rem;-webkit-border-start:1px solid var(--color-primary);-webkit-padding-start:.5rem;border-inline-start:1px solid var(--color-primary);margin-block-start:2rem;margin-inline-start:2rem;padding-inline-start:.5rem}main>.u-comment .mini-h-card{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-direction:row;flex-direction:row}main>.u-comment .mini-h-card .u-photo{-webkit-margin-after:.5rem;border-radius:50%;height:2rem;margin-block-end:.5rem;width:2rem}main .notes-subtitle{font-size:1.2rem;font-weight:600}main .webmentions-author-list{-webkit-box-orient:horizontal;-webkit-box-direction:normal;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;gap:1rem}main .webmentions-author-list img{border-radius:50%;height:4rem;width:4rem} +:root{--font-family-headings:"Archer SSm A","Archer SSm B",serif;--font-family-body:"Verlag A","Verlag B",sans-serif;--font-family-monospace:"Operator Mono SSm A","Operator Mono SSm B",monospace;--font-size-sm:0.75rem;--font-size-base:1rem;--font-size-md:1.25rem;--font-size-lg:1.5rem;--font-size-xl:1.75rem;--font-size-xxl:2rem;--font-size-xxxl:2.25rem;--color-primary:#334700;--color-secondary:#e3ffb7;--color-link:#00649e;--color-link-visited:#bc7aff;--color-primary-shadow:rgba(16,25,0,.4)}@supports (color:color(display-p3 0 0 0)){:root{--color-primary:color(display-p3 0.21567 0.27838 0.03615)}}@supports (color:oklab(0% 0 0)){:root{--color-primary:oklch(36.8% 0.1 125.505deg)}}@supports (color:color(display-p3 0 0 0)){:root{--color-secondary:color(display-p3 0.91016 0.99842 0.74082)}}@supports (color:oklab(0% 0 0)){:root{--color-secondary:oklch(96.3% 0.1 125.505deg)}}@supports (color:color(display-p3 0 0 0)){:root{--color-link:color(display-p3 0.01045 0.38351 0.63618)}}@supports (color:oklab(0% 0 0)){:root{--color-link:oklch(48.09% 0.146 241.41deg)}}@supports (color:color(display-p3 0 0 0)){:root{--color-link-visited:color(display-p3 0.70467 0.47549 0.99958)}}@supports (color:oklab(0% 0 0)){:root{--color-link-visited:oklch(70.44% 0.21 304.41deg)}}@supports (color:color(display-p3 0 0 0)){:root{--color-primary-shadow:color(display-p3 0.06762 0.09646 0.00441/0.4)}}@supports (color:oklab(0% 0 0)){:root{--color-primary-shadow:oklch(19.56% 0.054 125.505deg/40%)}}body{background-color:var(--color-secondary);color:var(--color-primary);font-family:var(--font-family-body);font-size:var(--font-size-md)}code{font-family:var(--font-family-monospace)}h1,h2,h3,h4,h5,h6{font-family:var(--font-family-headings)}.grid{display:grid;grid-template-columns:5vw 1fr 5vw;grid-template-rows:-webkit-min-content 1fr -webkit-min-content;grid-template-rows:min-content 1fr min-content;row-gap:1rem}#site-header{grid-column:2/3;grid-row:1/2}main{grid-column:2/3;grid-row:2/3}.h-feed{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;gap:2rem}.h-entry h1:first-of-type,.h-entry p:first-of-type{-webkit-margin-before:0;margin-block-start:0}.pagination{-webkit-margin-before:1rem;margin-block-start:1rem}footer{grid-column:2/3;grid-row:3/4}footer .iwc-logo{max-width:85vw}footer .footer-actions{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;gap:1rem}a{color:var(--color-link)}a:visited{color:var(--color-link-visited)}#site-header a:visited,a.auth:visited{color:var(--color-link)}.hljs{border-radius:.5rem}.h-card .hovercard{background-color:var(--color-secondary);border-radius:1rem;-webkit-box-shadow:0 .5rem .5rem .5rem var(--color-primary-shadow);box-shadow:0 .5rem .5rem .5rem var(--color-primary-shadow);display:none;opacity:0;padding:1rem;position:absolute;-webkit-transition:opacity .5s ease-in-out;transition:opacity .5s ease-in-out;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;z-index:100;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;gap:.5rem}.h-card .hovercard .u-photo{max-width:6rem}.h-card .hovercard .social-icon{height:1rem;width:1rem}.h-card:hover .hovercard{display:-webkit-box;display:-ms-flexbox;display:flex;opacity:1}.h-entry{-webkit-border-start:1px solid var(--color-primary);border-inline-start:1px solid var(--color-primary);-webkit-padding-start:.5rem;padding-inline-start:.5rem}.h-entry .reply-to{font-style:italic}.h-entry .post-info a{text-decoration:none}.h-entry .note-metadata{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;gap:1rem}.h-entry .note-metadata .likes,.h-entry .note-metadata .replies,.h-entry .note-metadata .reposts{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;gap:.5rem;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.h-entry .note-metadata .syndication-links{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap}.h-entry .note-metadata .syndication-links a{text-decoration:none}.h-entry .note-metadata .syndication-links a svg{height:1rem;width:1rem}.feather{height:24px;width:24px;stroke:currentcolor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none}.sr-only{clip:rect(0 0 0 0);-webkit-clip-path:inset(50%);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}main>.u-comment{-webkit-margin-before:2rem;margin-block-start:2rem;-webkit-margin-start:2rem;margin-inline-start:2rem;-webkit-border-start:1px solid var(--color-primary);border-inline-start:1px solid var(--color-primary);-webkit-padding-start:.5rem;padding-inline-start:.5rem}main>.u-comment .mini-h-card{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}main>.u-comment .mini-h-card .u-photo{border-radius:50%;height:2rem;width:2rem;-webkit-margin-after:.5rem;margin-block-end:.5rem}main .notes-subtitle{font-size:1.2rem;font-weight:600}main .webmentions-author-list{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;gap:1rem}main .webmentions-author-list img{border-radius:50%;height:4rem;width:4rem} diff --git a/public/assets/app.css.br b/public/assets/app.css.br index 9f5f9941..1c6315eb 100644 Binary files a/public/assets/app.css.br and b/public/assets/app.css.br differ diff --git a/public/assets/frontend/is-land.js b/public/assets/frontend/is-land.js new file mode 100644 index 00000000..435a13b9 --- /dev/null +++ b/public/assets/frontend/is-land.js @@ -0,0 +1,338 @@ +class Island extends HTMLElement { + static tagName = "is-land"; + static prefix = "is-land--"; + static attr = { + template: "data-island", + ready: "ready", + defer: "defer-hydration", + }; + + static onceCache = new Map(); + static onReady = new Map(); + + static fallback = { + ":not(is-land,:defined,[defer-hydration])": (readyPromise, node, prefix) => { + // remove from document to prevent web component init + let cloned = document.createElement(prefix + node.localName); + for(let attr of node.getAttributeNames()) { + cloned.setAttribute(attr, node.getAttribute(attr)); + } + + // Declarative Shadow DOM (with polyfill) + let shadowroot = node.shadowRoot; + if(!shadowroot) { + let tmpl = node.querySelector(":scope > template:is([shadowrootmode], [shadowroot])"); + if(tmpl) { + let mode = tmpl.getAttribute("shadowrootmode") || tmpl.getAttribute("shadowroot") || "closed"; + shadowroot = node.attachShadow({ mode }); // default is closed + shadowroot.appendChild(tmpl.content.cloneNode(true)); + } + } + + // Cheers to https://gist.github.com/developit/45c85e9be01e8c3f1a0ec073d600d01e + if(shadowroot) { + cloned.attachShadow({ mode: shadowroot.mode }).append(...shadowroot.childNodes); + } + + // Keep *same* child nodes to preserve state of children (e.g. details->summary) + cloned.append(...node.childNodes); + node.replaceWith(cloned); + + return readyPromise.then(() => { + // Restore original children and shadow DOM + if(cloned.shadowRoot) { + node.shadowRoot.append(...cloned.shadowRoot.childNodes); + } + node.append(...cloned.childNodes); + cloned.replaceWith(node); + }); + } + } + + constructor() { + super(); + + // Internal promises + this.ready = new Promise(resolve => { + this.readyResolve = resolve; + }); + } + + // any parents of `el` that are (with conditions) + static getParents(el, stopAt = false) { + let nodes = []; + while(el) { + if(el.matches && el.matches(Island.tagName)) { + if(stopAt && el === stopAt) { + break; + } + + if(Conditions.hasConditions(el)) { + nodes.push(el); + } + } + el = el.parentNode; + } + return nodes; + } + + static async ready(el, parents) { + if(!parents) { + parents = Island.getParents(el); + } + if(parents.length === 0) { + return; + } + let imports = await Promise.all(parents.map(p => p.wait())); + // return innermost module import + if(imports.length) { + return imports[0]; + } + } + + forceFallback() { + if(window.Island) { + Object.assign(Island.fallback, window.Island.fallback); + } + + for(let selector in Island.fallback) { + // Reverse here as a cheap way to get the deepest nodes first + let components = Array.from(this.querySelectorAll(selector)).reverse(); + + // with thanks to https://gist.github.com/cowboy/938767 + for(let node of components) { + if(!node.isConnected) { + continue; + } + + let parents = Island.getParents(node); + // must be in a leaf island (not nested deep) + if(parents.length === 1) { + let p = Island.ready(node, parents); + Island.fallback[selector](p, node, Island.prefix); + } + } + } + } + + wait() { + return this.ready; + } + + async connectedCallback() { + // Only use fallback content with loading conditions + if(Conditions.hasConditions(this)) { + // Keep fallback content without initializing the components + this.forceFallback(); + } + + await this.hydrate(); + } + + getTemplates() { + return this.querySelectorAll(`template[${Island.attr.template}]`); + } + + replaceTemplates(templates) { + // replace