Add files from local source
This commit is contained in:
parent
7ff50a1b8e
commit
1883af321a
45
.eleventy.js
Normal file
45
.eleventy.js
Normal 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
1
.eleventyignore
Normal file
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
||||
10
11ty-theme.code-workspace
Normal file
10
11ty-theme.code-workspace
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"editor.tabSize": 2
|
||||
}
|
||||
}
|
||||
10
11ty/markdown.js
Normal file
10
11ty/markdown.js
Normal 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
28
11ty/shortcodes.js
Normal 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
20
gulp-tasks/_fonts.js
Normal 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
24
gulp-tasks/images.js
Normal 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
50
gulp-tasks/sass.js
Normal 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
21
gulpfile.js
Normal 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
17525
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
28
package.json
Normal file
28
package.json
Normal 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
20
src/404.html
Normal 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
11
src/_data/Term.json
Normal 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
8
src/_data/global.js
Normal 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
74
src/_data/helpers.js
Normal 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
20
src/_data/navigation.json
Normal 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
17
src/_data/site.js
Normal 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
1
src/_includes/js/main.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
document.documentElement.classList.replace('no-js', 'js');
|
||||
62
src/_includes/js/theme-toggle.js
Normal file
62
src/_includes/js/theme-toggle.js
Normal 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()
|
||||
})
|
||||
16
src/_includes/layouts/article.html
Normal file
16
src/_includes/layouts/article.html
Normal 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 %}
|
||||
20
src/_includes/layouts/base.html
Normal file
20
src/_includes/layouts/base.html
Normal 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>
|
||||
11
src/_includes/layouts/home.html
Normal file
11
src/_includes/layouts/home.html
Normal 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 %}
|
||||
17
src/_includes/layouts/list.html
Normal file
17
src/_includes/layouts/list.html
Normal 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 %}
|
||||
22
src/_includes/partials/article-head.html
Normal file
22
src/_includes/partials/article-head.html
Normal 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>
|
||||
14
src/_includes/partials/footer.html
Normal file
14
src/_includes/partials/footer.html
Normal 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>
|
||||
17
src/_includes/partials/header.html
Normal file
17
src/_includes/partials/header.html
Normal 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>
|
||||
13
src/_includes/partials/icons/github.html
Normal file
13
src/_includes/partials/icons/github.html
Normal 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 |
12
src/_includes/partials/icons/rss.html
Normal file
12
src/_includes/partials/icons/rss.html
Normal 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 |
76
src/_includes/partials/metas.html
Normal file
76
src/_includes/partials/metas.html
Normal 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 %}
|
||||
22
src/_includes/partials/post-list.html
Normal file
22
src/_includes/partials/post-list.html
Normal 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>
|
||||
1
src/_includes/partials/skip-link.html
Normal file
1
src/_includes/partials/skip-link.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<a href="#main-content">{{Term.Site.SkipLink}}</a>
|
||||
31
src/_includes/partials/theme-toggle.html
Normal file
31
src/_includes/partials/theme-toggle.html
Normal 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>
|
||||
6
src/_includes/partials/top-link.html
Normal file
6
src/_includes/partials/top-link.html
Normal 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
10
src/blog.md
Normal 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
43
src/filters/date.js
Normal 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
BIN
src/images/meta/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
28
src/images/meta/favicon.svg
Normal file
28
src/images/meta/favicon.svg
Normal 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
8
src/index.md
Normal 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
96
src/posts/article.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
title: 'The IndieWeb for Everyone 2'
|
||||
leading: 'SuperMinimalCSS 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 ocean’s 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 California’s Monterey Bay have found that, through this process, giant larvaceans can filter all of the bay’s 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 don’t know nearly as much as we should,” said Kakani Katija, a principal engineer at the Monterey Bay Aquarium Research Institute and the study’s 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>
|
||||
— 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 larvacean’s 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 NASA’s 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 Saturn’s 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 Cassini’s 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 mission’s 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. Kurth’s instrument, and those waves, just like the ones bouncing through the Earth’s 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 Saturn’s 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. It’s 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. There’s 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 child’s 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
88
src/posts/article2.md
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
---
|
||||
title: 'The IndieWeb for Everyone 4'
|
||||
leading: 'SuperMinimalCSS 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 ocean’s 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 California’s Monterey Bay have found that, through this process, giant larvaceans can filter all of the bay’s 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 don’t know nearly as much as we should,” said Kakani Katija, a principal engineer at the Monterey Bay Aquarium Research Institute and the study’s 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>
|
||||
— 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 larvacean’s 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 NASA’s 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 Saturn’s 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 Cassini’s 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 mission’s 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. Kurth’s instrument, and those waves, just like the ones bouncing through the Earth’s 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 Saturn’s 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. It’s 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. There’s 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 child’s 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
4
src/posts/posts.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"layout": "post",
|
||||
"permalink": "/blog/{{ title | slug }}/index.html"
|
||||
}
|
||||
28
src/rss.html
Normal file
28
src/rss.html
Normal 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
43
src/scss/404.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
2
src/scss/components/_config.scss
Normal file
2
src/scss/components/_config.scss
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
@use './config/general';
|
||||
@use './config/palette';
|
||||
85
src/scss/components/_post.scss
Normal file
85
src/scss/components/_post.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/scss/components/_reset.scss
Normal file
50
src/scss/components/_reset.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
45
src/scss/components/_skip-link.scss
Normal file
45
src/scss/components/_skip-link.scss
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
385
src/scss/components/_superminimal.scss
Normal file
385
src/scss/components/_superminimal.scss
Normal 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);
|
||||
}
|
||||
31
src/scss/components/_top-button.scss
Normal file
31
src/scss/components/_top-button.scss
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/scss/components/_utilities.scss
Normal file
5
src/scss/components/_utilities.scss
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
@use './utilities/wrapper';
|
||||
@use './utilities/visibility';
|
||||
@use './utilities/list-inline';
|
||||
@use './utilities/focus';
|
||||
@use './utilities/dummy';
|
||||
17
src/scss/components/config/_general.scss
Normal file
17
src/scss/components/config/_general.scss
Normal 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;
|
||||
}
|
||||
69
src/scss/components/config/_palette.scss
Normal file
69
src/scss/components/config/_palette.scss
Normal 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;
|
||||
}
|
||||
20
src/scss/components/utilities/_dummy.scss
Normal file
20
src/scss/components/utilities/_dummy.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/scss/components/utilities/_focus.scss
Normal file
9
src/scss/components/utilities/_focus.scss
Normal 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;
|
||||
}
|
||||
31
src/scss/components/utilities/_list-inline.scss
Normal file
31
src/scss/components/utilities/_list-inline.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/scss/components/utilities/_visibility.scss
Normal file
15
src/scss/components/utilities/_visibility.scss
Normal 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;
|
||||
}
|
||||
29
src/scss/components/utilities/_wrapper.scss
Normal file
29
src/scss/components/utilities/_wrapper.scss
Normal 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
114
src/scss/critical.scss
Normal 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
35
src/tags.md
Normal 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>
|
||||
Loading…
Reference in a new issue