diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4d8715f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = tab +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f426b68 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +content/feed/pretty-atom-feed.xsl linguist-vendored \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3b462cb..5d74715 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,3 @@ -node_modules - -# Output -.output -.vercel -.netlify -.wrangler -/.svelte-kit -/build - -# OS -.DS_Store -Thumbs.db - -# Env -.env -.env.* -!.env.example -!.env.test - -# Vite -vite.config.js.timestamp-* -vite.config.ts.timestamp-* +_site/ +node_modules/ +.cache \ No newline at end of file diff --git a/.npmrc b/.npmrc deleted file mode 100644 index b6f27f1..0000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -engine-strict=true diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 7d74fe2..0000000 --- a/.prettierignore +++ /dev/null @@ -1,9 +0,0 @@ -# Package Managers -package-lock.json -pnpm-lock.yaml -yarn.lock -bun.lock -bun.lockb - -# Miscellaneous -/static/ diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 7ebb855..0000000 --- a/.prettierrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "useTabs": true, - "singleQuote": true, - "trailingComma": "none", - "printWidth": 100, - "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], - "overrides": [ - { - "files": "*.svelte", - "options": { - "parser": "svelte" - } - } - ] -} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5027c0d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017–2024 Zach Leatherman @zachleat + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 2ff5bca..6203b35 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,117 @@ -Otomata Labs' site. +# eleventy-base-blog v9 -Use pnpm to install dependencies and run the project locally. +A starter repository showing how to build a blog with the [Eleventy](https://www.11ty.dev/) site generator (using the [v3.0 release](https://github.com/11ty/eleventy/releases/tag/v3.0.0)). + +## Getting Started + +* [Want a more generic/detailed getting started guide?](https://www.11ty.dev/docs/getting-started/) + +1. Make a directory and navigate to it: -```bash -pnpm install -pnpm run dev ``` +mkdir my-blog-name +cd my-blog-name +``` + +2. Clone this Repository + +``` +git clone https://github.com/11ty/eleventy-base-blog.git . +``` + +_Optional:_ Review `eleventy.config.js` and `_data/metadata.js` to configure the site’s options and data. + +3. Install dependencies + +``` +npm install +``` + +4. Run Eleventy + +Generate a production-ready build to the `_site` folder: + +``` +npx @11ty/eleventy +``` + +Or build and host on a local development server: + +``` +npx @11ty/eleventy --serve +``` + +Or you can run [debug mode](https://www.11ty.dev/docs/debugging/) to see all the internals. + +## Features + +- Using [Eleventy v3](https://github.com/11ty/eleventy/releases/tag/v3.0.0) with zero-JavaScript output. + - Content is exclusively pre-rendered (this is a static site). + - Can easily [deploy to a subfolder without changing any content](https://www.11ty.dev/docs/plugins/html-base/) + - All URLs are decoupled from the content’s location on the file system. + - Configure templates via the [Eleventy Data Cascade](https://www.11ty.dev/docs/data-cascade/) +- **Performance focused**: four-hundos Lighthouse score out of the box! + - _0 Cumulative Layout Shift_ + - _0ms Total Blocking Time_ +- Local development live reload provided by [Eleventy Dev Server](https://www.11ty.dev/docs/dev-server/). +- Content-driven [navigation menu](https://www.11ty.dev/docs/plugins/navigation/) +- Fully automated [Image optimization](https://www.11ty.dev/docs/plugins/image/) + - Zero-JavaScript output. + - Support for modern image formats automatically (e.g. AVIF and WebP) + - Processes images on-request during `--serve` for speedy local builds. + - Prefers `` markup if possible (single image format) but switches automatically to `` for multiple image formats. + - Automated `` syntax markup with `srcset` and optional `sizes` + - Includes `width`/`height` attributes to avoid [content layout shift](https://web.dev/cls/). + - Includes `loading="lazy"` for native lazy loading without JavaScript. + - Includes [`decoding="async"`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decoding) + - Images can be co-located with blog post files. +- Per page CSS bundles [via `eleventy-plugin-bundle`](https://github.com/11ty/eleventy-plugin-bundle). +- Built-in [syntax highlighter](https://www.11ty.dev/docs/plugins/syntaxhighlight/) (zero-JavaScript output). +- Draft content: use `draft: true` to mark any template as a draft. Drafts are **only** included during `--serve`/`--watch` and are excluded from full builds. This is driven by the `addPreprocessor` configuration API in `eleventy.config.js`. Schema validator will show an error if non-boolean value is set in data cascade. +- Blog Posts + - Automated next/previous links + - Accessible deep links to headings +- Generated Pages + - Home, Archive, and About pages. + - [Atom feed included (with easy one-line swap to use RSS or JSON)](https://www.11ty.dev/docs/plugins/rss/) + - `sitemap.xml` + - Zero-maintenance tag pages ([View on the Demo](https://eleventy-base-blog.netlify.app/tags/)) + - Content not found (404) page + +## Demos + +- [Netlify](https://eleventy-base-blog.netlify.app/) +- [Vercel](https://demo-base-blog.11ty.dev/) +- [Cloudflare Pages](https://eleventy-base-blog-d2a.pages.dev/) +- [Remix on Glitch](https://glitch.com/~11ty-eleventy-base-blog) +- [GitHub Pages](https://11ty.github.io/eleventy-base-blog/) + +## Deploy this to your own site + +Deploy this Eleventy site in just a few clicks on these services: + +- Read more about [Deploying an Eleventy project](https://www.11ty.dev/docs/deployment/) to the web. +- [Deploy this to **Netlify**](https://app.netlify.com/start/deploy?repository=https://github.com/11ty/eleventy-base-blog) +- [Deploy this to **Vercel**](https://vercel.com/import/project?template=11ty%2Feleventy-base-blog) +- Look in `.github/workflows/gh-pages.yml.sample` for information on [Deploying to **GitHub Pages**](https://www.11ty.dev/docs/deployment/#deploy-an-eleventy-project-to-git-hub-pages). +- [Try it out on **Stackblitz**](https://stackblitz.com/github/11ty/eleventy-base-blog) + +### Implementation Notes + +- `content/about/index.md` is an example of a content page. +- `content/blog/` has the blog posts but really they can live in any directory. They need only the `posts` tag to be included in the blog posts [collection](https://www.11ty.dev/docs/collections/). +- Use the `eleventyNavigation` key (via the [Eleventy Navigation plugin](https://www.11ty.dev/docs/plugins/navigation/)) in your front matter to add a template to the top level site navigation. This is in use on `content/index.njk` and `content/about/index.md`. +- Content can be in _any template format_ (blog posts needn’t exclusively be markdown, for example). Configure your project’s supported templates in `eleventy.config.js` -> `templateFormats`. +- The `public` folder in your input directory will be copied to the output folder (via `addPassthroughCopy` in the `eleventy.config.js` file). This means `./public/css/*` will live at `./_site/css/*` after your build completes. +- This project uses three [Eleventy Layouts](https://www.11ty.dev/docs/layouts/): + - `_includes/layouts/base.njk`: the top level HTML structure + - `_includes/layouts/home.njk`: the home page template (wrapped into `base.njk`) + - `_includes/layouts/post.njk`: the blog post template (wrapped into `base.njk`) +- `_includes/postslist.njk` is a Nunjucks include and is a reusable component used to display a list of all the posts. `content/index.njk` has an example of how to use it. + +#### Content Security Policy + +If your site enforces a [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) (as public-facing sites should), you have a few choices (pick one): + +1. In `base.njk`, remove `` and uncomment `` +2. Configure the server with the CSP directive `style-src: 'unsafe-inline'` (less secure). diff --git a/_config/filters.js b/_config/filters.js new file mode 100644 index 0000000..3f247fb --- /dev/null +++ b/_config/filters.js @@ -0,0 +1,43 @@ +import { DateTime } from "luxon"; + +export default function(eleventyConfig) { + eleventyConfig.addFilter("readableDate", (dateObj, format, zone) => { + // Formatting tokens for Luxon: https://moment.github.io/luxon/#/formatting?id=table-of-tokens + return DateTime.fromJSDate(dateObj, { zone: zone || "utc" }).toFormat(format || "dd LLLL yyyy"); + }); + + eleventyConfig.addFilter("htmlDateString", (dateObj) => { + // dateObj input: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-date-string + return DateTime.fromJSDate(dateObj, { zone: "utc" }).toFormat('yyyy-LL-dd'); + }); + + // Get the first `n` elements of a collection. + eleventyConfig.addFilter("head", (array, n) => { + if(!Array.isArray(array) || array.length === 0) { + return []; + } + if( n < 0 ) { + return array.slice(n); + } + + return array.slice(0, n); + }); + + // Return the smallest number argument + eleventyConfig.addFilter("min", (...numbers) => { + return Math.min.apply(null, numbers); + }); + + // Return the keys used in an object + eleventyConfig.addFilter("getKeys", target => { + return Object.keys(target); + }); + + eleventyConfig.addFilter("filterTagList", function filterTagList(tags) { + return (tags || []).filter(tag => ["all", "posts"].indexOf(tag) === -1); + }); + + eleventyConfig.addFilter("sortAlphabetically", strings => + (strings || []).sort((b, a) => b.localeCompare(a)) + ); +}; diff --git a/_data/eleventyDataSchema.js b/_data/eleventyDataSchema.js new file mode 100644 index 0000000..ca764ec --- /dev/null +++ b/_data/eleventyDataSchema.js @@ -0,0 +1,13 @@ +import { z } from "zod"; +import { fromZodError } from 'zod-validation-error'; + +export default function(data) { + // Draft content, validate `draft` front matter + let result = z.object({ + draft: z.boolean().or(z.undefined()), + }).safeParse(data); + + if(result.error) { + throw fromZodError(result.error); + } +} diff --git a/_data/metadata.js b/_data/metadata.js new file mode 100644 index 0000000..d3eb08e --- /dev/null +++ b/_data/metadata.js @@ -0,0 +1,6 @@ +export default { + title: "Otomata Labs", + url: "https://otomatalabs.net/", + language: "en", + description: "Pushing old computers to their limits.", +} diff --git a/_includes/layouts/base.njk b/_includes/layouts/base.njk new file mode 100644 index 0000000..3d9c1bc --- /dev/null +++ b/_includes/layouts/base.njk @@ -0,0 +1,75 @@ + + + + + + + {{ title or metadata.title }} + + + + {#- Uncomment this if you’d like folks to know that you used Eleventy to build your site! #} + {#- #} + + {#- + Plain-text bundles are provided via the `eleventy-plugin-bundle` plugin: + 1. CSS: + * Add to a per-page bundle using `{% css %}{% endcss %}` + * Retrieve bundle content using `{% getBundle "css" %}` or `{% getBundleFileUrl "css" %}` + 2. Or for JavaScript: + * Add to a per-page bundle using `{% js %}{% endjs %}` + * Retrieve via `{% getBundle "js" %}` or `{% getBundleFileUrl "js" %}` + 3. Learn more: https://github.com/11ty/eleventy-plugin-bundle + #} + + {#- Add CSS to the bundle #} + + + {#- Add the contents of a file to the bundle #} + + + {#- Or you can add from node_modules #} + {# #} + + {#- Render the CSS bundle using inlined CSS (for the fastest site performance in production) #} + + + {#- Renders the CSS bundle using a separate file, if you can't set CSP directive style-src: 'unsafe-inline' #} + {#- #} + + {#- Add the heading-anchors web component to the JavaScript bundle #} + + + + + +
+ + + {#- Read more about `eleventy-navigation` at https://www.11ty.dev/docs/plugins/navigation/ #} + +
+ +
+ + {{ content | safe }} + +
+ + + + + + + diff --git a/_includes/layouts/home.njk b/_includes/layouts/home.njk new file mode 100644 index 0000000..cf8dc9c --- /dev/null +++ b/_includes/layouts/home.njk @@ -0,0 +1,5 @@ +--- +layout: layouts/base.njk +--- + +{{ content | safe }} \ No newline at end of file diff --git a/_includes/layouts/post.njk b/_includes/layouts/post.njk new file mode 100644 index 0000000..a1ee3d6 --- /dev/null +++ b/_includes/layouts/post.njk @@ -0,0 +1,28 @@ +--- +layout: layouts/base.njk +--- +{# Only include the syntax highlighter CSS on blog posts, included with the CSS per-page bundle #} + + +

{{ title }}

+ + + +{{ content | safe }} + +{%- if collections.posts %} +{%- set previousPost = collections.posts | getPreviousCollectionItem %} +{%- set nextPost = collections.posts | getNextCollectionItem %} +{%- if nextPost or previousPost %} + +{%- endif %} +{%- endif %} diff --git a/_includes/postslist.njk b/_includes/postslist.njk new file mode 100644 index 0000000..65f1fe6 --- /dev/null +++ b/_includes/postslist.njk @@ -0,0 +1,8 @@ +
    +{%- for post in postslist | reverse %} +
  1. + {% if post.data.title %}{{ post.data.title }}{% else %}{{ post.url }}{% endif %} + +
  2. +{%- endfor %} +
diff --git a/content/404.md b/content/404.md new file mode 100644 index 0000000..50a3fa9 --- /dev/null +++ b/content/404.md @@ -0,0 +1,20 @@ +--- +permalink: 404.html +eleventyExcludeFromCollections: true +--- +# Content not found. + +Go home. + + diff --git a/content/about.md b/content/about.md new file mode 100644 index 0000000..d4c6eea --- /dev/null +++ b/content/about.md @@ -0,0 +1,9 @@ +---js +const eleventyNavigation = { + key: "ABOUT", + order: 3 +}; +--- +# About + +TODO: Write a description about Otomata Labs \ No newline at end of file diff --git a/content/blog.njk b/content/blog.njk new file mode 100644 index 0000000..4b0dd94 --- /dev/null +++ b/content/blog.njk @@ -0,0 +1,10 @@ +---js +const eleventyNavigation = { + key: "ARCHIVE", + order: 2 +}; +--- +

Archive

+ +{% set postslist = collections.posts %} +{% include "postslist.njk" %} diff --git a/content/blog/blog-example.md b/content/blog/blog-example.md new file mode 100644 index 0000000..7177b90 --- /dev/null +++ b/content/blog/blog-example.md @@ -0,0 +1,18 @@ +---js +const title = "Example blog"; +const description = "Example description"; +const date = "2025-08-27"; +const tags = ["example tag", "another example tag", "demoscene"]; +const draft = true; +--- + +# Example header +This post is a draft. You shouldn't see this post in the final build of the website. +## Header 2 +Lorem ipsum. This is an example of a blog post. What more can I say? +Well, here is a [link to MOVE 10 STEPS](https://www.pouet.net/prod.php?which=104866). +It takes you to pouet. Wooooahh. + +Here is a picture! + +A screenshot of MOVE 10 STEPS \ No newline at end of file diff --git a/content/blog/blog.11tydata.js b/content/blog/blog.11tydata.js new file mode 100644 index 0000000..614f505 --- /dev/null +++ b/content/blog/blog.11tydata.js @@ -0,0 +1,6 @@ +export default { + tags: [ + "posts" + ], + "layout": "layouts/post.njk", +}; diff --git a/content/content.11tydata.js b/content/content.11tydata.js new file mode 100644 index 0000000..8b0bb8e --- /dev/null +++ b/content/content.11tydata.js @@ -0,0 +1,3 @@ +export default { + layout: "layouts/home.njk", +}; diff --git a/content/feed/pretty-atom-feed.xsl b/content/feed/pretty-atom-feed.xsl new file mode 100644 index 0000000..6a1c4de --- /dev/null +++ b/content/feed/pretty-atom-feed.xsl @@ -0,0 +1,89 @@ + + + + + + + + <xsl:value-of select="atom:feed/atom:title"/> + + + + + + +
+
+

+ + + + + + + + + + + + + + + + + + + Web Feed Preview +

+

+

+

This preview only shows titles, but the actual feed contains the full content.

+ + + + + Visit Website → + +
+

Recent Items

+ +
+ + +
+ +
+

+ + + + + + +

+ + Published: + +
+
+
diff --git a/content/index.njk b/content/index.njk new file mode 100644 index 0000000..68731cc --- /dev/null +++ b/content/index.njk @@ -0,0 +1,29 @@ +---js +const eleventyNavigation = { + key: "HOME", + order: 1 +}; + +const numberOfLatestPostsToShow = 10; +--- +{% set postsCount = collections.posts | length %} +{% set latestPostsCount = postsCount | min(numberOfLatestPostsToShow) %} +

Latest {{ latestPostsCount }} Post{% if latestPostsCount != 1 %}s{% endif %}

+ +{% set postslist = collections.posts | head(-1 * numberOfLatestPostsToShow) %} +{% set postslistCounter = postsCount %} +{% include "postslist.njk" %} + +{% set morePosts = postsCount - numberOfLatestPostsToShow %} +{% if morePosts > 0 %} +

{{ morePosts }} more post{% if morePosts != 1 %}s{% endif %} can be found in the archive.

+{% endif %} + +{# List every content page in the project #} +{# + +#} diff --git a/content/res/otomata_labs.svg b/content/res/otomata_labs.svg new file mode 100644 index 0000000..7a06410 --- /dev/null +++ b/content/res/otomata_labs.svg @@ -0,0 +1,52 @@ + + + + diff --git a/content/sitemap.xml.njk b/content/sitemap.xml.njk new file mode 100644 index 0000000..956cd10 --- /dev/null +++ b/content/sitemap.xml.njk @@ -0,0 +1,17 @@ +--- +permalink: /sitemap.xml +layout: false +eleventyExcludeFromCollections: true +--- + + +{%- for page in collections.all %} + {% if page.data.permalink != false %} + {% set absoluteUrl %}{{ page.url | htmlBaseUrl(metadata.url) }}{% endset %} + + {{ absoluteUrl }} + {{ page.date | htmlDateString }} + + {% endif %} +{%- endfor %} + diff --git a/content/tag-pages.njk b/content/tag-pages.njk new file mode 100644 index 0000000..bc331a0 --- /dev/null +++ b/content/tag-pages.njk @@ -0,0 +1,27 @@ +---js +// - -{@render children()} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte deleted file mode 100644 index cc88df0..0000000 --- a/src/routes/+page.svelte +++ /dev/null @@ -1,2 +0,0 @@ -

Welcome to SvelteKit

-

Visit svelte.dev/docs/kit to read the documentation

diff --git a/static/favicon.svg b/static/favicon.svg deleted file mode 100644 index cc5dc66..0000000 --- a/static/favicon.svg +++ /dev/null @@ -1 +0,0 @@ -svelte-logo \ No newline at end of file diff --git a/svelte.config.js b/svelte.config.js deleted file mode 100644 index 8ab677d..0000000 --- a/svelte.config.js +++ /dev/null @@ -1,19 +0,0 @@ -import { mdsvex } from 'mdsvex'; -import adapter from '@sveltejs/adapter-auto'; -import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - // Consult https://svelte.dev/docs/kit/integrations - // for more information about preprocessors - preprocess: [vitePreprocess(), mdsvex()], - kit: { - // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. - // If your environment is not supported, or you settled on a specific environment, switch out the adapter. - // See https://svelte.dev/docs/kit/adapters for more information about adapters. - adapter: adapter() - }, - extensions: ['.svelte', '.svx'] -}; - -export default config; diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 0b2d886..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "extends": "./.svelte-kit/tsconfig.json", - "compilerOptions": { - "allowJs": true, - "checkJs": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true, - "strict": true, - "moduleResolution": "bundler" - } - // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias - // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files - // - // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes - // from the referenced tsconfig.json - TypeScript does not merge them in -} diff --git a/vite.config.ts b/vite.config.ts deleted file mode 100644 index 2d35c4f..0000000 --- a/vite.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import tailwindcss from '@tailwindcss/vite'; -import { sveltekit } from '@sveltejs/kit/vite'; -import { defineConfig } from 'vite'; - -export default defineConfig({ - plugins: [tailwindcss(), sveltekit()] -});