diff --git a/eleventy.config.js b/eleventy.config.js index 91bc1a0..b6d5e7f 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -69,6 +69,8 @@ export default async function (eleventyConfig) { } }); + eleventyConfig.addPlugin(plugins.EleventyNavigationPlugin); + // --------------------- bundle eleventyConfig.addBundle('css', {hoist: true}); @@ -117,6 +119,11 @@ export default async function (eleventyConfig) { eleventyConfig.on('eleventy.after', events.svgToJpeg); } + // Pagefind search index + if (process.env.ELEVENTY_RUN_MODE === 'build') { + eleventyConfig.on('eleventy.after', events.buildPagefind); + } + // --------------------- Passthrough File Copy // -- same path diff --git a/package-lock.json b/package-lock.json index 4dda91c..9496630 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { - "name": "eleventy-excellent", - "version": "4.5.0", + "name": "hypnagaga", + "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "eleventy-excellent", - "version": "4.5.0", + "name": "hypnagaga", + "version": "0.0.1", "license": "ISC", "dependencies": { "@11ty/eleventy": "^3.1.2", "@11ty/eleventy-fetch": "^5.1.0", "@11ty/eleventy-img": "^6.0.4", + "@11ty/eleventy-navigation": "^1.0.4", "@11ty/eleventy-plugin-rss": "^2.0.4", "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.2", "@11ty/eleventy-plugin-webc": "^0.11.2", @@ -42,6 +43,7 @@ "markdown-it-prism": "^3.0.1", "netlify-plugin-cache": "^1.0.3", "pa11y-ci": "^4.0.1", + "pagefind": "^1.4.0", "postcss": "^8.5.6", "postcss-cli": "^11.0.1", "postcss-import": "^16.1.1", @@ -604,6 +606,19 @@ "@img/sharp-win32-x64": "0.33.5" } }, + "node_modules/@11ty/eleventy-navigation": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-navigation/-/eleventy-navigation-1.0.5.tgz", + "integrity": "sha512-zb6xe29cM9viSdYtZywKIkJw2HIROyBINdBcFWC9uD0c/jYOTAex5nwy3HNEuh5t6/Ld/S9V4gEizfmeYuYpCQ==", + "license": "MIT", + "dependencies": { + "dependency-graph": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, "node_modules/@11ty/eleventy-plugin-bundle": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@11ty/eleventy-plugin-bundle/-/eleventy-plugin-bundle-3.0.7.tgz", @@ -2786,7 +2801,6 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "license": "MIT", - "peer": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", diff --git a/package.json b/package.json index 7c4791e..24a9c62 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "eleventy-excellent", - "version": "4.5.0", - "description": "Eleventy starter built around the workflow suggested by Andy Bell's buildexcellentwebsit.es.", - "author": "Lene Saile", + "name": "hypnagaga", + "version": "0.0.1", + "description": "A web site.", + "author": "Ben Aultowski", "license": "ISC", "type": "module", "engines": { @@ -16,8 +16,9 @@ "screenshots": "node ./src/_config/setup/generate-screenshots.js", "dev:11ty": "cross-env ELEVENTY_ENV=development eleventy --serve", "build:11ty": "cross-env ELEVENTY_ENV=production eleventy", + "build:search": "pagefind --site 'dist' --glob '**/*.html'", "start": "npm run dev:11ty", - "build": "npm run clean && npm run build:11ty", + "build": "npm run clean && npm run build:11ty && npm run build:search", "pa11y:build": "npm run clean && cross-env ELEVENTY_ENV=test eleventy", "pa11y:test": "sleep 3 && pa11y-ci --config ./dist/pa11y.json", "pa11y:serve": "ELEVENTY_ENV=test eleventy --serve --ignore-initial", @@ -26,12 +27,13 @@ "keywords": [], "repository": { "type": "git", - "url": "https://github.com/madrilene/eleventy-excellent.git" + "url": "https://git.hypnagaga.com/wires/hypnagaga.git" }, "dependencies": { "@11ty/eleventy": "^3.1.2", "@11ty/eleventy-fetch": "^5.1.0", "@11ty/eleventy-img": "^6.0.4", + "@11ty/eleventy-navigation": "^1.0.4", "@11ty/eleventy-plugin-rss": "^2.0.4", "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.2", "@11ty/eleventy-plugin-webc": "^0.11.2", @@ -62,6 +64,7 @@ "markdown-it-prism": "^3.0.1", "netlify-plugin-cache": "^1.0.3", "pa11y-ci": "^4.0.1", + "pagefind": "^1.4.0", "postcss": "^8.5.6", "postcss-cli": "^11.0.1", "postcss-import": "^16.1.1", diff --git a/src/_config/events.js b/src/_config/events.js index 21b012e..aace2e9 100644 --- a/src/_config/events.js +++ b/src/_config/events.js @@ -1,9 +1,11 @@ import {svgToJpeg} from './events/svg-to-jpeg.js'; import {buildAllCss} from './events/build-css.js'; import {buildAllJs} from './events/build-js.js'; +import {buildPagefind} from './events/build-pagefind-index.js'; export default { svgToJpeg, buildAllCss, - buildAllJs + buildAllJs, + buildPagefind }; diff --git a/src/_config/events/build-pagefind-index.js b/src/_config/events/build-pagefind-index.js new file mode 100644 index 0000000..c998a7b --- /dev/null +++ b/src/_config/events/build-pagefind-index.js @@ -0,0 +1,13 @@ +import {execSync} from 'node:child_process'; + +export const buildPagefind = () => { + console.log('Building Pagefind index...'); + try { + execSync(`npx pagefind --site dist --glob "**/*.html"`, { + encoding: 'utf-8', + stdio: 'inherit' + }); + } catch (error) { + console.error('Pagefind build failed: ', error.message); + } +}; \ No newline at end of file diff --git a/src/_config/plugins.js b/src/_config/plugins.js index 50f54be..03d2d42 100644 --- a/src/_config/plugins.js +++ b/src/_config/plugins.js @@ -4,6 +4,7 @@ import rss from '@11ty/eleventy-plugin-rss'; import syntaxHighlight from '@11ty/eleventy-plugin-syntaxhighlight'; import webc from '@11ty/eleventy-plugin-webc'; import {eleventyImageTransformPlugin} from '@11ty/eleventy-img'; +import EleventyNavigationPlugin from '@11ty/eleventy-navigation'; // custom import {markdownLib} from './plugins/markdown.js'; @@ -18,6 +19,7 @@ export default { syntaxHighlight, webc, eleventyImageTransformPlugin, + EleventyNavigationPlugin, markdownLib, drafts, htmlConfig diff --git a/src/_data/meta.js b/src/_data/meta.js index bcf31f4..4906d93 100644 --- a/src/_data/meta.js +++ b/src/_data/meta.js @@ -1,24 +1,24 @@ export const url = process.env.URL || 'http://localhost:8080'; // Extract domain from `url` export const domain = new URL(url).hostname; -export const siteName = 'Eleventy Excellent'; -export const siteDescription = 'Eleventy starter for building modern, resilient websites'; +export const siteName = 'Hypnagaga'; +export const siteDescription = 'A vision quest.'; export const siteType = 'Person'; // schema export const locale = 'en_EN'; export const lang = 'en'; export const skipContent = 'Skip to content'; export const author = { - name: 'Lene Saile', // i.e. Lene Saile - page / blog author's name. Must be set. + name: 'Ben Aultowski', avatar: '/icon-512x512.png', // path to the author's avatar. In this case just using a favicon. - email: 'hola@lenesaile.com', // i.e. hola@lenesaile.com - email of the author - website: 'https://www.lenesaile.com', // i.e. https.://www.lenesaile.com - the personal site of the author - fediverse: '@lene@front-end.social' // used for highlighting journalism on the fediverse. Can be Mastodon, Flipboard, Threads, WordPress (with the ActivityPub plugin installed), PeerTube, Pixelfed, etc. https://blog.joinmastodon.org/2024/07/highlighting-journalism-on-mastodon/ + email: 'benaultowski@proton.me', + website: 'https://www.hypnagaga.com', + fediverse: '' // used for highlighting journalism on the fediverse. Can be Mastodon, Flipboard, Threads, WordPress (with the ActivityPub plugin installed), PeerTube, Pixelfed, etc. https://blog.joinmastodon.org/2024/07/highlighting-journalism-on-mastodon/ }; export const creator = { - name: 'Lene Saile', // i.e. Lene Saile - creator's (developer) name. - email: 'hola@lenesaile.com', - website: 'https://www.lenesaile.com', - social: 'https://front-end.social/@lene' + name: 'Ben Aultowski', // i.e. Lene Saile - creator's (developer) name. + email: 'benaultowski@proton.me', + website: 'https://www.hypnagaga.com', + social: '' }; export const pathToSvgLogo = 'src/assets/svg/misc/logo.svg'; // used for favicon generation export const themeColor = '#dd4462'; // used in manifest, for example primary color value diff --git a/src/_data/navigation.js b/src/_data/navigation.js index 7c8a257..ebb405a 100644 --- a/src/_data/navigation.js +++ b/src/_data/navigation.js @@ -1,38 +1,26 @@ export default { top: [ { - text: 'About', - url: '/about/' + text: 'Why', + url: '/why/' }, { - text: 'Docs', - url: '/get-started/' + text: 'Thoughts', + url: '/thoughts/' }, { - text: 'Built with', - url: '/built-with/' + text: 'Stories', + url: '/stories/' }, { - text: 'Blog', - url: '/blog/' + text: 'Search', + url: '/search/' } ], bottom: [ { text: 'Style guide', url: '/styleguide/' - }, - { - text: 'Imprint', - url: '/imprint/' - }, - { - text: 'Privacy', - url: '/privacy/' - }, - { - text: 'Accessibility', - url: '/accessibility/' } ] }; diff --git a/src/_layouts/post.njk b/src/_layouts/post.njk index 42b9c51..c1653dd 100644 --- a/src/_layouts/post.njk +++ b/src/_layouts/post.njk @@ -25,7 +25,7 @@ schema: BlogPosting %} {% for tag in tags %}{% if tag != "posts" %} - {{ tag }} + {{ tag }} {% endif %}{% endfor %} {% endif %}
diff --git a/src/assets/css/global/blocks/search.css b/src/assets/css/global/blocks/search.css new file mode 100644 index 0000000..fe8376f --- /dev/null +++ b/src/assets/css/global/blocks/search.css @@ -0,0 +1,255 @@ +/*============================================= += Search Form = +=============================================*/ + +.search-form { + display: flex; + gap: var(--space-xs); +} + +.search-form__input { + flex-grow: 1; + padding: var(--space-xs) var(--space-s); + border: 1px solid var(--color-primary); + border-radius: var(--rounded); + font-size: var(--text-base); + background-color: var(--color-bg); + color: var(--color-text); +} + +.search-form__input:focus { + outline: 2px solid var(--color-secondary); + outline-offset: 2px; +} + +.search-form__button { + padding: var(--space-xs) var(--space-s); + border: 1px solid var(--color-primary); + border-radius: var(--rounded); + background-color: var(--color-primary); + color: var(--color-bg); + cursor: pointer; + font-size: var(--text-base); +} + +.search-form__button:hover { + background-color: var(--color-secondary); +} + +/*============================================= += Search Results = +=============================================*/ + +.search-results-summary { + margin-block-end: var(--space-l); + font-size: var(--text-l); + font-weight: var(--font-bold); +} + +.search-results-list { + list-style: none; + padding: 0; + margin: 0; + display: grid; + gap: var(--space-l); +} + +.search-result { + padding: var(--space-m); + border: 1px solid var(--color-bg-accent); + border-radius: var(--rounded-lg); + background-color: var(--color-bg-accent); +} + +.search-result__title { + margin-block-start: 0; + margin-block-end: var(--space-xs); + font-size: var(--text-xl); +} + +.search-result__title a { + text-decoration: none; + color: var(--color-primary); +} + +.search-result__title a:hover { + text-decoration: underline; +} + +.search-result__excerpt { + margin: 0; +} + +.search-results-list custom-card p mark { + background-color: var(--color-highlight); + color: var(--color-text); + padding: 0.1em 0.2em; + border-radius: var(--rounded-sm); +} + +/*============================================= += Search Filters = +=============================================*/ + +#custom-search-filters { + margin-block-start: var(--space-l); + padding: var(--space-m); + border: 1px solid var(--color-bg-accent); + border-radius: var(--rounded-lg); + background-color: var(--color-bg-accent); +} + +#custom-search-filters h3 { + margin-block-start: 0; + margin-block-end: var(--space-m); + font-size: var(--text-l); + font-weight: var(--font-bold); +} + +.filter-group { + display: flex; + flex-wrap: wrap; + gap: var(--space-s); +} + +.filter-group label { + display: flex; + align-items: center; + gap: var(--space-xs); + padding: var(--space-xs) var(--space-s); + border: 1px solid var(--color-bg-accent); + border-radius: var(--rounded); + cursor: pointer; + user-select: none; + transition: background-color 0.2s ease; +} + +.filter-group label:hover { + background-color: var(--color-bg-accent); +} + +.filter-group input[type="checkbox"] { + /* Hide the default checkbox */ + appearance: none; + -webkit-appearance: none; + width: 1em; + height: 1em; + border: 1px solid var(--color-primary); + border-radius: var(--rounded-sm); + vertical-align: middle; + position: relative; +} + +.filter-group input[type="checkbox"]:checked { + background-color: var(--color-primary); +} + +.filter-group input[type="checkbox"]:checked::after { + content: '✔'; + color: var(--color-bg); + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 0.8em; + line-height: 1; +} + +/*============================================= += Search Result Cards (custom-card styling) = +=============================================*/ + +.search-results-list custom-card { + --gutter: var(--space-xs-s); + background-color: var(--card-bg, 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; + position: relative; + text-decoration: none; + transition: border-color 0.2s ease; +} + +/* avoid flow space being added to unused elements */ +.search-results-list custom-card > :empty { + display: none; +} + +.search-results-list custom-card ::selection { + color: var(--color-dark); + background-color: var(--color-secondary); +} + +.search-results-list custom-card :is(h2, h3) { + --flow-space: var(--space-m); + grid-row: headline; +} + +.search-results-list custom-card :is(h2, h3) a { + text-decoration: none; +} + +.search-results-list custom-card > :is(picture, figure) { + grid-row: image; + --flow-space: 0; +} + +.search-results-list custom-card img { + width: 100%; + height: 200px; + object-fit: cover; + object-position: center; +} + +.search-results-list custom-card > .meta { + grid-row: meta; + font-size: var(--size-step-min-1); +} + +.search-results-list custom-card > p { + grid-row: desc; +} + +.search-results-list custom-card > footer { + grid-row: footer; + font-size: var(--size-step-min-2); +} + +/* avoid overflow of long words */ +.search-results-list custom-card :is(a, p, h2, h3) { + overflow-wrap: break-word; + word-wrap: break-word; + word-break: break-word; +} + +/* squared image variant */ +.search-results-list custom-card[img-square] img { + aspect-ratio: 1; +} + +/* clickable variant */ +.search-results-list custom-card[clickable]:hover, +.search-results-list custom-card[clickable]:focus-within { + border: 4px solid var(--color-primary); +} + +.search-results-list custom-card[clickable]:focus-within a:focus { + outline: none; +} + +.search-results-list custom-card[clickable] { + position: relative; +} + +.search-results-list custom-card[clickable] a:after { + bottom: 0; + content: ''; + left: 0; + position: absolute; + right: 0; + top: 0; +} \ No newline at end of file diff --git a/src/assets/css/local/search.css b/src/assets/css/local/search.css new file mode 100644 index 0000000..d06b285 --- /dev/null +++ b/src/assets/css/local/search.css @@ -0,0 +1,33 @@ +form#search { + .filter-and-results { + display: flex; + flex-direction: row; + + &.filter-and-results--hidden { + display: none; + } + + fieldset.types { + display: flex; + flex-direction: column; + width: fit-content; + font-size: smaller; + } + + .results-area { + padding-right: 0.7em; + } + } +} + +@media screen(ltsm) { + form#search { + .filter-and-results { + flex-direction: column; + + .results-area { + padding-right: 0; + } + } + } +} diff --git a/src/assets/og-images/gasheyes-creek-preview.jpeg b/src/assets/og-images/gasheyes-creek-preview.jpeg new file mode 100644 index 0000000..2051bb0 Binary files /dev/null and b/src/assets/og-images/gasheyes-creek-preview.jpeg differ diff --git a/src/assets/scripts/bundle/search.js b/src/assets/scripts/bundle/search.js new file mode 100644 index 0000000..0afae0a --- /dev/null +++ b/src/assets/scripts/bundle/search.js @@ -0,0 +1,226 @@ +import dayjs from 'dayjs'; + +const allTypes = ['Page', 'Post']; + +async function ensurePagefind() { + if (window.pagefind) return Promise.resolve(window.pagefind); + return import('/pagefind/pagefind.js') + .then(function (mod) { + mod.options({ + highlightParam: 'highlight' + }); + window.pagefind = mod; + return window.pagefind || mod.pagefind || mod.default || mod; + }) + .catch(function () { + return new Promise(function (resolve) { + const s = document.createElement('script'); + s.src = '/pagefind/pagefind.js'; + s.type = 'module'; + s.onload = function () { + resolve(window.pagefind); + }; + s.onerror = function () { + resolve(undefined); + }; + document.head.appendChild(s); + }); + }); +} + +function renderItem(item) { + let { + url, + excerpt, + meta: {author, date, title, type, image, image_alt, tag} + } = item; + + //debugging + console.log(item.meta); + + // create date + const dateHTML = date ? `${dayjs(date).format('MMMM D, YYYY') }` : ''; + + // create hero image preview + let imageHTML = ''; + if (item.meta.image) { + // Try to get alt text from Pagefind metadata, fallback to title, then generic text + const altText = item.meta['image[alt]'] || item.meta.title || 'Search result image'; + imageHTML = `This blog has a pagination of {{ pagination.size }} posts per page.
+ The pagination is only shown if there are more posts ({{ collections.posts.length }}) than items per
+ page ({{ pagination.size }}).
+