Add files from local source

This commit is contained in:
TheThomaas 2023-05-20 22:35:38 +02:00
parent 7ff50a1b8e
commit 1883af321a
59 changed files with 19543 additions and 0 deletions

45
.eleventy.js Normal file
View file

@ -0,0 +1,45 @@
const pluginRss = require("@11ty/eleventy-plugin-rss");
const markdownIt = require('./11ty/markdown.js');
const customShortcodes = require('./11ty/shortcodes.js');
const {
getDatetime,
getMonthDay,
getYear,
toFullDate,
} = require("./src/filters/date.js")
module.exports = config => {
config.setUseGitIgnore(false);
config.setLibrary('md', markdownIt);
config.addPlugin(pluginRss);
config.addFilter("getDatetime", getDatetime)
config.addFilter("getMonthDay", getMonthDay)
config.addFilter("getYear", getYear)
config.addFilter("toFullDate", toFullDate)
config.addShortcode('fetch', customShortcodes.fetch);
config.addPairedShortcode('full', customShortcodes.full);
config.addPassthroughCopy({"./src/_includes/js/" : "/js"});
config.addLayoutAlias('home', 'layouts/home.html');
config.addLayoutAlias('post', 'layouts/article.html');
config.addLayoutAlias('list', 'layouts/list.html');
config.addCollection('blog', collection => {
return [...collection.getFilteredByGlob('./src/posts/*.md')].reverse();
});
return {
markdownTemplateEngine: 'njk',
dataTemplateEngine: 'njk',
htmlTemplateEngine: 'njk',
dir: {
input: 'src',
output: 'dist'
}
};
};

1
.eleventyignore Normal file
View file

@ -0,0 +1 @@
node_modules

10
11ty-theme.code-workspace Normal file
View file

@ -0,0 +1,10 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
"editor.tabSize": 2
}
}

10
11ty/markdown.js Normal file
View file

@ -0,0 +1,10 @@
const markdownItDefault = require('markdown-it');
// you can use any plugins and configs you want
const markdownIt = markdownItDefault({
html: true,
breaks: false,
linkify: true,
});
module.exports = markdownIt;

28
11ty/shortcodes.js Normal file
View file

@ -0,0 +1,28 @@
const EleventyFetch = require("@11ty/eleventy-fetch");
const markdownIt = require('./markdown.js');
const outdent = require('outdent');
const full = (children) => {
const content = markdownIt.render(children);
return outdent`<div class="full-bleed" style="background-color: var(--surface2);margin-block:1rem;">${content}</div>`
};
const fetch = async (url, type) => {
try {
const text = await EleventyFetch(url,
{
duration: '*',
type: type
}
);
return text;
} catch (ex) {
console.log(ex);
return "";
}
};
module.exports = {
full,
fetch
};

20
gulp-tasks/_fonts.js Normal file
View file

@ -0,0 +1,20 @@
const {dest, src} = require('gulp');
const GetGoogleFonts = require('get-google-fonts');
const fonts = async () => {
// Setup of the library instance by setting where we want
// the output to go. CSS is relative to output font directory
const instance = new GetGoogleFonts({
outputDir: './dist/fonts',
cssFile: './fonts.css'
});
// Grabs fonts and CSS from google and puts in the dist folder
const result = await instance.download(
'https://fonts.googleapis.com/css2?family=Literata:ital,wght@0,400;0,700;1,400&family=Red+Hat+Display:wght@400;900'
);
return result;
};
module.exports = fonts;

24
gulp-tasks/images.js Normal file
View file

@ -0,0 +1,24 @@
const {dest, src} = require('gulp');
const imagemin = require('gulp-imagemin');
// Grabs all images, runs them through imagemin
// and plops them in the dist folder
const images = () => {
// We have specific configs for jpeg and png files to try
// to really pull down asset sizes
return src('./src/images/**/*')
.pipe(
imagemin(
[
imagemin.mozjpeg({quality: 60, progressive: true}),
imagemin.optipng({optimizationLevel: 5, interlaced: null})
],
{
silent: true
}
)
)
.pipe(dest('./dist/images'));
};
module.exports = images;

50
gulp-tasks/sass.js Normal file
View file

@ -0,0 +1,50 @@
const {dest, src} = require('gulp');
const cleanCSS = require('gulp-clean-css');
const sassProcessor = require('gulp-sass')(require('sass'));
// We want to be using canonical Sass, rather than node-sass
sassProcessor.compiler = require('sass');
// Flags whether we compress the output etc
const isProduction = process.env.NODE_ENV === 'production';
// An array of outputs that should be sent over to includes
const criticalStyles = ['critical.scss', 'home.scss', 'page.scss', 'work-item.scss'];
// Takes the arguments passed by `dest` and determines where the output file goes
const calculateOutput = ({history}) => {
// By default, we want a CSS file in our dist directory, so the
// HTML can grab it with a <link />
let response = './dist/css';
// Get everything after the last slash
const sourceFileName = /[^(/|\\)]*$/.exec(history[0])[0];
// If this is critical CSS though, we want it to go
// to the _includes directory, so nunjucks can include it
// directly in a <style>
if (criticalStyles.includes(sourceFileName)) {
response = './src/_includes/css';
}
return response;
};
// The main Sass method grabs all root Sass files,
// processes them, then sends them to the output calculator
const sass = () => {
return src('./src/scss/*.scss')
.pipe(sassProcessor().on('error', sassProcessor.logError))
.pipe(
cleanCSS(
isProduction
? {
level: 2
}
: {}
)
)
.pipe(dest(calculateOutput, {sourceMaps: !isProduction}));
};
module.exports = sass;

21
gulpfile.js Normal file
View file

@ -0,0 +1,21 @@
const {parallel, watch} = require('gulp');
// Pull in each task
const images = require('./gulp-tasks/images.js');
const sass = require('./gulp-tasks/sass.js');
// Set each directory and contents that we want to watch and
// assign the relevant task. `ignoreInitial` set to true will
// prevent the task being run when we run `gulp watch`, but it
// will run when a file changes.
const watcher = () => {
watch('./src/scss/**/*.scss', {ignoreInitial: true}, sass);
watch('./src/images/**/*', {ignoreInitial: true}, images);
};
// The default (if someone just runs `gulp`) is to run each task in parrallel
exports.default = parallel(images, sass);
// This is our watcher task that instructs gulp to watch directories and
// act accordingly
exports.watch = watcher;

17525
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

28
package.json Normal file
View file

@ -0,0 +1,28 @@
{
"name": "11ty-theme",
"version": "1.0.0",
"description": "",
"main": ".eleventy.js",
"scripts": {
"start": "npx gulp && concurrently \"npx gulp watch\" \"npx eleventy --serve\"",
"production": "SET NODE_ENV=production npx gulp && SET NODE_ENV=production npx @11ty/eleventy"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@11ty/eleventy": "^2.0.1",
"@11ty/eleventy-fetch": "^4.0.0",
"concurrently": "^7.6.0",
"gulp": "^4.0.2",
"gulp-clean-css": "^4.3.0",
"gulp-imagemin": "7.1.0",
"gulp-sass": "^5.1.0",
"moment": "^2.29.4",
"outdent": "^0.8.0",
"sass": "^1.57.1"
},
"devDependencies": {
"@11ty/eleventy-plugin-rss": "^1.2.0"
}
}

20
src/404.html Normal file
View file

@ -0,0 +1,20 @@
---
title: Oops! Not Found
description: This is where you should tell the user how to find their content. Maybe on the <a href="/">home page?</a>
permalink: 404.html
---
<!doctype html>
<html lang={{ site.lang }}>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<style>{% include "dist/css/404.css" %}</style>
</head>
<body>
<div>
<h1>{{ title }}</h1>
<p>{{ description | safe }}</p>
</div>
</body>
</html>

11
src/_data/Term.json Normal file
View file

@ -0,0 +1,11 @@
{
"Site": {
"SkipLink": "Skip to content",
"ToggleTheme": "Toggle theme",
"TopLink": "Back to top",
"Footer": {
"lastDeployment": "Dernier déploiement le",
"createdWith": "Built with"
}
}
}

8
src/_data/global.js Normal file
View file

@ -0,0 +1,8 @@
module.exports = {
random() {
const segment = () => {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return `${segment()}-${segment()}-${segment()}`;
}
};

74
src/_data/helpers.js Normal file
View file

@ -0,0 +1,74 @@
module.exports = {
/**
* Returns back some attributes based on whether the
* link is active or a parent of an active item
*
* @param {String} itemUrl The link in question
* @param {String} pageUrl The page context
* @returns {String} The attributes or empty
*/
getLinkActiveState(itemUrl, pageUrl) {
let response = '';
if (itemUrl === pageUrl) {
response = ' aria-current="page"';
}
if (itemUrl.length > 1 && pageUrl.indexOf(itemUrl) === 0) {
response += ' data-state="active"';
}
return response;
},
/**
* Filters out the passed item from the passed collection
* and randomises and limits them based on flags
*
* @param {Array} collection The 11ty collection we want to take from
* @param {Object} item The item we want to exclude (often current page)
* @param {Number} limit=3 How many items we want back
* @param {Boolean} random=true Wether or not this should be randomised
* @returns {Array} The resulting collection
*/
getSiblingContent(collection, item, limit = 3, random = true) {
let filteredItems = collection.filter(x => x.url !== item.url);
if (random) {
let counter = filteredItems.length;
while (counter > 0) {
// Pick a random index
let index = Math.floor(Math.random() * counter);
counter--;
let temp = filteredItems[counter];
// Swap the last element with the random one
filteredItems[counter] = filteredItems[index];
filteredItems[index] = temp;
}
}
// Lastly, trim to length
if (limit > 0) {
filteredItems = filteredItems.slice(0, limit);
}
return filteredItems;
},
/**
* Take an array of keys and return back items that match.
* Note: items in the collection must have a key attribute in
* Front Matter
*
* @param {Array} collection 11ty collection
* @param {Array} keys collection of keys
* @returns {Array} result collection or empty
*/
filterCollectionByKeys(collection, keys) {
return collection.filter(x => keys.includes(x.data.key));
}
};

20
src/_data/navigation.json Normal file
View file

@ -0,0 +1,20 @@
{
"items": [
{
"text": "Home",
"url": "/"
},
{
"text": "About",
"url": "/about/"
},
{
"text": "Blog",
"url": "/blog/"
},
{
"text": "Contact",
"url": "/contact/"
}
]
}

17
src/_data/site.js Normal file
View file

@ -0,0 +1,17 @@
module.exports = {
author: {
name: "Thomas",
mail: "",
about: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Distinctio doloribus, iure eum nostrum error tempora facilis ea."
},
socials: {
github: "https://github.com/TheThomaas"
},
buildTime: new Date(),
isProduction: process.env.NODE_ENV === 'production',
title: "SuperMinimalCSS",
description: "Superminimal Superminimal",
lang: "fr",
feed: "feed.xml",
url: "http://localhost:8080"
}

1
src/_includes/js/main.js Normal file
View file

@ -0,0 +1 @@
document.documentElement.classList.replace('no-js', 'js');

View file

@ -0,0 +1,62 @@
const storageKey = 'theme-preference'
const onClick = () => {
// flip current value
theme.value = theme.value === 'light'
? 'dark'
: 'light';
document
.querySelector('.theme-toggle')
.classList.toggle('theme-toggle--toggled');
setPreference()
}
const getColorPreference = () => {
if (localStorage.getItem(storageKey))
return localStorage.getItem(storageKey)
else
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
}
const setPreference = () => {
localStorage.setItem(storageKey, theme.value)
reflectPreference()
}
const reflectPreference = () => {
document.firstElementChild
.setAttribute('data-theme', theme.value)
document
.querySelector('#theme-toggle')
?.setAttribute('aria-label', theme.value)
}
const theme = {
value: getColorPreference(),
}
// set early so no page flashes / CSS is made aware
reflectPreference()
window.onload = () => {
// set on load so screen readers can see latest value on the button
reflectPreference()
// now this script can find and listen for clicks on the control
document
.querySelector('.theme-toggle')
.addEventListener('click', onClick)
}
// sync with system changes
window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', ({matches:isDark}) => {
theme.value = isDark ? 'dark' : 'light'
setPreference()
})

View file

@ -0,0 +1,16 @@
{% extends "layouts/base.html" %}
{% block content %}
<article class="post">
{% include "partials/article-head.html" %}
<div class="wrapper">
{% if leading %}
<p class="lead">{{ leading }}</p>
{% endif %}
{{ content | safe }}
</div>
</article>
{% endblock %}

View file

@ -0,0 +1,20 @@
{% set assetHash = global.random() %}
<!DOCTYPE html>
<html lang={{ site.lang }} class="no-js">
<head>
{% include "partials/metas.html" %}
</head>
<body>
{% include "partials/header.html" %}
<main tabindex="-1" id="main-content">
{% block content %}{% endblock %}
{% include "partials/top-link.html" %}
</main>
{% include "partials/footer.html" %}
<script src="/js/main.js"></script>
</body>
</html>

View file

@ -0,0 +1,11 @@
{% extends "layouts/base.html" %}
{% block content %}
{% include "partials/article-head.html" %}
<br>
<div class="wrapper-lg">
{{ content | safe }}
</div>
{% endblock %}

View file

@ -0,0 +1,17 @@
{% extends "layouts/base.html" %}
{% set pageHeaderTitle = title %}
{% set pageHeaderSummary = content %}
{% set postListItems = pagination.items %}
{# If this is a tag, grab those items instead as one large collection #}
{% if tag %}
{% set postListItems = collections[tag] %}
{% set pageHeaderTitle = 'Blog posts filed under "' + tag + '"' %}
{% endif %}
{% block content %}
<article class="wrapper">
{{ content | safe }}
</article>
{% endblock %}

View file

@ -0,0 +1,22 @@
<header class="wrapper-lg">
<div>
<h1>{{ title }}</h1>
{% if description %}
<p>{{ description }}</p>
{% endif %}
{% if date or tags %}
<ul class="list-inline">
{% if date %}
<li><time datetime="{{ date | getDatetime }}">{{ date | toFullDate }}</time></li>
{% endif %}
{% if tags %}
{% for tag in tags %}
<li>
<a href="/tag/{{ tag | slug }}/">{{ tag | title }}</a>
</li>
{% endfor %}
{% endif %}
</ul>
{% endif %}
</div>
</header>

View file

@ -0,0 +1,14 @@
<footer>
<div>
<ul class="list-inline">
<li><a href="{{ site.socials.github }}" target="_blank">{% include "partials/icons/github.html" %}<span class="sr-only">Github</span></a></li>
<li><a href="{{ site.url }}/{{ site.feed }}">{% include "partials/icons/rss.html" %}<span class="sr-only">RSS Feed</span></a></li>
</ul>
</div>
<div>
<p><small>{{Term.Site.Footer.createdWith}} <a href="https://www.11ty.dev/" rel="nofollow">11ty</a> / {{Term.Site.Footer.lastDeployment}} {{ site.buildTime | toFullDate }}</small></p>
</div>
<div>
{% include "partials/theme-toggle.html" %}
</div>
</footer>

View file

@ -0,0 +1,17 @@
{% include "partials/skip-link.html" %}
<header id="site-header">
<div>
<h1><a href="/index.html">{{ site.title }}</a></h1>
</div>
<div>
<nav>
<ul>
{% for item in navigation.items %}
<li>
<a href="{{ item.url }}" {{ helpers.getLinkActiveState(item.url, page.url) | safe }}>{{ item.text }}</a>
</li>
{% endfor %}
</ul>
</nav>
</div>
</header>

View file

@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
height="1em"
width="1em"
aria-hidden="true"
focusable="false"
fill="currentColor"
>
<path
d="M12 .3a12 12 0 0 0-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.6-1.4-1.4-1.8-1.4-1.8-1-.7.1-.7.1-.7 1.2 0 1.9 1.2 1.9 1.2 1 1.8 2.8 1.3 3.5 1 0-.8.4-1.3.7-1.6-2.7-.3-5.5-1.3-5.5-6 0-1.2.5-2.3 1.3-3.1-.2-.4-.6-1.6 0-3.2 0 0 1-.3 3.4 1.2a11.5 11.5 0 0 1 6 0c2.3-1.5 3.3-1.2 3.3-1.2.6 1.6.2 2.8 0 3.2.9.8 1.3 1.9 1.3 3.2 0 4.6-2.8 5.6-5.5 5.9.5.4.9 1 .9 2.2v3.3c0 .3.1.7.8.6A12 12 0 0 0 12 .3"
/>
</svg>

After

Width:  |  Height:  |  Size: 578 B

View file

@ -0,0 +1,12 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
height="1em"
width="1em"
aria-hidden="true"
focusable="false"
fill="currentColor"
>
<circle cx="6.18" cy="17.82" r="2.18"/>
<path d="M4 4.44v2.83c7.03 0 12.73 5.7 12.73 12.73h2.83c0-8.59-6.97-15.56-15.56-15.56zm0 5.66v2.83c3.9 0 7.07 3.17 7.07 7.07h2.83c0-5.47-4.43-9.9-9.9-9.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 366 B

View file

@ -0,0 +1,76 @@
{% set pageTitle = title + ' - ' + site.title %}
{# We don't want any duplication. This is likely for the home page. #}
{% if site.title === title %}
{% set pageTitle = title %}
{% endif %}
{% set siteTitle = site.title %}
{% set currentUrl = site.url + page.url %}
{% if not socialImage %}
{% set socialImage = site.url + '/images/meta/social-share.png' %}
{% endif %}
{# If the page's Front Matter has specific metaTitle and/or metaDesc items, switch them into the mix. #}
{% if metaTitle %}
{% set pageTitle = metaTitle %}
{% endif %}
{% if not metaDesc %}
{% set metaDesc = summary %}
{% endif %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="generator" content="{{ eleventy.generator }}">
<title>{{ pageTitle }}</title>
<link rel="canonical" href="{{ currentUrl }}" />
<meta property="og:site_name" content="{{ siteTitle }}" />
<meta property="og:title" content="{{ pageTitle }}" />
<meta property="og:type" content="website" />
<meta property="og:url" content="{{ currentUrl }}" />
{% if socialImage %}
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:image" content="{{ socialImage }}" />
<meta name="twitter:image" content="{{ socialImage }}" />
<meta property="og:image:alt" content="Page image for {{ site.title }}" />
<meta name="twitter:image:alt" content="Page image for {{ site.title }}" />
{% endif %}
{% if metaDesc %}
<meta name="description" content="{{ metaDesc }}" />
<meta name="twitter:description" content="{{ metaDesc }}" />
<meta property="og:description" content="{{ metaDesc }}" />
{% endif %}
<link rel="alternate" type="application/rss+xml" href="{{ site.url }}/{{ site.feed }}">
<link rel="icon" href="/images/meta/favicon.ico" sizes="any">
<link rel="icon" href="/images/meta/favicon.svg" type="image/svg+xml">
<meta name="color-scheme" content="light dark">
<script>{% include "js/theme-toggle.js" %}</script>
<style>
{% fetch "https://cdn.jsdelivr.net/npm/theme-toggles@4.10.1/css/around.min.css", "css" %}
{% include "css/critical.css" %}
</style>
{# Add facility for pages to delare an array of critical styles #}
{% if pageCriticalStyles %}
{% for item in pageCriticalStyles %}
<style>{% include item %}</style>
{% endfor %}
{% endif %}
{# Add facility for pages to declare an array of stylesheet paths #}
{% if pageStylesheets %}
{% for item in pageStylesheets %}
<link rel="stylesheet" media="print" href="{{ item }}?{{ assetHash }}" onload="this.media='all'" />
{% endfor %}
{% endif %}

View file

@ -0,0 +1,22 @@
<section class="projects">
<ol reversed class="postlist">
{%- asyncEach blog in collections.blog -%}
<li>
<h2><a href="{{ blog.url }}">{{ blog.data.title }}</a></h2>
<ul class="list-inline">
<li><time datetime="{{ blog.date | getDatetime }}">{{ blog.date | toFullDate }}</time></li>
{%- if blog.data.tags -%}
{%- for tag in blog.data.tags -%}
<li>
<a href="/tag/{{ tag | slug }}/">{{ tag | title }}</a>
</li>
{%- endfor -%}
{%- endif -%}
</ul>
{%- if blog.data.leading -%}
<p>{{ blog.data.leading }}</p>
{%- endif -%}
</li>
{%- endeach -%}
</ol>
</section>

View file

@ -0,0 +1 @@
<a href="#main-content">{{Term.Site.SkipLink}}</a>

View file

@ -0,0 +1,31 @@
<button
class="theme-toggle"
type="button"
title="{{Term.Site.ToggleTheme}}"
aria-label="{{Term.Site.ToggleTheme}}"
>
<svg
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
width="1em"
height="1em"
fill="currentColor"
class="theme-toggle__around"
viewBox="0 0 32 32"
>
<clipPath id="theme-toggle__around__cutout">
<path d="M0 0h42v30a1 1 0 00-16 13H0Z" />
</clipPath>
<g clip-path="url(#theme-toggle__around__cutout)">
<circle cx="16" cy="16" r="8.4" />
<g>
<circle cx="16" cy="3.3" r="2.3" />
<circle cx="27" cy="9.7" r="2.3" />
<circle cx="27" cy="22.3" r="2.3" />
<circle cx="16" cy="28.7" r="2.3" />
<circle cx="5" cy="22.3" r="2.3" />
<circle cx="5" cy="9.7" r="2.3" />
</g>
</g>
</svg>
</button>

View file

@ -0,0 +1,6 @@
<div class="scroll-top">
<a href="#">
<span class="sr-only">{{Term.Site.TopLink}}</span>
<svg viewBox="0 0 100 100"><path d="m50 0c-13.262 0-25.98 5.2695-35.355 14.645s-14.645 22.094-14.645 35.355 5.2695 25.98 14.645 35.355 22.094 14.645 35.355 14.645 25.98-5.2695 35.355-14.645 14.645-22.094 14.645-35.355-5.2695-25.98-14.645-35.355-22.094-14.645-35.355-14.645zm20.832 62.5-20.832-22.457-20.625 22.457c-1.207 0.74219-2.7656 0.57812-3.7891-0.39844-1.0273-0.98047-1.2695-2.5273-0.58594-3.7695l22.918-25c0.60156-0.61328 1.4297-0.96094 2.2891-0.96094 0.86328 0 1.6914 0.34766 2.293 0.96094l22.918 25c0.88672 1.2891 0.6875 3.0352-0.47266 4.0898-1.1562 1.0508-2.9141 1.0859-4.1133 0.078125z"></path></svg>
</a>
</div>

10
src/blog.md Normal file
View file

@ -0,0 +1,10 @@
---
title: 'Blog'
layout: 'list'
---
# Blog
The latest articles from around the studio, demonstrating our design
thinking, strategy and expertise.
{% include "partials/post-list.html" %}

43
src/filters/date.js Normal file
View file

@ -0,0 +1,43 @@
const siteData = require("../_data/site.js")
function toFullDate(value) {
const dateObject = new Date(value)
const dateParts = new Intl.DateTimeFormat(siteData.lang, {
year: "numeric",
day: "numeric",
month: "long",
}).formatToParts(dateObject)
const dayPart = dateParts.find((part) => part.type === "day").value
const monthPart = dateParts.find((part) => part.type === "month").value
const yearPart = dateParts.find((part) => part.type === "year").value
return `${dayPart} ${monthPart} ${yearPart}`
}
function getMonthDay(value) {
const dateObject = new Date(value)
const month = new Intl.DateTimeFormat(siteData.lang, {
month: "long",
}).format(dateObject)
return `${dateObject.getDate()} ${month}`
}
function getYear(value) {
const dateObject = new Date(value)
return dateObject.getFullYear()
}
function getDatetime(value) {
return new Date(value).toISOString().split("T")[0]
}
module.exports = {
getDatetime,
getMonthDay,
getYear,
toFullDate,
}

BIN
src/images/meta/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,28 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 447 428">
<style>
.favicon-stroke {
stroke-width: 8px;
stroke: #8929ff;
}
#skull-outline { fill: white }
#eyes-and-nose, #hat-outline { fill: #8929ff }
#hat-fill, #hat-bill { fill: #e662e6 }
@media (prefers-color-scheme: dark) {
.favicon-stroke { stroke: #343a40 }
#skull-outline { fill: #adb5bd }
#hat-outline { fill: #343a40 }
#eyes-and-nose { fill: #343a40 }
}
</style>
<g id="skull">
<path id="skull-outline" class="favicon-stroke" stroke-linejoin="round" d="M19.62 188.39A166.62 166.62 0 0 1 186.24 21.77c115.25 0 166.61 74.59 166.61 166.62 0 1.83-.08 3.64-.13 5.46h.13s.68 175.09.68 178.65c0 30.11-16.26 41.67-36.32 41.67-12.7 0-35.22-3.93-36.22-32.69h-.2c-1 28.76-16.81 32.69-36.22 32.69-18 0-32.87-6.78-35.77-32.53-2.9 25.75-17.8 32.53-35.8 32.53-20.06 0-36.32-11.56-36.32-41.67 0-2.48.36-24.88.36-24.88A166.68 166.68 0 0 1 19.62 188.39Z" />
<path id="eyes-and-nose" d="M180.77 205.76c0 23.64 12.84 42.81 28.68 42.81s28.68-19.17 28.68-42.81-12.84-42.82-28.68-42.82-28.68 19.17-28.68 42.82M275 205.76c0 23.64 12.84 42.81 28.68 42.81s28.68-19.17 28.68-42.81-12.84-42.82-28.68-42.82S275 182.11 275 205.76M264.51 276.85s-29.26 43.53-20.12 49.23c7.07 4.41 20.49-16.71 20.49-16.71s12.82 22.58 16.76 20c16.24-10.71-17.13-52.5-17.13-52.5"/>
<path id="jawline" class="favicon-stroke" fill="none" stroke-linecap="round" d="M114.92 284.33c22.54-1 22 7 22 62.48" />
</g>
<g id="hat">
<path id="hat-fill" d="m36.27 118.44 2-5.41c.37-1 9.25-24.14 33-47.53 21.83-21.56 60.88-47.26 122.91-47.26C258 18.24 294.8 44 314.46 65.65c21.38 23.52 27.49 46.82 27.74 47.8l1.27 5Z"></path>
<path id="hat-outline" d="M194.17 22.24c120.68 0 144.15 92.2 144.15 92.2H42.05s34.67-92.2 152.12-92.2m0-8c-27.9 0-53.65 5.07-76.52 15.08a162.3 162.3 0 0 0-49.21 33.33c-24.31 24-33.5 48-33.88 49l-4.07 10.82h318.13l-2.54-10c-.26-1-6.62-25.26-28.66-49.51a140.29 140.29 0 0 0-46.52-33.6c-22.25-10-48.06-15.12-76.73-15.12"></path>
<path id="hat-bill" class="favicon-stroke" stroke-linecap="square" d="M350.7 117.76c69.17 0 112.9-106.93 57.6-106.93-81 0-95.18 74.6-198.47 106.93M80.32 117.86h267.53"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

8
src/index.md Normal file
View file

@ -0,0 +1,8 @@
---
title: 'Home'
description: 'A made up agency site that you build if you take Learn Eleventy From Scratch, by Piccalilli'
metaDesc: 'A made up agency site that you build if you take Learn Eleventy From Scratch, by Piccalilli'
layout: 'home'
---
{% include "partials/post-list.html" %}

96
src/posts/article.md Normal file
View file

@ -0,0 +1,96 @@
---
title: 'The IndieWeb for Everyone 2'
leading: 'Super­Minimal­CSS is pure black & white CSS awesomeness with nice typography (for both English and Chinese). Less is more is the main principle of this mini design system.'
date: '2022-01-14'
tags: ['Tips And Tricks']
---
For *decades*, **South Florida** ***schoolchildren*** and adults fascinated by far-off galaxies, earthly ecosystems, the properties of light and sound and other wonders of science had only a quaint, antiquated museum here in which to explore their interests. "Now, with the long-delayed opening of a vast new science museum downtown set for Monday, visitors will be able to stand underneath a suspended, 500,000-gallon aquarium tank and gaze at hammerhead and tiger sharks, mahi mahi, devil rays and other creatures through a 60,000-pound oculus, a lens that will give the impression of seeing the fish from the bottom of a huge cocktail glass."
{% full %}
## This is a heading level 2
This contains some [**Markdown**](https://www.11ty.dev/docs/languages/markdown/).
```css
.ranger--texas { color: chucknorris; }
```
{% endfull %}
Swimming hundreds of feet beneath the oceans surface in many parts of the world are prolific architects called `giant larvaceans`. These zooplankton are not particularly giant themselves (they resemble tadpoles and are about the size of a pinkie finger), but every day, they construct one or more spacious houses that can exceed three feet in length. The houses are transparent mucus structures that encase the creatures inside. Giant larvaceans beat their tails to pump seawater through these structures, which filter tiny bits of dead or drifting organic matter for the animals to eat. When their filters get clogged, the larvaceans abandon ship and construct a new house. Laden with debris from the water column, old houses rapidly sink to the seafloor.
<figure>
<img src="https://via.placeholder.com/900x600" alt="Placeholder" width=900 height=600>
<figcaption>
Fig 1: The recording starts with the patter of a summer squall.
</figcaption>
</figure>
## In Science Advances on Wednesday
Scientists near Californias Monterey Bay have found that, through this process, giant larvaceans can filter all of the bays water from about 300 to 1,000 feet deep in less than two weeks, making them the fastest known zooplankton filter feeders. In doing so, the creatures help transfer carbon that has been removed from the atmosphere by photosynthesizing organisms to the deep sea, where it can be buried and stored long term. And given their abundance in other parts of the world, these organisms likely play a crucial role in the global carbon cycle. When it comes to the flow of carbon in the ocean, we dont know nearly as much as we should,” said Kakani Katija, a principal engineer at the Monterey Bay Aquarium Research Institute and the studys lead author. If we really want to understand how the system works, we have to look at all the players involved. Giant larvaceans are one important group we need to learn more about.” <kbd>enter</kbd>
<blockquote>
<p>
<q>My favorite book is <cite>The Elements of Typographic Style</cite>.</q>
</p>
<footer>
<small>
&mdash;&thinsp;Mike Mai
</small>
</footer>
</blockquote>
### In The Past
Other scientists have tried studying giant larvaceans in the laboratory. But these efforts always failed because the animals houses were too fragile to be harvested and collected specimens were never able to build houses outside the ocean. To study the zooplankton in their natural habitat, Dr. Katija and her collaborators developed a new deep-sea imaging instrument, called DeepPIV, which they paired with a remotely operated vehicle. DeepPIV projects a sheet of laser light that cuts straight through a larvaceans mucus house.
```css
.ranger--texas { color: chucknorris; }
```
* A high-definition camera on the remotely operated vehicle
* can then capture the inner pumping mechanisms
* illuminated by the laser.
The recording starts with the patter of a summer squall. Later, a drifting tone like that of a not-quite-tuned-in radio station rises and for a while drowns out the patter. These are the sounds encountered by NASAs Cassini spacecraft as it dove through the gap between Saturn and its innermost ring on April 26, the first of 22 such encounters before it will plunge into Saturns atmosphere in September.
### What Cassini Did Not Detect
Were many of the collisions of dust particles hitting the spacecraft as it passed through the plane of the rings. You can hear a couple of clicks,” said William S. Kurth, a research scientist at the University of Iowa who is the principal investigator for Cassinis radio and plasma science instrument. The few dust hits that were recorded sounded like the small pops caused by dust on a LP record, he said. What he had expected was something more like the din of driving through Iowa in a hailstorm,” Dr. Kurth said. Since Cassini had not passed through this region before, scientists and engineers did not know for certain what it would encounter. Cassini would be traveling at more than 70,000 miles per hour as it passed within 2,000 miles of the cloud tops, and a chance hit with a sand grain could be trouble. The analysis indicated that the chances of such a collision were slim, but still risky enough that mission managers did not send Cassini here until the missions final months. As a better-safe-than-sorry precaution, the spacecraft was pointed with its big radio dish facing forward, like a shield. Not only was there nothing catastrophic, there was hardly anything at all. The few clicking sounds were generated by dust the size of cigarette smoke particles about a micron, or one-25,000th of an inch, in diameter. To be clear: Cassini did not actually hear any sounds.
1. It is, after all, flying
2. through space where there is no air
3. and thus no vibrating air molecules to convey sound waves.
But space is full of radio waves, recorded by Dr. Kurths instrument, and those waves, just like the ones bouncing through the Earths atmosphere to broadcast the songs of Bruno Mars, Beyoncé and Taylor Swift, can be converted into audible sounds. Dr. Kurth said the background patter was likely oscillations of charged particles in the upper part of Saturns ionosphere where atoms are broken apart by solar and cosmic radiation. The louder tones were almost certainly whistler mode emissions” when the charged particles oscillate in unison.
## Sifting Through Teaspoons of Clay
And sand scraped from the floors of caves, German researchers have managed to isolate ancient human DNA — without turning up a single bone. Their new technique, described in a study published on Thursday in the journal Science, promises to open new avenues of research into human prehistory and was met with excitement by geneticists and archaeologists. Its a bit like discovering that you can extract gold dust from the air,” said Adam Siepel, a population geneticist at Cold Spring Harbor Laboratory.An absolutely amazing and exciting paper,” added David Reich, a genetics professor at Harvard who focuses on ancient DNA. Until recently, the only way to study the genes of ancient humans like the Neanderthals and their cousins, the Denisovans, was to recover DNA from fossil bones. But they are scarce and hard to find, which has greatly limited research into where early humans lived and how widely they ranged. The only Denisovan bones and teeth that scientists have, for example, come from a single cave in Siberia.
<figure>
<table>
<thead>
<tr>
<td></td>
<th>Name</th>
<th>Gender</th>
<th>Age</th>
</tr>
</thead>
<tbody>
<tr>
<th>Player 1</th>
<td>Mike Mai</td>
<td>Male</td>
<td>25</td>
</tr>
<tr>
<th>Player 2</th>
<td>Angi Cheung</td>
<td>Female</td>
<td>25</td>
</tr>
</tbody>
<caption>Fig 5: Available players.</caption>
</table>
</figure>
## Looking for These Genetic Signposts
In sediment has become possible only in the last few years, with recent developments in technology, including rapid sequencing of DNA. Although DNA sticks to minerals and decayed plants in soil, scientists did not know whether it would ever be possible to fish out gene fragments that were tens of thousands of years old and buried deep among other genetic debris. Bits of genes from ancient humans make up just a minute fraction of the DNA floating around in the natural world. But the German scientists, led by Matthias Meyer at the Max Planck Institute for Developmental Biology in Tübingen, have spent years developing methods to find DNA even where it seemed impossibly scarce and degraded. Theres been a real revolution in technology invented by this lab,” Dr. Reich said. Matthias is kind of a wizard in pushing the envelope.” Scientists began by retrieving DNA from ancient bones: first Neanderthals, then Denisovans. To identify the Denisovans, Svante Paabo, a geneticist at the Planck Institute and a co-author of the new paper, had only a childs pinkie bone to work with. His group surprised the world in 2010 by reporting that it had extracted DNA from the bone, finding that it belonged to a group of humans distinct from both Neanderthals and modern humans. But that sort of analysis is limited by the availability of fossil bones. In a lot of cases, you can get bones, but not enough,” said Hendrik Poinar, an evolutionary geneticist at McMaster University.
If you just have one small piece of bone from one site, curators do not want you to grind it up.

88
src/posts/article2.md Normal file
View file

@ -0,0 +1,88 @@
---
title: 'The IndieWeb for Everyone 4'
leading: 'Super­Minimal­CSS is pure black & white CSS awesomeness with nice typography (for both English and Chinese). Less is more is the main principle of this mini design system.'
date: '2023-01-14'
tags: ['Tips And Tricks', 'Demo']
---
For decades, South Florida schoolchildren and adults fascinated by far-off galaxies, earthly ecosystems, the properties of light and sound and other wonders of science had only a quaint, antiquated museum here in which to explore their interests. "Now, with the long-delayed opening of a vast new science museum downtown set for Monday, visitors will be able to stand underneath a suspended, 500,000-gallon aquarium tank and gaze at hammerhead and tiger sharks, mahi mahi, devil rays and other creatures through a 60,000-pound oculus, a lens that will give the impression of seeing the fish from the bottom of a huge cocktail glass."
<figure>
<img src="https://via.placeholder.com/900x600" alt="Placeholder" width=900 height=600>
<figcaption>
Fig 1: The recording starts with the patter of a summer squall.
</figcaption>
</figure>
Swimming hundreds of feet beneath the oceans surface in many parts of the world are prolific architects called `giant larvaceans`. These zooplankton are not particularly giant themselves (they resemble tadpoles and are about the size of a pinkie finger), but every day, they construct one or more spacious houses that can exceed three feet in length. The houses are transparent mucus structures that encase the creatures inside. Giant larvaceans beat their tails to pump seawater through these structures, which filter tiny bits of dead or drifting organic matter for the animals to eat. When their filters get clogged, the larvaceans abandon ship and construct a new house. Laden with debris from the water column, old houses rapidly sink to the seafloor.
## In Science Advances on Wednesday
Scientists near Californias Monterey Bay have found that, through this process, giant larvaceans can filter all of the bays water from about 300 to 1,000 feet deep in less than two weeks, making them the fastest known zooplankton filter feeders. In doing so, the creatures help transfer carbon that has been removed from the atmosphere by photosynthesizing organisms to the deep sea, where it can be buried and stored long term. And given their abundance in other parts of the world, these organisms likely play a crucial role in the global carbon cycle. When it comes to the flow of carbon in the ocean, we dont know nearly as much as we should,” said Kakani Katija, a principal engineer at the Monterey Bay Aquarium Research Institute and the studys lead author. If we really want to understand how the system works, we have to look at all the players involved. Giant larvaceans are one important group we need to learn more about.” <kbd>enter</kbd>
<blockquote>
<p>
<q>My favorite book is <cite>The Elements of Typographic Style</cite>.</q>
</p>
<footer>
<small>
&mdash;&thinsp;Mike Mai
</small>
</footer>
</blockquote>
### In The Past
Other scientists have tried studying giant larvaceans in the laboratory. But these efforts always failed because the animals houses were too fragile to be harvested and collected specimens were never able to build houses outside the ocean. To study the zooplankton in their natural habitat, Dr. Katija and her collaborators developed a new deep-sea imaging instrument, called DeepPIV, which they paired with a remotely operated vehicle. DeepPIV projects a sheet of laser light that cuts straight through a larvaceans mucus house.
```css
.ranger--texas { color: chucknorris; }
```
* A high-definition camera on the remotely operated vehicle
* can then capture the inner pumping mechanisms
* illuminated by the laser.
The recording starts with the patter of a summer squall. Later, a drifting tone like that of a not-quite-tuned-in radio station rises and for a while drowns out the patter. These are the sounds encountered by NASAs Cassini spacecraft as it dove through the gap between Saturn and its innermost ring on April 26, the first of 22 such encounters before it will plunge into Saturns atmosphere in September.
### What Cassini Did Not Detect
Were many of the collisions of dust particles hitting the spacecraft as it passed through the plane of the rings. You can hear a couple of clicks,” said William S. Kurth, a research scientist at the University of Iowa who is the principal investigator for Cassinis radio and plasma science instrument. The few dust hits that were recorded sounded like the small pops caused by dust on a LP record, he said. What he had expected was something more like the din of driving through Iowa in a hailstorm,” Dr. Kurth said. Since Cassini had not passed through this region before, scientists and engineers did not know for certain what it would encounter. Cassini would be traveling at more than 70,000 miles per hour as it passed within 2,000 miles of the cloud tops, and a chance hit with a sand grain could be trouble. The analysis indicated that the chances of such a collision were slim, but still risky enough that mission managers did not send Cassini here until the missions final months. As a better-safe-than-sorry precaution, the spacecraft was pointed with its big radio dish facing forward, like a shield. Not only was there nothing catastrophic, there was hardly anything at all. The few clicking sounds were generated by dust the size of cigarette smoke particles about a micron, or one-25,000th of an inch, in diameter. To be clear: Cassini did not actually hear any sounds.
1. It is, after all, flying
2. through space where there is no air
3. and thus no vibrating air molecules to convey sound waves.
But space is full of radio waves, recorded by Dr. Kurths instrument, and those waves, just like the ones bouncing through the Earths atmosphere to broadcast the songs of Bruno Mars, Beyoncé and Taylor Swift, can be converted into audible sounds. Dr. Kurth said the background patter was likely oscillations of charged particles in the upper part of Saturns ionosphere where atoms are broken apart by solar and cosmic radiation. The louder tones were almost certainly whistler mode emissions” when the charged particles oscillate in unison.
## Sifting Through Teaspoons of Clay
And sand scraped from the floors of caves, German researchers have managed to isolate ancient human DNA — without turning up a single bone. Their new technique, described in a study published on Thursday in the journal Science, promises to open new avenues of research into human prehistory and was met with excitement by geneticists and archaeologists. Its a bit like discovering that you can extract gold dust from the air,” said Adam Siepel, a population geneticist at Cold Spring Harbor Laboratory.An absolutely amazing and exciting paper,” added David Reich, a genetics professor at Harvard who focuses on ancient DNA. Until recently, the only way to study the genes of ancient humans like the Neanderthals and their cousins, the Denisovans, was to recover DNA from fossil bones. But they are scarce and hard to find, which has greatly limited research into where early humans lived and how widely they ranged. The only Denisovan bones and teeth that scientists have, for example, come from a single cave in Siberia.
<figure>
<table>
<thead>
<tr>
<td></td>
<th>Name</th>
<th>Gender</th>
<th>Age</th>
</tr>
</thead>
<tbody>
<tr>
<th>Player 1</th>
<td>Mike Mai</td>
<td>Male</td>
<td>25</td>
</tr>
<tr>
<th>Player 2</th>
<td>Angi Cheung</td>
<td>Female</td>
<td>25</td>
</tr>
</tbody>
<caption>Fig 5: Available players.</caption>
</table>
</figure>
## Looking for These Genetic Signposts
In sediment has become possible only in the last few years, with recent developments in technology, including rapid sequencing of DNA. Although DNA sticks to minerals and decayed plants in soil, scientists did not know whether it would ever be possible to fish out gene fragments that were tens of thousands of years old and buried deep among other genetic debris. Bits of genes from ancient humans make up just a minute fraction of the DNA floating around in the natural world. But the German scientists, led by Matthias Meyer at the Max Planck Institute for Developmental Biology in Tübingen, have spent years developing methods to find DNA even where it seemed impossibly scarce and degraded. Theres been a real revolution in technology invented by this lab,” Dr. Reich said. Matthias is kind of a wizard in pushing the envelope.” Scientists began by retrieving DNA from ancient bones: first Neanderthals, then Denisovans. To identify the Denisovans, Svante Paabo, a geneticist at the Planck Institute and a co-author of the new paper, had only a childs pinkie bone to work with. His group surprised the world in 2010 by reporting that it had extracted DNA from the bone, finding that it belonged to a group of humans distinct from both Neanderthals and modern humans. But that sort of analysis is limited by the availability of fossil bones. In a lot of cases, you can get bones, but not enough,” said Hendrik Poinar, an evolutionary geneticist at McMaster University.
If you just have one small piece of bone from one site, curators do not want you to grind it up.

4
src/posts/posts.json Normal file
View file

@ -0,0 +1,4 @@
{
"layout": "post",
"permalink": "/blog/{{ title | slug }}/index.html"
}

28
src/rss.html Normal file
View file

@ -0,0 +1,28 @@
---
permalink: '/{{ site.feed }}'
---
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>{% if title %}{{ title }}{% else %}{{ site.title }}{% endif %}</title>
<subtitle>{% if summary %}{{ summary }}{% else %}{{ site.description }}{% endif %}</subtitle>
<link href="{{ site.url }}{{ permalink }}" rel="self"/>
<link href="{{ site.url }}/"/>
<updated>{{ collections.blog | rssLastUpdatedDate }}</updated>
<id>{{ site.url }}</id>
<author>
<name>{{ site.author.name }}</name>
<email>{{ site.author.mail }}</email>
</author>
{% for post in collections.blog %}
{% set absolutePostUrl %}{{ site.url }}{{ post.url | url }}{% endset %}
<entry>
<title>{{ post.data.title }}</title>
<link href="{{ absolutePostUrl }}"/>
<updated>{{ post.date | rssDate }}</updated>
<id>{{ absolutePostUrl }}</id>
<content type="html"><![CDATA[
{{ post.templateContent | safe }}
]]></content>
</entry>
{% endfor %}
</feed>

43
src/scss/404.scss Normal file
View file

@ -0,0 +1,43 @@
* {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", sans-serif;;
}
:root {
color-scheme: light;
}
body {
background-color: hsl(24, 10%, 90%);
margin: 0;
display: grid;
place-items: center;
min-height: 100vh;
> div {
text-align: center;
> p {
font-size: 1.6rem;
}
}
}
h1 {
color: red;
font-size: 4rem;
}
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
}
body {
background-color: hsl(24, 0%, 10%);
}
h1 {
color: rgb(255, 69, 69);
font-size: 4rem;
}
}

View file

@ -0,0 +1,2 @@
@use './config/general';
@use './config/palette';

View file

@ -0,0 +1,85 @@
.lead {
font-size: clamp(1.2rem, 2vw + 1rem, 1.6rem);
font-weight: 300;
line-height: 1.4;
margin-block-end: 1rem;
}
article {
header {
> div {
display: flex;
flex-direction: column;
padding-block: 0 1.5rem;
@media screen and (min-width: 768px) {
padding-block: 2rem 3rem;
align-items: center;
}
}
}
h1 {
font-size: 3.5rem;
font-size: clamp(2.5rem, 5vw + 1.25rem, 4.5rem);
letter-spacing: -2px;
font-weight: 500;
margin: 0;
@media screen and (min-width: 768px) {
margin-block-end: 1.2rem;
text-align: center;
}
}
ul a {
font-family: var(--font-italic);
text-transform: uppercase;
letter-spacing: 2px;
}
}
.postlist {
margin: 0;
padding: 0;
&.grid {
margin: 0 auto;
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}
> * {
margin-block-end: 3rem;
}
h2 {
font-size: 2rem;
font-size: clamp(1.8rem, 2vw + 1.25rem, 2rem);
letter-spacing: -2px;
font-weight: 500;
margin: 0;
a {
text-decoration: none;
color: var(--brand);
transition: color .2s ease;
&:hover {
color: var(--brand-variant);
}
}
}
ul {
font-size: clamp(.6rem, 2vw + 1.25rem, 1rem);
a {
font-family: var(--font-italic);
text-transform: uppercase;
letter-spacing: 2px;
}
}
}

View file

@ -0,0 +1,50 @@
/*
Josh's Custom CSS Reset
https://www.joshwcomeau.com/css/custom-css-reset/
*/
*, *::before, *::after {
box-sizing: border-box;
}
* {
margin: 0;
}
:where(html) {
overflow-x: hidden;
}
:where(html, body) {
block-size: 100%;
}
:where(body) {
text-rendering: optimizeSpeed;
line-height: 1.5;
}
:where(img, picture, video, canvas) {
display: block;
max-inline-size: 100%;
}
:where(input, button, textarea, select, a) {
font: inherit;
}
:where(p, h1, h2, h3, h4, h5, h6) {
overflow-wrap: break-word;
}
:where(ul, ol):where([role='list'], [class]) {
list-style: none;
}
:where(html:focus-within) {
scroll-behavior: smooth;
}
:where(img, picture) {
block-size: auto;
}
@media (prefers-reduced-motion: reduce) {
* {
animation-play-state: paused !important;
transition: none !important;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}

View file

@ -0,0 +1,45 @@
:where([href="#main-content"], button, [type="submit"], [type="button"], [type="reset"]) {
background-color: var(--surface1);
padding: .2rem .5rem;
text-decoration: none;
font-weight: bold;
border: 2px solid var(--text2);
color: var(--text1);
width: auto;
display: inline-block;
--outline-border-radius: var(--border-radius);
--outline-offset: .1rem;
&:hover {
text-decoration: underline;
cursor: pointer;
}
}
:where([href="#main-content"], [type="submit"]) {
background-color: var(--brand);
}
body > a {
$link-offset: .3rem;
position: absolute;
inset-block-start: $link-offset;
inset-inline-start: $link-offset;
z-index: 99999;
padding: .4rem 1rem;
--outline-color: var(--text2);
--outline-offset: .15rem;
&:not(:focus) {
size: 1px;
margin: -1px;
padding: 0;
white-space: nowrap;
border: 0;
clip: rect(0 0 0 0);
clip-path: inset(50%);
overflow: hidden;
}
}

View file

@ -0,0 +1,385 @@
/* ------------------------------------ *\
https://codepen.io/mikemai2awesome/full/KKvMZVz
\* ------------------------------------ */
:root {
color: var(--text1);
background-color: var(--surface1);
accent-color: var(--text1);
}
:where(a) {
color: var(--text1);
}
:where(input, textarea, select) {
color: var(--text1);
background-color: var(--surface1);
}
:where(button) {
color: var(--surface1);
background-color: var(--text1);
}
:where(code) {
color: var(--text1);
background-color: var(--surface3);
}
/* ------------------------------------ *\
Typography
\* ------------------------------------ */
:root {
font-size: clamp(
var(--font-size),
var(--font-size) * 0.9 + 0.25vw,
var(--font-size) * 2
); // % ensures browser zoom is supported.
font-family: var(--font); // Use system UI font.
font-weight: var(--font-weight-regular);
line-height: var(--leading); // Standard leading for good legibility.
letter-spacing: var(--tracking);
}
:where(a) {
text-decoration: underline;
text-decoration-thickness: var(--border-width);
text-underline-offset: calc(var(--border-width) * 2);
}
:where(a):hover {
text-decoration-thickness: calc(var(--border-width) * 3);
}
:where(pre, code, kbd, dl, figcaption, abbr, table) {
font-family: var(
--font-code
); // Differentiate preformatted, code, description, and table text from body text.
font-size: 0.8em; // Make monospace and small text smaller than body text.
}
:where(big) {
font-size: 1.2em; // <big> is technically deprecated, but I love using it. This creates a fallback if a browser doesn't support it.
letter-spacing: 0.006em;
}
:where(pre code) {
display: block; // Define block code.
font-size: 1em; // Prevent cascading.
}
:where(blockquote, em, i, cite) {
font-family: var(
--font-italic
); // Differentiate blockquote, citation, idiomatic, and emphasisized text from body text. Also, sans-serif italic is ugly.
font-weight: var(
--font-weight-regular
); // Prevent italics to be bold. Bold italic is also ugly and unnecessary.
}
:where(blockquote) {
font-size: clamp(1.5rem, 1.25rem + 1vw, 3rem);
letter-spacing: 0;
}
:where(blockquote p) {
max-inline-size: 35ch;
}
:where(blockquote q):before {
position: absolute;
transform: translateX(-100%);
}
:where(strong, b, th, button) {
font-weight: var(
--font-weight-semibold
); // Make non-heading emphasized text less bold than heading text.
}
:where(h1, h2, h3, h4, h5, h6, summary strong, legend) {
font-weight: var(--font-weight-bold);
}
:where(h1, h2, h3, h4, h5, h6, summary strong, legend, big) {
font-stretch: var(--font-stretch, expanded);
}
:where(button, input[type="file"]) {
font-stretch: var(--font-stretch, condensed);
}
:where(abbr) {
text-decoration: underline;
text-decoration-style: dotted; // Differentiate abbreviaions from links.
text-underline-offset: calc(var(--border-width) * 2);
}
:where(button, label, select, summary) {
cursor: pointer;
}
:where(summary:hover > *) {
text-decoration: underline;
text-underline-offset: calc(var(--border-width) * 2);
}
:where(figcaption) {
text-align: center;
}
:where(th) {
text-align: start;
}
:where(th, td) {
vertical-align: baseline;
}
:where(fieldset > ol) {
list-style: none;
}
/* ------------------------------------ *\
Spacing
\* ------------------------------------ */
:where(h1, h2, h3, h4, h5, h6, p, figure, form, pre, blockquote, ul, ol, dl, li, details) {
margin-block-end: 0;
}
:where(h1, h2, h3, h4, h5, h6) {
margin-block-start: calc(var(--stack) * 1.75);
}
:where(p, figure, form, fieldset, pre, blockquote, ul, ol, dl, details) {
margin-inline: 0;
margin-block-start: var(--stack);
}
:where(h1, h2, h3, h4, h5, h6, p, figure, form, fieldset, pre, blockquote, ul, ol, dl, details):first-child,
:where(legend + *) {
margin-block-start: 0;
}
:where(h1, h2, h3, h4, h5, h6) + * {
margin-block-start: calc(var(--stack) / 5);
}
// :where(ul, ol) {
// padding: 0;
// }
:where(li > ul, li > ol) {
margin-block-start: calc(var(--stack) / 5);
}
:where(details ul, details ol) {
margin-inline-start: 4ch; // Unify indent for all lists inside details.
}
:where(li > ul, li > ol, fieldset ul, fieldset ol) {
margin-inline-start: 2.25ch; // Unify indent for all nested lists.
}
:where(li + li) {
margin-block-start: calc(var(--stack) / 5);
}
:where(fieldset > ol li + li) {
margin-block-start: calc(var(--stack) / 2);
}
:where(fieldset > ol) {
margin-inline: 0;
}
:where(hr) {
margin-block-start: calc(var(--stack) * 3);
margin-block-end: calc(var(--stack) * 3);
}
:where(hr + *) {
margin-block-start: 0;
}
:where(figure > img, table) {
margin-inline: auto;
}
:where(blockquote > *) {
margin-block-start: calc(var(--stack) / 4);
}
:where(blockquote:not(:last-child)) {
padding-block-end: calc(var(--stack) / 2);
}
:where(button, dd, th, td) {
// Unify inset spacing on bordered elements.
padding-block: calc(var(--stack) / 6);
padding-inline: calc(var(--gutter) / 3);
}
:where(input, textarea) {
padding-inline: 2px;
}
:where(caption, figcaption) {
padding-block: calc(var(--stack) / 2);
}
:where(code, kbd) {
padding-block: 0.25ex;
padding-inline: 0.5ch;
}
:where(figure, pre) {
padding-block-start: calc(
var(--stack) / 2.5
); // Line up top of images/codeblocks with text in an adjacent column layout.
}
:where(pre) {
padding-block-end: calc(var(--stack) / 2.5);
}
:where(pre code) {
padding-block: var(--stack);
padding-inline: var(--gutter);
}
details[open] summary + * {
margin-block-start: calc(var(--stack) / 4);
}
/* ------------------------------------ *\
General
\* ------------------------------------ */
*,
*:before,
*:after {
font-feature-settings: "kern";
font-kerning: normal;
-moz-osx-font-smoothing: grayscale !important;
-webkit-font-smoothing: antialiased !important;
box-sizing: border-box;
}
:where(input, textarea, select, fieldset, button, kbd, dd, table, th, td) {
// Unify border styles.
border: var(--border-width) solid var(--text1);
}
:where(input, textarea, select, fieldset, button, kbd) {
// Unify interactive elements border radius.
border-radius: var(--border-radius);
}
:where(pre) {
white-space: -moz-pre-wrap;
white-space: -o-pre-wrap;
white-space: pre-wrap;
word-spacing: normal;
word-break: normal;
word-wrap: break-word;
}
:where(dl) {
display: grid;
grid-template-columns: auto minmax(75%, 1fr);
gap: calc(var(--gutter) / 2);
align-items: baseline;
}
:where(dt) {
border-block-end: var(--border-width) dotted;
}
:where(dd) {
block-size: 100%;
margin-inline-start: 0;
}
:where(input:not([type="checkbox"]):not([type="radio"]), select, textarea) {
display: block;
inline-size: 100%;
}
:where(input[type="radio"], input[type="checkbox"]) {
size: 1.5ex;
vertical-align: baseline;
}
:where(input[type="file"]) {
padding-inline: 0;
border: 0;
}
::-webkit-file-upload-button {
appearance: button;
cursor: pointer;
font: inherit;
}
:where(input, textarea, select) ~ * {
margin-block-start: 0;
font-size: 0.8em;
}
:where(input:required + mark) {
display: none;
color: var(--text1);
background-color: transparent;
}
:where(input:required:invalid + mark) {
display: block;
}
:where(hr) {
block-size: 0;
border: 0;
border-block-start: var(--border-width) dashed var(--text1);
}
:where(figure, figure table) {
inline-size: 100%;
}
:where(figure) {
overflow-x: auto;
}
:where(figure > img) {
display: block;
}
:where(table) {
caption-side: bottom;
border-collapse: collapse;
border-spacing: 0;
}
:where(tr > *:first-child) {
white-space: nowrap;
}
:where(summary > *) {
display: inline;
vertical-align: middle;
}
/* ------------------------------------ *\
Add-ons
- Requires data attributes.
- Remove to do your own layouts.
\* ------------------------------------ */
:where(body main) {
padding-block-start: clamp(var(--stack) * 1, 10vmax, var(--stack) * 2);
}

View file

@ -0,0 +1,31 @@
.scroll-top {
$button-size: 32px;
position: absolute;
inset: 120vh 0 0 auto;
pointer-events: none;
overflow: clip;
a {
position: sticky;
inset-block-start: 94vh;
text-decoration:none;
padding:0 calc(var(--button-size, $button-size ) / 2) 0 0;
color: white;
font-weight:700;
pointer-events: auto;
}
svg {
transition: fill .3s;
fill: var(--button-fill, var(--text2));
background: var(--button-background, var(--surface2));
border-radius: 100%;
inline-size: var(--button-size, $button-size);
block-size: var(--button-size, $button-size);
&:hover {
fill: var(--button-fill-hover, var(--text1));
}
}
}

View file

@ -0,0 +1,5 @@
@use './utilities/wrapper';
@use './utilities/visibility';
@use './utilities/list-inline';
@use './utilities/focus';
@use './utilities/dummy';

View file

@ -0,0 +1,17 @@
:root {
--font: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", sans-serif;
--font-italic: "Georgia", serif;
--font-code: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
--font-size: 1.2rem;
--font-weight-semibold: 700;
--font-weight-bold: 900;
--leading: 1.45;
--gutter: clamp(1ch, 2.5vmax, 3ch);
--stack: clamp(1.25ex, 1ex + 2.5vmax, 1.75ex);
--line-length-small: 30ch;
--line-length: 75ch;
--line-length-large: 115ch;
--page-padding-inline: calc((100vw - min(var(--line-length), 80vw)) / 2);
--border-width: 1px;
--border-radius: 4px;
}

View file

@ -0,0 +1,69 @@
@mixin light-theme-font {
--font-weight-regular: 400;
--tracking: -0.006em;
}
@mixin dark-theme-font {
--font-weight-regular: 300;
--tracking: 0;
}
/**
* Palette
*/
@mixin light-theme {
color-scheme: light;
--brand: hsl(22, 60%, 60%);
--brand-variant: hsl(22, 60%, 50%);
--primary-accent: hsl(232, 60%, 60%);
--secondary-accent: hsl(172, 35%, 35%);
--text1: hsl(22, 96%, 6%);
--text2: hsl(20, 10%, 30%);
--surface1: hsl(20, 12%, 95%);
--surface2: hsl(24, 10%, 90%);
--surface3: hsl(22, 11%, 85%);
--surface4: hsl(24, 10%, 80%);
--img-opacity: 1;
--shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
@include light-theme-font;
}
@mixin dark-theme {
color-scheme: dark;
--brand: hsl(22, 45%, 45%);
--brand-variant: hsl(22, 45%, 55%);
--primary-accent: hsl(232, 65%, 65%);
--secondary-accent: hsl(172, 65%, 65%);
--text1: hsl(22, 22%, 90%);
--text2: hsl(20, 10%, 70%);
--surface1: hsl(24, 0%, 10%);
--surface2: hsl(23, 0%, 15%);
--surface3: hsl(24, 0%, 20%);
--surface4: hsl(23, 0%, 25%);
--img-opacity: .8;
--shadow: 0 0 0 rgba(0, 0, 0, 0);
@include dark-theme-font;
}
:root {
@include light-theme;
}
@media (prefers-color-scheme: dark) {
:root {
@include dark-theme;
}
}
[data-theme="light"] {
@include light-theme;
}
[data-theme="dark"] {
@include dark-theme;
}

View file

@ -0,0 +1,20 @@
.dummy-content p {
display: inline;
background: rgba(var(--dummy-color, black), 0.12);
color: transparent;
user-select: none;
border-radius: 2px;
box-decoration-break: clone;
[data-theme="dark"] & {
--dummy-color: white;
}
+ p {
&:before {
visibility: hidden;
content: ".";
display: block;
}
}
}

View file

@ -0,0 +1,9 @@
:focus {
outline: var(--outline, var(--outline-width, .125rem) var(--outline-style, dashed) var(--outline-color, var(--brand)));
outline-offset: var(--outline-offset, .25rem);
border-radius: var(--outline-border-radius, 8px);
}
:focus:not(:focus-visible) {
outline: none;
}

View file

@ -0,0 +1,31 @@
.list-inline {
$item-gap : 1ch;
display: flex;
flex-wrap: wrap;
padding-left: 0;
list-style: none;
margin-left: calc(var(--item-gap, $item-gap) * 3 * -1);
clip-path: inset(0 0 0 calc(var(--item-gap, $item-gap) * 3));
align-items: center;
color: var(--item-color, var(--text2));
li {
padding-left: var(--item-gap, $item-gap);
&::before {
content: var(--item-separator, '');
display: inline-block;
margin-right: var(--item-gap, $item-gap);
width: var(--item-gap, $item-gap);
text-align: center;
}
}
a {
color: var(--item-color, var(--text2));
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}

View file

@ -0,0 +1,15 @@
.sr-only {
position: absolute !important;
block-size: 1px !important;
inline-size: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
.invisible {
visibility: hidden !important;
}

View file

@ -0,0 +1,29 @@
.wrapper {
$wrapper-gap: var(--gutter);
display: grid;
grid-template-columns:
1fr
min(var(--wrapper-max-length, var(--line-length)), calc(100% - var(--wrapper-gap, $wrapper-gap) * 2))
1fr;
grid-column-gap: var(--wrapper-gap, $wrapper-gap);
> * {
grid-column: 2;
}
}
.wrapper-lg {
@extend .wrapper;
$wrapper-gap: var(--gutter);
grid-template-columns:
1fr
min(var(--wrapper-max-length, var(--line-length-large)), calc(100% - var(--wrapper-gap, $wrapper-gap) * 2))
1fr;
}
.full-bleed {
inline-size: 100%;
grid-column: 1 / -1;
padding: var(--padding-block, 1rem) var(--padding-inline, 2rem);
background-color: var(--background-color, none);
}

114
src/scss/critical.scss Normal file
View file

@ -0,0 +1,114 @@
@use './components/config';
@use './components/reset';
@use './components/utilities';
@use './components/superminimal';
@use './components/top-button';
@use './components/skip-link';
@use './components/post';
:root {
@media screen and (prefers-reduced-motion: no-preference) {
scroll-behavior: smooth;
}
}
body {
min-height: 100vh;
> header {
display: flex;
justify-content: space-between;
padding: .2rem 1rem;
flex-direction: column;
@media screen and (min-width: 768px) {
align-items: center;
flex-direction: row;
}
nav {
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
li {
padding: 6px 20px;
@media screen and (min-width: 768px) {
display: inline-block;
}
}
a[aria-current="page"] {
color: var(--brand-variant);
}
}
}
> footer {
display: flex;
justify-content: space-between;
padding: 1rem;
flex-direction: column;
align-items: center;
.list-inline {
--item-separator: "";
--item-gap: .25ch;
a:hover {
--item-color: var(--text1)
}
}
@media (min-width: 768px) {
flex-direction: row;
}
&:has(.site-footer__part:only-child) {
flex-direction: column;
align-items: center;
}
}
}
main {
position: relative;
}
img {
vertical-align: middle;
opacity: var(--img-opacity);
&:hover {
opacity: 1;
}
}
/**
* Header anchor
*/
.header-anchor {
opacity: 0;
text-decoration: none;
color: var(--text2);
font-size: .85em;
float: left;
margin-left: -0.87em;
padding-right: 0.23em;
margin-top: 0.125em;
&:hover {
text-decoration: underline;
}
:where(h2, h3, h4):hover & {
opacity: 1;
}
}
.theme-toggle {
color: var(--text1);
}

35
src/tags.md Normal file
View file

@ -0,0 +1,35 @@
---
title: 'Tag Archive'
layout: 'list'
pagination:
data: collections
size: 1
alias: tag
permalink: '/tag/{{ tag | slug }}/'
---
<h1>Tagged “{{ tag }}”</h1>
<section class="projects">
<ol reversed class="postlist">
{% set taglist = collections[ tag ] %}
{% for blog in taglist | reverse %}
<li>
<h2><a href="{{ blog.url }}">{{ blog.data.title }}</a></h2>
<ul class="list-inline">
<li><time datetime="{{ blog.date | getDatetime }}">{{ blog.date | toFullDate }}</time></li>
{%- if blog.data.tags -%}
{%- for tag in blog.data.tags -%}
<li>
<a href="/tag/{{ tag | slug }}/">{{ tag | title }}</a>
</li>
{%- endfor -%}
{%- endif -%}
</ul>
{%- if blog.data.leading -%}
<p>{{ blog.data.leading }}</p>
{%- endif -%}
</li>
{%- endfor -%}
</ol>
</section>