Update formatting settings and reformat the codebase

This commit is contained in:
Yannik Rödel 2022-03-15 09:38:13 +01:00
parent 6f52b98dcb
commit ee64ae9493
25 changed files with 1275 additions and 1187 deletions

View file

@ -1,7 +1,7 @@
root = true root = true
[*] [*]
indent_style = space indent_style = tab
indent_size = 2 indent_size = 2
end_of_line = lf end_of_line = lf
insert_final_newline = true insert_final_newline = true

View file

@ -1,5 +1,4 @@
const fs = require('fs'); const fs = require('fs');
const path = require('path');
const { DateTime } = require('luxon'); const { DateTime } = require('luxon');
const pluginRss = require('@11ty/eleventy-plugin-rss'); const pluginRss = require('@11ty/eleventy-plugin-rss');
@ -11,94 +10,94 @@ const markdownItAnchor = require('markdown-it-anchor');
const markdownItAttrs = require('markdown-it-attrs'); const markdownItAttrs = require('markdown-it-attrs');
function hyphenize(input) { function hyphenize(input) {
return input return input
.replace(/[^\w- ]/, '') .replace(/[^\w- ]/, '')
.replace(/[_ ]/, '-') .replace(/[_ ]/, '-')
.toLowerCase(); .toLowerCase();
} }
module.exports = function (eleventyConfig) { module.exports = function (eleventyConfig) {
eleventyConfig.addPlugin(pluginRss); eleventyConfig.addPlugin(pluginRss);
eleventyConfig.addPlugin(pluginSyntaxHighlight); eleventyConfig.addPlugin(pluginSyntaxHighlight);
eleventyConfig.addPlugin(pluginNavigation); eleventyConfig.addPlugin(pluginNavigation);
eleventyConfig.setDataDeepMerge(true); eleventyConfig.setDataDeepMerge(true);
// //
// Filters // Filters
// //
eleventyConfig.addFilter('readableDate', (dateObj) => { eleventyConfig.addFilter('readableDate', (dateObj) => {
return DateTime.fromJSDate(dateObj, { zone: 'utc' }).toFormat( return DateTime.fromJSDate(dateObj, { zone: 'utc' }).toFormat(
'dd LLL yyyy' 'dd LLL yyyy'
); );
}); });
eleventyConfig.addFilter('htmlDateString', (dateObj) => { eleventyConfig.addFilter('htmlDateString', (dateObj) => {
return DateTime.fromJSDate(dateObj, { zone: 'utc' }).toFormat('yyyy-LL-dd'); return DateTime.fromJSDate(dateObj, { zone: 'utc' }).toFormat('yyyy-LL-dd');
}); });
eleventyConfig.addFilter('head', (array, n) => eleventyConfig.addFilter('head', (array, n) =>
n < 0 ? array.slice(n) : array.slice(0, n) n < 0 ? array.slice(n) : array.slice(0, n)
); );
eleventyConfig.addFilter('min', (...numbers) => eleventyConfig.addFilter('min', (...numbers) =>
Math.min.apply(null, numbers) Math.min.apply(null, numbers)
); );
function filterTagList(tags) { function filterTagList(tags) {
return (tags || []).filter( return (tags || []).filter(
(tag) => ['all', 'nav', 'post', 'posts'].indexOf(tag) === -1 (tag) => ['all', 'nav', 'post', 'posts'].indexOf(tag) === -1
); );
} }
eleventyConfig.addFilter('filterTagList', filterTagList); eleventyConfig.addFilter('filterTagList', filterTagList);
// Build collections for the top and bottom navigation - the first one // Build collections for the top and bottom navigation - the first one
// contains the main sites and the latter contains legal pages. // contains the main sites and the latter contains legal pages.
eleventyConfig.addCollection('topNavigation', (collection) => { eleventyConfig.addCollection('topNavigation', (collection) => {
return collection return collection
.getAll() .getAll()
.filter((item) => !(item.data.tags || []).includes('legal')); .filter((item) => !(item.data.tags || []).includes('legal'));
}); });
eleventyConfig.addCollection('bottomNavigation', (collection) => { eleventyConfig.addCollection('bottomNavigation', (collection) => {
return collection return collection
.getAll() .getAll()
.filter((item) => (item.data.tags || []).includes('legal')); .filter((item) => (item.data.tags || []).includes('legal'));
}); });
eleventyConfig.addCollection('posts', (collection) => { eleventyConfig.addCollection('posts', (collection) => {
return collection.getAll().filter((item) => item.data.layout === 'post'); return collection.getAll().filter((item) => item.data.layout === 'post');
}); });
// //
// Widgets // Widgets
// //
eleventyConfig.addPairedShortcode( eleventyConfig.addPairedShortcode(
'section', 'section',
(content, inverted) => ` (content, inverted) => `
<section class="page-section${inverted ? ' inverse' : ''}"> <section class="page-section${inverted ? ' inverse' : ''}">
${content} ${content}
</section> </section>
` `
); );
eleventyConfig.addPairedShortcode( eleventyConfig.addPairedShortcode(
'tabs', 'tabs',
(content) => ` (content) => `
<div class="tabs-widget"> <div class="tabs-widget">
${content} ${content}
</div> </div>
` `
); );
eleventyConfig.addPairedShortcode('tab', (content, title) => { eleventyConfig.addPairedShortcode('tab', (content, title) => {
const hyphenizedTitle = hyphenize(title); const hyphenizedTitle = hyphenize(title);
return ` return `
<div id="${hyphenizedTitle}" class="tab"> <div id="${hyphenizedTitle}" class="tab">
${content} ${content}
</div> </div>
<a href="#${hyphenizedTitle}" rel="tab">${title}</a> <a href="#${hyphenizedTitle}" rel="tab">${title}</a>
`; `;
}); });
eleventyConfig.addPairedShortcode('timeline', (content, stamp) => { eleventyConfig.addPairedShortcode('timeline', (content, stamp) => {
return ` return `
<section class="timeline"> <section class="timeline">
<span class="stamp${stamp === undefined ? ' small' : ''}"> <span class="stamp${stamp === undefined ? ' small' : ''}">
${stamp || ''} ${stamp || ''}
@ -108,101 +107,101 @@ ${content}
</div> </div>
</section> </section>
`; `;
}); });
eleventyConfig.addPairedAsyncShortcode( eleventyConfig.addPairedAsyncShortcode(
'banner', 'banner',
async (content, title, backgroundSource, backgroundAlt) => { async (content, title, backgroundSource, backgroundAlt) => {
const backgroundMetadata = await Image(`src/images/${backgroundSource}`, { const backgroundMetadata = await Image(`src/images/${backgroundSource}`, {
widths: [1200, 1980, 4000], widths: [1200, 1980, 4000],
formats: ['avif', 'webp', 'jpeg'], formats: ['avif', 'webp', 'jpeg'],
urlPath: '/assets/img', urlPath: '/assets/img',
outputDir: './dist/assets/img', outputDir: './dist/assets/img',
sharpAvifOptions: { quality: 40 }, sharpAvifOptions: { quality: 40 },
sharpWebpOptions: { quality: 50 }, sharpWebpOptions: { quality: 50 },
sharpJpegOptions: { quality: 65 }, sharpJpegOptions: { quality: 65 },
}); });
const backgroundHTML = Image.generateHTML(backgroundMetadata, { const backgroundHTML = Image.generateHTML(backgroundMetadata, {
alt: backgroundAlt, alt: backgroundAlt,
sizes: '100vw', sizes: '100vw',
loading: 'lazy', loading: 'lazy',
decoding: 'async', decoding: 'async',
whitespaceMode: 'inline', whitespaceMode: 'inline',
}); });
return ` return `
<div class="page-banner"> <div class="page-banner">
<div class="background">${backgroundHTML}</div> <div class="background">${backgroundHTML}</div>
<div class="content"> <div class="content">
${title ? '<div class="title">' + title + '</div>' : ''} ${title ? '<div class="title">' + title + '</div>' : ''}
${ ${
// The '\n's here are required so that markdown still gets rendered in the // The '\n's here are required so that markdown still gets rendered in the
// content block: // content block:
content.trim() ? '<div>\n' + content + '\n</div>' : '' content.trim() ? '<div>\n' + content + '\n</div>' : ''
} }
</div> </div>
</div> </div>
`; `;
} }
); );
// //
// Templating // Templating
// //
eleventyConfig.addLayoutAlias('page', 'layouts/page.njk'); eleventyConfig.addLayoutAlias('page', 'layouts/page.njk');
eleventyConfig.addLayoutAlias('post', 'layouts/post.njk'); eleventyConfig.addLayoutAlias('post', 'layouts/post.njk');
let markdownLibrary = markdownIt({ let markdownLibrary = markdownIt({
html: true, html: true,
breaks: false, breaks: false,
linkify: true, linkify: true,
}) })
.use(markdownItAnchor) .use(markdownItAnchor)
.use(markdownItAttrs); .use(markdownItAttrs);
eleventyConfig.setLibrary('md', markdownLibrary); eleventyConfig.setLibrary('md', markdownLibrary);
// //
// Build settings // Build settings
// //
eleventyConfig.addPassthroughCopy({ 'src/assets': 'assets' }); eleventyConfig.addPassthroughCopy({ 'src/assets': 'assets' });
eleventyConfig.setBrowserSyncConfig({ eleventyConfig.setBrowserSyncConfig({
callbacks: { callbacks: {
ready: function (err, browserSync) { ready: function (err, browserSync) {
const content_404 = fs.readFileSync('dist/404.html'); const content_404 = fs.readFileSync('dist/404.html');
browserSync.addMiddleware('*', (req, res) => { browserSync.addMiddleware('*', (req, res) => {
// Provides the 404 content without redirect. // Provides the 404 content without redirect.
res.writeHead(404, { 'Content-Type': 'text/html; charset=UTF-8' }); res.writeHead(404, { 'Content-Type': 'text/html; charset=UTF-8' });
res.write(content_404); res.write(content_404);
res.end(); res.end();
}); });
}, },
}, },
ui: false, ui: false,
ghostMode: false, ghostMode: false,
}); });
// //
// Other settings // Other settings
// //
return { return {
dir: { dir: {
input: 'src/content', input: 'src/content',
// These are all relative to the input directory so the paths get a little // These are all relative to the input directory so the paths get a little
// weird: // weird:
includes: '../includes', includes: '../includes',
data: '../data', data: '../data',
output: 'dist', output: 'dist',
}, },
templateFormats: ['md', 'njk', 'html', 'liquid'], templateFormats: ['md', 'njk', 'html', 'liquid'],
markdownTemplateEngine: 'njk', markdownTemplateEngine: 'njk',
htmlTemplateEngine: 'njk', htmlTemplateEngine: 'njk',
dataTemplateEngine: false, dataTemplateEngine: false,
}; };
}; };

View file

@ -1,7 +1,8 @@
{ {
"arrowParens": "always", "arrowParens": "always",
"printWidth": 80, "printWidth": 80,
"proseWrap": "preserve", "proseWrap": "preserve",
"singleQuote": true, "singleQuote": true,
"trailingComma": "es5" "trailingComma": "es5",
"useTabs": true
} }

172
flake.nix
View file

@ -1,106 +1,106 @@
{ {
description = "Angestöpselt Homepage"; description = "Angestöpselt Homepage";
inputs.nixpkgs.url = "nixpkgs/nixos-unstable"; inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
inputs.flake-utils.url = "github:numtide/flake-utils"; inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = {self, nixpkgs, flake-utils }: let outputs = {self, nixpkgs, flake-utils }: let
base = flake-utils.lib.eachDefaultSystem (system: base = flake-utils.lib.eachDefaultSystem (system:
let let
pkgs = import nixpkgs { inherit system; }; pkgs = import nixpkgs { inherit system; };
nodejs = pkgs.nodejs-16_x; nodejs = pkgs.nodejs-16_x;
nodePackages = import ./nix/default.nix { inherit pkgs system nodejs; }; nodePackages = import ./nix/default.nix { inherit pkgs system nodejs; };
nodeDependencies = nodePackages.nodeDependencies.override { nodeDependencies = nodePackages.nodeDependencies.override {
nativeBuildInputs = with pkgs; [ pkg-config ]; nativeBuildInputs = with pkgs; [ pkg-config ];
buildInputs = with pkgs; [ vips ]; buildInputs = with pkgs; [ vips ];
dontNpmInstall = true; dontNpmInstall = true;
}; };
in in
rec { rec {
packages = { packages = {
site = pkgs.stdenv.mkDerivation { site = pkgs.stdenv.mkDerivation {
name = "angestoepselt-site"; name = "angestoepselt-site";
src = self; src = self;
buildInputs = [ nodejs nodeDependencies ]; buildInputs = [ nodejs nodeDependencies ];
buildPhase = '' buildPhase = ''
npm run build npm run build
''; '';
installPhase = '' installPhase = ''
mv dist $out mv dist $out
''; '';
}; };
container = pkgs.dockerTools.buildImage { container = pkgs.dockerTools.buildImage {
name = "angestoepselt-site-container"; name = "angestoepselt-site-container";
tag = "latest"; tag = "latest";
config = { config = {
Cmd = [ Cmd = [
"${pkgs.caddy}/bin/caddy" "${pkgs.caddy}/bin/caddy"
"file-server" "file-server"
"-root" "${packages.site}" "-root" "${packages.site}"
]; ];
}; };
}; };
# This package isn't actually the fully-built site, but rather a # This package isn't actually the fully-built site, but rather a
# derivation that contains the relevant programs (with correctly set up # derivation that contains the relevant programs (with correctly set up
# environment) to develop and build the site. It can either be used with # environment) to develop and build the site. It can either be used with
# `nix develop` see the repository's readme for details or compiled # `nix develop` see the repository's readme for details or compiled
# with `nix build`. The latter will output a folder which contains node # with `nix build`. The latter will output a folder which contains node
# and npm binaries that can be used in an IDE. # and npm binaries that can be used in an IDE.
devEnv = pkgs.symlinkJoin { devEnv = pkgs.symlinkJoin {
name = "devEnv"; name = "devEnv";
buildInputs = [ pkgs.makeWrapper ]; buildInputs = [ pkgs.makeWrapper ];
paths = [ nodejs nodeDependencies ]; paths = [ nodejs nodeDependencies ];
postBuild = '' postBuild = ''
wrapProgram "$out/bin/node" \ wrapProgram "$out/bin/node" \
--prefix PATH : "$out/lib/node_modules/.bin" \ --prefix PATH : "$out/lib/node_modules/.bin" \
--prefix NODE_PATH : "$out/lib/node_modules" --prefix NODE_PATH : "$out/lib/node_modules"
''; '';
shellHook = '' shellHook = ''
export NODE_PATH=${nodeDependencies}/lib/node_modules export NODE_PATH=${nodeDependencies}/lib/node_modules
export PATH="${nodeDependencies}/bin:${nodejs}/bin:${pkgs.nodePackages.npm-check-updates}/bin:$PATH" export PATH="${nodeDependencies}/bin:${nodejs}/bin:${pkgs.nodePackages.npm-check-updates}/bin:$PATH"
echo "" echo ""
echo " To start editing content, run:" echo " To start editing content, run:"
echo "" echo ""
echo "npm run build:styles" echo "npm run build:styles"
echo "npm run dev:site" echo "npm run dev:site"
echo "" echo ""
echo " The site will be available under http://localhost:8080/ for" echo " The site will be available under http://localhost:8080/ for"
echo " local development and rebuilds automatically when content" echo " local development and rebuilds automatically when content"
echo " changes." echo " changes."
echo "" echo ""
''; '';
}; };
}; };
defaultPackage = packages.angestoepseltSite; defaultPackage = packages.angestoepseltSite;
devShell = packages.devEnv; devShell = packages.devEnv;
}); });
in (base // { in (base // {
hydraJobs = rec { hydraJobs = rec {
inherit (base.packages.x86_64-linux) site; inherit (base.packages.x86_64-linux) site;
container = let container = let
pkgs = import nixpkgs { system = "x86_64-linux"; }; pkgs = import nixpkgs { system = "x86_64-linux"; };
containerFile = base.packages.x86_64-linux.container; containerFile = base.packages.x86_64-linux.container;
in pkgs.runCommand "container" {} '' in pkgs.runCommand "container" {} ''
mkdir -p $out/nix-support mkdir -p $out/nix-support
ln -s ${containerFile} $out/angestoepselt-site.tar.gz ln -s ${containerFile} $out/angestoepselt-site.tar.gz
echo "file oci $out/angestoepselt-site.tar.gz" > $out/nix-support/hydra-build-products echo "file oci $out/angestoepselt-site.tar.gz" > $out/nix-support/hydra-build-products
''; '';
}; };
}); });
} }

View file

@ -2,21 +2,21 @@ PROJECT_DIR=$(dirname "$(dirname "$0")")
NIX_DIR="$PROJECT_DIR/nix" NIX_DIR="$PROJECT_DIR/nix"
if [ -h "$PROJECT_DIR/node_modules" ]; then if [ -h "$PROJECT_DIR/node_modules" ]; then
rm node_modules rm node_modules
fi fi
npm install --package-lock-only npm install --package-lock-only
node2nix \ node2nix \
-i "$PROJECT_DIR/package.json" \ -i "$PROJECT_DIR/package.json" \
-l "$PROJECT_DIR/package-lock.json" \ -l "$PROJECT_DIR/package-lock.json" \
-o "$NIX_DIR/node-packages.nix" \ -o "$NIX_DIR/node-packages.nix" \
-c "$NIX_DIR/default.nix" \ -c "$NIX_DIR/default.nix" \
-e "$NIX_DIR/node-env.nix" \ -e "$NIX_DIR/node-env.nix" \
--development \ --development \
--include-peer-dependencies --include-peer-dependencies
nix build -o "$PROJECT_DIR/.dev" ".#angestoepseltSiteEnv" nix build -o "$PROJECT_DIR/.dev" ".#angestoepseltSiteEnv"
if [ ! -e "$PROJECT_DIR/node_modules" ]; then if [ ! -e "$PROJECT_DIR/node_modules" ]; then
cd "$PROJECT_DIR"; ln -s .dev/lib/node_modules . cd "$PROJECT_DIR"; ln -s .dev/lib/node_modules .
fi fi

View file

@ -1,29 +1,29 @@
{ {
"name": "angestoepselt-site", "name": "angestoepselt-site",
"version": "0.1.0", "version": "0.1.0",
"description": "Angestöpselt Homepage", "description": "Angestöpselt Homepage",
"scripts": { "scripts": {
"build:site": "eleventy", "build:site": "eleventy",
"build:styles": "sass --style=compressed src/styles/:dist/assets/css/", "build:styles": "sass --style=compressed src/styles/:dist/assets/css/",
"build": "npm run build:site && npm run build:styles", "build": "npm run build:site && npm run build:styles",
"dev:site": "eleventy --serve", "dev:site": "eleventy --serve",
"dev:styles": "sass --watch src/styles/:dist/assets/css/" "dev:styles": "sass --watch src/styles/:dist/assets/css/"
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"express": "^4.17.1" "express": "^4.17.1"
}, },
"devDependencies": { "devDependencies": {
"@11ty/eleventy": "^0.12.1", "@11ty/eleventy": "^0.12.1",
"@11ty/eleventy-img": "^1.0.0", "@11ty/eleventy-img": "^1.0.0",
"@11ty/eleventy-navigation": "^0.3.2", "@11ty/eleventy-navigation": "^0.3.2",
"@11ty/eleventy-plugin-rss": "^1.1.2", "@11ty/eleventy-plugin-rss": "^1.1.2",
"@11ty/eleventy-plugin-syntaxhighlight": "^3.1.3", "@11ty/eleventy-plugin-syntaxhighlight": "^3.1.3",
"luxon": "^2.0.2", "luxon": "^2.0.2",
"markdown-it": "^12.2.0", "markdown-it": "^12.2.0",
"markdown-it-anchor": "^8.4.1", "markdown-it-anchor": "^8.4.1",
"markdown-it-attrs": "^4.1.0", "markdown-it-attrs": "^4.1.0",
"prettier": "^2.4.1", "prettier": "^2.4.1",
"sass": "^1.43.3" "sass": "^1.43.3"
} }
} }

View file

@ -5,10 +5,10 @@ eleventyExcludeFromCollections: true
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{%- for page in collections.all %} {%- for page in collections.all %}
{% set absoluteUrl %}{{ page.url | url | absoluteUrl(metadata.url) }}{% endset %} {% set absoluteUrl %}{{ page.url | url | absoluteUrl(metadata.url) }}{% endset %}
<url> <url>
<loc>{{ absoluteUrl }}</loc> <loc>{{ absoluteUrl }}</loc>
<lastmod>{{ page.date | htmlDateString }}</lastmod> <lastmod>{{ page.date | htmlDateString }}</lastmod>
</url> </url>
{%- endfor %} {%- endfor %}
</urlset> </urlset>

View file

@ -1,102 +1,102 @@
<!doctype html> <!doctype html>
<html lang="{{ metadata.language }}"> <html lang="{{ metadata.language }}">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>{{ title or metadata.title }}</title> <title>{{ title or metadata.title }}</title>
<link rel="preload" href="{{ '/assets/css/base.css' | url }}" as="style"> <link rel="preload" href="{{ '/assets/css/base.css' | url }}" as="style">
{# We only bother with preloading the variable font here because chances are {# We only bother with preloading the variable font here because chances are
that if a browser doesn't support variable fonts it won't support that if a browser doesn't support variable fonts it won't support
preloading either: preloading either:
https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload#browser_compatibility https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload#browser_compatibility
#} #}
<link rel="preload" href="{{ '/assets/fonts/Comfortaa-VariableFont_wght.ttf' | url }}" as="font" crossorigin="anonymous"> <link rel="preload" href="{{ '/assets/fonts/Comfortaa-VariableFont_wght.ttf' | url }}" as="font" crossorigin="anonymous">
{% for name in (extraStylesheets or []) %} {% for name in (extraStylesheets or []) %}
<link rel="preload" href="{{ '/assets/css/' + name + '.css' | url }}" as="style"> <link rel="preload" href="{{ '/assets/css/' + name + '.css' | url }}" as="style">
{% endfor %} {% endfor %}
{% block headStart %}{% endblock %} {% block headStart %}{% endblock %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="description" content="{{ description or metadata.description }}"> <meta name="description" content="{{ description or metadata.description }}">
<link rel="alternate" href="{{ metadata.feed.path | url }}" type="application/atom+xml" title="{{ metadata.title }}"> <link rel="alternate" href="{{ metadata.feed.path | url }}" type="application/atom+xml" title="{{ metadata.title }}">
<link rel="alternate" href="{{ metadata.jsonfeed.path | url }}" type="application/json" title="{{ metadata.title }}"> <link rel="alternate" href="{{ metadata.jsonfeed.path | url }}" type="application/json" title="{{ metadata.title }}">
<link rel="stylesheet" href="{{ '/assets/css/base.css' | url }}"> <link rel="stylesheet" href="{{ '/assets/css/base.css' | url }}">
{% for name in (extraStylesheets or []) %} {% for name in (extraStylesheets or []) %}
<link rel="stylesheet" href="{{ '/assets/css/' + name + '.css' | url }}"> <link rel="stylesheet" href="{{ '/assets/css/' + name + '.css' | url }}">
{% endfor %} {% endfor %}
{% block headEnd %}{% endblock %} {% block headEnd %}{% endblock %}
</head> </head>
<body> <body>
<header class="site-header"> <header class="site-header">
<a class="site-logo" href="{{ '/' | url }}">{{ metadata.title }}</a> <a class="site-logo" href="{{ '/' | url }}">{{ metadata.title }}</a>
<details class="site-mobile-navigation"> <details class="site-mobile-navigation">
<summary>Menü</summary> <summary>Menü</summary>
<nav class="site-navigation"> <nav class="site-navigation">
<ul> <ul>
{% for entry in collections.topNavigation | eleventyNavigation %} {% for entry in collections.topNavigation | eleventyNavigation %}
<li{% if entry.url == page.url %} class="active"{% endif %}> <li{% if entry.url == page.url %} class="active"{% endif %}>
<a href="{{ entry.url }}"> <a href="{{ entry.url }}">
{{ entry.title }} {{ entry.title }}
</a> </a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
</nav> </nav>
</details> </details>
<nav class="site-navigation horizontal"> <nav class="site-navigation horizontal">
<ul> <ul>
{% for entry in collections.topNavigation | eleventyNavigation %} {% for entry in collections.topNavigation | eleventyNavigation %}
<li{% if entry.url == page.url %} class="active"{% endif %}> <li{% if entry.url == page.url %} class="active"{% endif %}>
<a href="{{ entry.url }}"> <a href="{{ entry.url }}">
{{ entry.title }} {{ entry.title }}
</a> </a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
</nav> </nav>
</header> </header>
<main id="content" {% if contentClass %} class="{{ contentClass }}"{% endif %}> <main id="content" {% if contentClass %} class="{{ contentClass }}"{% endif %}>
{% block content %} {% block content %}
{{ content | safe }} {{ content | safe }}
{% endblock %} {% endblock %}
</main> </main>
<footer class="page-section footer"> <footer class="page-section footer">
<div class="page-content site-footer"> <div class="page-content site-footer">
<div> <div>
<p> <p>
{{ metadata.author.name }} {{ metadata.author.name }}
{% for line in (metadata.author.address or []) %} {% for line in (metadata.author.address or []) %}
<br /> <br />
{{ line }} {{ line }}
{% endfor %} {% endfor %}
</p> </p>
<p> <p>
{{ metadata.author.email }} {{ metadata.author.email }}
</p> </p>
</div> </div>
<div> <div>
<nav class="site-navigation"> <nav class="site-navigation">
<ul> <ul>
{% for entry in collections.bottomNavigation | eleventyNavigation %} {% for entry in collections.bottomNavigation | eleventyNavigation %}
<li{% if entry.url == page.url %} class="active"{% endif %}> <li{% if entry.url == page.url %} class="active"{% endif %}>
<a href="{{ entry.url }}"> <a href="{{ entry.url }}">
{{ entry.title }} {{ entry.title }}
</a> </a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
</nav> </nav>
</div> </div>
</div> </div>
</footer> </footer>
</body> </body>
</html> </html>

View file

@ -1,5 +1,5 @@
{% if useForms %} {% if useForms %}
{% set extraStylesheets = [ "forms" ].concat(extraStylesheets or []) %} {% set extraStylesheets = [ "forms" ].concat(extraStylesheets or []) %}
{% endif %} {% endif %}
{% extends "layouts/base.njk" %} {% extends "layouts/base.njk" %}

View file

@ -17,7 +17,7 @@ templateClass: tmpl-post
{%- if nextPost or previousPost %} {%- if nextPost or previousPost %}
<hr> <hr>
<ul> <ul>
{%- if nextPost %}<li>Next: <a href="{{ nextPost.url | url }}">{{ nextPost.data.title }}</a></li>{% endif %} {%- if nextPost %}<li>Next: <a href="{{ nextPost.url | url }}">{{ nextPost.data.title }}</a></li>{% endif %}
{%- if previousPost %}<li>Previous: <a href="{{ previousPost.url | url }}">{{ previousPost.data.title }}</a></li>{% endif %} {%- if previousPost %}<li>Previous: <a href="{{ previousPost.url | url }}">{{ previousPost.data.title }}</a></li>{% endif %}
</ul> </ul>
{%- endif %} {%- endif %}

View file

@ -7,5 +7,5 @@
@include typography.root-config; @include typography.root-config;
* { * {
box-sizing: border-box; box-sizing: border-box;
} }

View file

@ -5,187 +5,219 @@
// Actions are another module that is present on the home page. It shows a small // Actions are another module that is present on the home page. It shows a small
// number of CTA-style buttons which lead to different parts of the site. // number of CTA-style buttons which lead to different parts of the site.
.page-actions { .page-actions {
padding: layout.$large-gap; padding: layout.$large-gap;
background-color: colors.$teal-300; background-color: colors.$teal-300;
> *:not(:last-child) { > *:not(:last-child) {
margin-bottom: layout.$large-gap; margin-bottom: layout.$large-gap;
} }
> div { > div {
> *:first-child { > *:first-child {
margin-top: 0; margin-top: 0;
} }
> *:last-child { > *:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
} }
> a { > a {
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;
margin: layout.$large-gap 0; margin: layout.$large-gap 0;
padding: layout.$large-gap; padding: layout.$large-gap;
background: colors.$gray-50; background: colors.$gray-50;
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
@include colors.card-shadow; @include colors.card-shadow;
transition: motion.$subtle background-color, motion.$subtle transform; transition: motion.$subtle background-color, motion.$subtle transform;
> h3 { > h3 {
margin: 0; margin: 0;
text-transform: uppercase; text-transform: uppercase;
} }
> svg { > svg {
align-self: center; align-self: center;
margin-bottom: layout.$normal-gap; margin-bottom: layout.$normal-gap;
height: 10rem; height: 10rem;
} }
&:hover { &:hover {
background-color: colors.$gray-100; background-color: colors.$gray-100;
transform: translateY(-0.5rem); transform: translateY(-0.5rem);
> .action-icon { > .action-icon {
@extend %action-icon-hover; @extend %action-icon-hover;
} }
} }
} }
@media screen and (min-width: layout.$breakpoint) { @media screen and (min-width: layout.$breakpoint) {
display: grid; display: grid;
grid-template-columns: repeat(6, 1fr); grid-template-columns: repeat(6, 1fr);
gap: layout.$large-gap; gap: layout.$large-gap;
align-items: stretch; align-items: stretch;
> * { > * {
grid-column: span 3; grid-column: span 3;
&:not(:last-child) { &:not(:last-child) {
margin-bottom: 0; margin-bottom: 0;
} }
} }
> a { > a {
margin: 0; margin: 0;
&.first { &.first {
position: relative; position: relative;
bottom: layout.$huge-gap; bottom: layout.$huge-gap;
} }
&:not(.first) { &:not(.first) {
grid-column: span 2; grid-column: span 2;
} }
} }
} }
} }
.action-icon { .action-icon {
overflow: visible; overflow: visible;
fill: colors.$main-text; fill: colors.$main-text;
// This seems to prevent jittering in Firefox: // This seems to prevent jittering in Firefox:
pointer-events: none; pointer-events: none;
&:hover { &:hover {
@extend %action-icon-hover; @extend %action-icon-hover;
} }
.emphasis-gradient > stop { .emphasis-gradient > stop {
stop-color: colors.$blue-500; stop-color: colors.$blue-500;
transition: motion.$gentle stop-color; transition: motion.$gentle stop-color;
} }
.emphasis { .emphasis {
transition: motion.$gentle transform; transition: motion.$gentle transform;
&.heart-left { &.heart-left {
transform-origin: top left; transform-origin: top left;
} }
&.heart-right { &.heart-right {
transform-origin: top right; transform-origin: top right;
} }
&.coin { &.coin {
transform-origin: 58% 39%; transform-origin: 58% 39%;
} }
} }
} }
%action-icon-hover { %action-icon-hover {
.emphasis-gradient { .emphasis-gradient {
@keyframes action-icon-emphasis-gradient-1 { @keyframes action-icon-emphasis-gradient-1 {
0% { stop-color: colors.$blue-500; } 0% {
40% { stop-color: colors.$yellow-500; } stop-color: colors.$blue-500;
80% { stop-color: colors.$teal-500; } }
} 40% {
stop-color: colors.$yellow-500;
}
80% {
stop-color: colors.$teal-500;
}
}
@keyframes action-icon-emphasis-gradient-2 { @keyframes action-icon-emphasis-gradient-2 {
0% { stop-color: colors.$blue-500; } 0% {
20% { stop-color: colors.$blue-800; } stop-color: colors.$blue-500;
60% { stop-color: colors.$yellow-500; } }
100% { stop-color: colors.$teal-500; } 20% {
} stop-color: colors.$blue-800;
}
60% {
stop-color: colors.$yellow-500;
}
100% {
stop-color: colors.$teal-500;
}
}
@for $i from 1 through 2 { @for $i from 1 through 2 {
> stop:nth-of-type(#{$i}) { > stop:nth-of-type(#{$i}) {
stop-color: colors.$teal-500; stop-color: colors.$teal-500;
animation: animation: motion.$prominent
motion.$prominent 0s 1 normal backwards running 0s
action-icon-emphasis-gradient-#{$i}; 1
} normal
} backwards
} running
action-icon-emphasis-gradient-#{$i};
}
}
}
.heart-left { .heart-left {
$final-transformation: $final-transformation: translateX(-0.8rem) translateY(1.4rem) scale(1.5)
translateX(-0.8rem) translateY(1.4rem) scale(1.5) rotate(-25deg); rotate(-25deg);
@keyframes action-icon-heart-left { @keyframes action-icon-heart-left {
0% { transform: none; } 0% {
50% { transform: none;
transform: }
translateX(0.2rem) translateY(0.8rem) scale(1.2) rotate(-10deg); 50% {
} transform: translateX(0.2rem) translateY(0.8rem) scale(1.2)
100% { transform: $final-transformation; } rotate(-10deg);
} }
100% {
transform: $final-transformation;
}
}
transform: $final-transformation; transform: $final-transformation;
animation: animation: motion.$gentle 0s 1 normal backwards running
motion.$gentle 0s 1 normal backwards running action-icon-heart-left; action-icon-heart-left;
} }
.heart-right { .heart-right {
$final-transformation: $final-transformation: translateX(1.4rem) translateY(-0.1rem) scale(1.6)
translateX(1.4rem) translateY(-0.1rem) scale(1.6) rotate(15deg); rotate(15deg);
@keyframes action-icon-heart-right { @keyframes action-icon-heart-right {
0% { transform: none; } 0% {
50% { transform: none;
transform: }
translateX(1.3rem) translateY(0.8rem) scale(1.4) rotate(30deg); 50% {
} transform: translateX(1.3rem) translateY(0.8rem) scale(1.4)
100% { transform: $final-transformation; } rotate(30deg);
} }
100% {
transform: $final-transformation;
}
}
transform: $final-transformation; transform: $final-transformation;
animation: animation: motion.$gentle 0s 1 normal backwards running
motion.$gentle 0s 1 normal backwards running action-icon-heart-right; action-icon-heart-right;
} }
.coin { .coin {
$final-transformation: scale(0.8); $final-transformation: scale(0.8);
@keyframes action-icon-coin { @keyframes action-icon-coin {
0% { transform: none; } 0% {
50% { transform: scale(1.1); } transform: none;
100% { transform: $final-transformation; } }
} 50% {
transform: scale(1.1);
}
100% {
transform: $final-transformation;
}
}
transform: $final-transformation; transform: $final-transformation;
animation: motion.$gentle 0s 1 normal backwards running action-icon-coin; animation: motion.$gentle 0s 1 normal backwards running action-icon-coin;
} }
} }

View file

@ -7,56 +7,56 @@
// - A .background element which contains an image to render behind the text // - A .background element which contains an image to render behind the text
// - A .content which holds the actual text. // - A .content which holds the actual text.
.page-banner { .page-banner {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-around; justify-content: space-around;
position: relative; position: relative;
min-height: 60vh; min-height: 60vh;
background-color: colors.$blue-800; background-color: colors.$blue-800;
> .background { > .background {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
overflow: hidden; overflow: hidden;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
} }
> .content { > .content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
position: relative; position: relative;
padding: layout.$normal-gap max(#{layout.$large-gap}, 15vw); padding: layout.$normal-gap max(#{layout.$large-gap}, 15vw);
> div { > div {
display: inline-block; display: inline-block;
padding: layout.$small-gap layout.$normal-gap; padding: layout.$small-gap layout.$normal-gap;
font-size: typography.$subheading-size; font-size: typography.$subheading-size;
background-color: colors.$yellow-600; background-color: colors.$yellow-600;
> p:first-child { > p:first-child {
margin-top: 0; margin-top: 0;
} }
> p:last-child { > p:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
} }
> .title { > .title {
padding: 0 layout.$normal-gap; padding: 0 layout.$normal-gap;
line-height: 5rem; line-height: 5rem;
font-size: typography.$title-size; font-size: typography.$title-size;
font-weight: typography.$emphasized-weight; font-weight: typography.$emphasized-weight;
background-color: colors.$teal-500; background-color: colors.$teal-500;
} }
} }
} }

View file

@ -4,91 +4,94 @@
@use '../lib/layout'; @use '../lib/layout';
.form-choices { .form-choices {
@extend %narrow-content; @extend %narrow-content;
margin-top: layout.$huge-gap; margin-top: layout.$huge-gap;
margin-bottom: layout.$huge-gap; margin-bottom: layout.$huge-gap;
list-style: none; list-style: none;
text-align: center; text-align: center;
> li { > li {
position: relative; position: relative;
&:before { &:before {
content: '\2771'; content: '\2771';
display: inline-block; display: inline-block;
position: absolute; position: absolute;
left: 0; left: 0;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
} }
&:not(:last-child) { &:not(:last-child) {
&:before { &:before {
top: calc(50% - #{math.div(layout.$large-gap, 2)}); top: calc(50% - #{math.div(layout.$large-gap, 2)});
} }
&::after { &::after {
content: ''; content: '';
display: block; display: block;
margin: layout.$large-gap auto; margin: layout.$large-gap auto;
width: 15rem; width: 15rem;
height: 1px; height: 1px;
background-color: colors.$gray-300; background-color: colors.$gray-300;
} }
} }
> a { > a {
@extend %form-choice-link; @extend %form-choice-link;
} }
} }
&.narrow { &.narrow {
margin-top: layout.$large-gap; margin-top: layout.$large-gap;
margin-bottom: layout.$large-gap; margin-bottom: layout.$large-gap;
> li::after { > li::after {
display: none; display: none;
} }
} }
} }
%form-choice-link { %form-choice-link {
padding: layout.$small-gap layout.$normal-gap; padding: layout.$small-gap layout.$normal-gap;
display: inline-block; display: inline-block;
line-height: 2.5; line-height: 2.5;
text-decoration: none; text-decoration: none;
transition: motion.$subtle background-color, motion.$subtle box-shadow; transition: motion.$subtle background-color, motion.$subtle box-shadow;
@keyframes form-choice-hover { @keyframes form-choice-hover {
0% { background-position: -100% 0; } 0% {
100% { background-position: 200% 0; } background-position: -100% 0;
} }
100% {
background-position: 200% 0;
}
}
&:hover, &:hover,
&:focus-visible { &:focus-visible {
background-color: colors.$yellow-600; background-color: colors.$yellow-600;
background-image: linear-gradient( background-image: linear-gradient(
-45deg, -45deg,
transparent 0%, transparent 0%,
#{transparentize(colors.$yellow-500, 0.6)} 50%, #{transparentize(colors.$yellow-500, 0.6)} 50%,
transparent 100% transparent 100%
); );
background-size: 200% 100%; background-size: 200% 100%;
background-repeat: no-repeat; background-repeat: no-repeat;
color: inherit; color: inherit;
@include colors.card-shadow(colors.$yellow-500); @include colors.card-shadow(colors.$yellow-500);
animation: animation: motion.$prominent 0s 1 normal both running form-choice-hover;
motion.$prominent 0s 1 normal both running form-choice-hover;
> em { > em {
background-color: transparent; background-color: transparent;
} }
} }
> em { > em {
padding-block: layout.$small-gap; padding-block: layout.$small-gap;
font-style: inherit; font-style: inherit;
background-color: colors.$yellow-600; background-color: colors.$yellow-600;
transition: motion.$subtle background-color; transition: motion.$subtle background-color;
} }
} }

View file

@ -4,101 +4,101 @@
@use '../lib/typography'; @use '../lib/typography';
%form-item { %form-item {
@extend %narrow-content-gutter; @extend %narrow-content-gutter;
display: flex; display: flex;
align-items: center; align-items: center;
} }
%form-label { %form-label {
font-weight: typography.$emphasized-weight; font-weight: typography.$emphasized-weight;
color: colors.$blue-800; color: colors.$blue-800;
} }
%base-form-input { %base-form-input {
background-color: transparent; background-color: transparent;
border: 0.1rem solid colors.$blue-800; border: 0.1rem solid colors.$blue-800;
} }
%form-input { %form-input {
@extend %base-form-input; @extend %base-form-input;
padding: layout.$small-gap; padding: layout.$small-gap;
font: inherit; font: inherit;
transition: motion.$subtle background-color; transition: motion.$subtle background-color;
&:hover { &:hover {
background-color: colors.$gray-300; background-color: colors.$gray-300;
} }
&:focus-visible, &:focus-visible,
&:active { &:active {
outline: none; outline: none;
background-color: colors.$yellow-500; background-color: colors.$yellow-500;
} }
} }
.form-input { .form-input {
@extend %form-item; @extend %form-item;
> span { > span {
@extend %form-label; @extend %form-label;
flex-grow: 1; flex-grow: 1;
text-align: right; text-align: right;
} }
> input, > input,
> textarea { > textarea {
@extend %form-input; @extend %form-input;
flex-basis: 60%; flex-basis: 60%;
margin-left: layout.$normal-gap; margin-left: layout.$normal-gap;
} }
} }
.form-checkbox { .form-checkbox {
$size: 2rem; $size: 2rem;
$gap: layout.$small-gap; $gap: layout.$small-gap;
@extend %form-item; @extend %form-item;
justify-content: flex-end; justify-content: flex-end;
&:hover > div { &:hover > div {
background-color: colors.$gray-300; background-color: colors.$gray-300;
} }
> input { > input {
opacity: 0; opacity: 0;
&:checked + div::before { &:checked + div::before {
content: '\2713'; content: '\2713';
font-size: 1.8rem; font-size: 1.8rem;
line-height: $size; line-height: $size;
} }
&:focus-visible + div { &:focus-visible + div {
background-color: colors.$yellow-600; background-color: colors.$yellow-600;
} }
} }
> div { > div {
@extend %base-form-input; @extend %base-form-input;
width: $size; width: $size;
height: $size; height: $size;
text-align: center; text-align: center;
} }
> span { > span {
@extend %form-label; @extend %form-label;
flex-basis: calc(60% - #{$size + $gap}); flex-basis: calc(60% - #{$size + $gap});
margin-left: $gap; margin-left: $gap;
} }
} }
.form-submit { .form-submit {
@extend %form-item; @extend %form-item;
justify-content: flex-end; justify-content: flex-end;
margin: layout.$huge-gap auto; margin: layout.$huge-gap auto;
> input { > input {
@extend %form-input; @extend %form-input;
flex-basis: 60%; flex-basis: 60%;
} }
} }

View file

@ -4,148 +4,151 @@
@use '../lib/typography'; @use '../lib/typography';
body { body {
margin: 0; margin: 0;
color: colors.$main-text; color: colors.$main-text;
} }
%title { %title {
@extend %content; @extend %content;
margin-top: layout.$huge-gap; margin-top: layout.$huge-gap;
margin-bottom: layout.$large-gap; margin-bottom: layout.$large-gap;
font-size: typography.$title-size; font-size: typography.$title-size;
line-height: typography.$heading-line-height; line-height: typography.$heading-line-height;
text-align: center; text-align: center;
} }
h1 { h1 {
@extend %title; @extend %title;
} }
%heading { %heading {
@extend %content; @extend %content;
margin-top: layout.$large-gap; margin-top: layout.$large-gap;
margin-bottom: layout.$large-gap; margin-bottom: layout.$large-gap;
font-size: typography.$heading-size; font-size: typography.$heading-size;
line-height: typography.$heading-line-height; line-height: typography.$heading-line-height;
&:after { &:after {
content: ''; content: '';
display: block; display: block;
width: 8rem; width: 8rem;
height: 0.3rem; height: 0.3rem;
margin-top: 0.2rem; margin-top: 0.2rem;
border-radius: 0.5rem; border-radius: 0.5rem;
background-color: colors.$blue-800; background-color: colors.$blue-800;
} }
} }
h2 { h2 {
@extend %heading; @extend %heading;
} }
%subheading { %subheading {
@extend %content-gutter; @extend %content-gutter;
font-size: typography.$subheading-size; font-size: typography.$subheading-size;
line-height: typography.$heading-line-height; line-height: typography.$heading-line-height;
} }
h3 { h3 {
@extend %subheading; @extend %subheading;
} }
p { p {
@extend %content-gutter; @extend %content-gutter;
} }
ul, ol, dl { ul,
@extend %content-gutter; ol,
dl {
@extend %content-gutter;
} }
li { li {
margin: layout.$small-gap 0; margin: layout.$small-gap 0;
} }
dt { dt {
margin: layout.$normal-gap 0; margin: layout.$normal-gap 0;
font-weight: typography.$emphasized-weight; font-weight: typography.$emphasized-weight;
color: colors.$blue-800; color: colors.$blue-800;
} }
dd { dd {
margin: layout.$normal-gap 0 layout.$normal-gap layout.$large-gap; margin: layout.$normal-gap 0 layout.$normal-gap layout.$large-gap;
} }
em { em {
background-color: colors.$teal-300; background-color: colors.$teal-300;
padding: 0 layout.$small-gap 0 layout.$small-gap; padding: 0 layout.$small-gap 0 layout.$small-gap;
} }
:any-link, :any-link,
a[href] { a[href] {
color: colors.$main-text; color: colors.$main-text;
transition: color motion.$subtle; transition: color motion.$subtle;
&:hover { &:hover {
color: colors.$blue-800; color: colors.$blue-800;
} }
} }
blockquote { blockquote {
@extend %narrow-content-gutter; @extend %narrow-content-gutter;
$border: 0.5em solid colors.$teal-300; $border: 0.5em solid colors.$teal-300;
border-top: $border; border-top: $border;
border-bottom: $border; border-bottom: $border;
} }
.cta-link { .cta-link {
display: block; display: block;
margin: 0 auto; margin: 0 auto;
padding: 0 layout.$normal-gap; padding: 0 layout.$normal-gap;
max-width: layout.$narrow-content-width; max-width: layout.$narrow-content-width;
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
line-height: 3rem; line-height: 3rem;
border: 1px solid colors.$main-text; border: 1px solid colors.$main-text;
transition: font motion.$subtle, border-color motion.$subtle, color motion.$subtle; transition: font motion.$subtle, border-color motion.$subtle,
color motion.$subtle;
&:hover { &:hover {
border-color: colors.$blue-800; border-color: colors.$blue-800;
font-weight: typography.$emphasized-weight; font-weight: typography.$emphasized-weight;
font-size: 110%; font-size: 110%;
} }
} }
ul.link-grid { ul.link-grid {
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: layout.$larger-gap layout.$normal-gap; gap: layout.$larger-gap layout.$normal-gap;
justify-items: center; justify-items: center;
align-items: center; align-items: center;
list-style: none; list-style: none;
@media screen and (min-width: layout.$breakpoint) { @media screen and (min-width: layout.$breakpoint) {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
} }
> li { > li {
> a { > a {
display: inline-block; display: inline-block;
padding: layout.$normal-gap; padding: layout.$normal-gap;
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
color: colors.$gray-600; color: colors.$gray-600;
transition: color motion.$subtle, box-shadow motion.$subtle; transition: color motion.$subtle, box-shadow motion.$subtle;
> img { > img {
display: block; display: block;
margin: 0 auto layout.$small-gap auto; margin: 0 auto layout.$small-gap auto;
max-width: 100%; max-width: 100%;
max-height: 6rem; max-height: 6rem;
} }
&:hover { &:hover {
@include colors.card-shadow; @include colors.card-shadow;
} }
} }
} }
} }

View file

@ -3,35 +3,35 @@
@use '../lib/typography'; @use '../lib/typography';
.page-section { .page-section {
padding: layout.$large-gap; padding: layout.$large-gap;
&.inverse { &.inverse {
color: colors.$inverse-text; color: colors.$inverse-text;
background-color: colors.$blue-800; background-color: colors.$blue-800;
h2:after { h2:after {
background-color: colors.$gray-50; background-color: colors.$gray-50;
} }
:any-link, :any-link,
a[href] { a[href] {
color: colors.$gray-300; color: colors.$gray-300;
&:hover { &:hover {
color: colors.$yellow-500; color: colors.$yellow-500;
} }
} }
.cta-link { .cta-link {
border-color: colors.$gray-300; border-color: colors.$gray-300;
&:hover { &:hover {
border-color: colors.$yellow-500; border-color: colors.$yellow-500;
} }
} }
} }
&.footer { &.footer {
background-color: colors.$teal-500; background-color: colors.$teal-500;
} }
} }

View file

@ -3,130 +3,130 @@
@use '../lib/typography'; @use '../lib/typography';
@mixin header-item { @mixin header-item {
padding: 0 layout.$large-gap; padding: 0 layout.$large-gap;
line-height: 4rem; line-height: 4rem;
} }
// The site logo text. More specific styles for this element are also present // The site logo text. More specific styles for this element are also present
// underneath .site-header. // underneath .site-header.
.site-logo { .site-logo {
margin: 0 layout.$large-gap; margin: 0 layout.$large-gap;
text-transform: lowercase; text-transform: lowercase;
} }
// The navigation is present twice on the site: once in the header and once in // The navigation is present twice on the site: once in the header and once in
// the footer. The former also has some specific styles (see .site-header // the footer. The former also has some specific styles (see .site-header
// below). // below).
.site-navigation { .site-navigation {
&.horizontal { &.horizontal {
> ul { > ul {
display: flex; display: flex;
margin-bottom: 0; margin-bottom: 0;
> li { > li {
margin: 0; margin: 0;
} }
} }
a { a {
display: inline-block; display: inline-block;
@include header-item; @include header-item;
} }
} }
> ul { > ul {
margin: 0; margin: 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
} }
a { a {
display: block; display: block;
padding: layout.$small-gap; padding: layout.$small-gap;
font-weight: typography.$emphasized-weight; font-weight: typography.$emphasized-weight;
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
text-transform: lowercase; text-transform: lowercase;
} }
} }
.site-mobile-navigation { .site-mobile-navigation {
cursor: pointer; cursor: pointer;
> summary { > summary {
display: block; display: block;
@include header-item; @include header-item;
} }
&[open] { &[open] {
flex-basis: 100%; flex-basis: 100%;
> summary { > summary {
// The positioning requires that the header component has relative // The positioning requires that the header component has relative
// positioning. // positioning.
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
color: colors.$blue-800; color: colors.$blue-800;
} }
} }
// This is the next site navigation block (the one that's visible on desktop), // This is the next site navigation block (the one that's visible on desktop),
// while... // while...
+ .site-navigation { + .site-navigation {
display: none; display: none;
} }
// ... this here is the one inside the mobile-only <details> block. // ... this here is the one inside the mobile-only <details> block.
> .site-navigation { > .site-navigation {
&::before { &::before {
content: ''; content: '';
display: block; display: block;
margin: 0 auto; margin: 0 auto;
width: calc(100% - #{2 * layout.$normal-gap}); width: calc(100% - #{2 * layout.$normal-gap});
height: 1px; height: 1px;
background-color: colors.$gray-300; background-color: colors.$gray-300;
} }
} }
@media screen and (min-width: layout.$breakpoint) { @media screen and (min-width: layout.$breakpoint) {
display: none; display: none;
+ .site-navigation { + .site-navigation {
display: block; display: block;
} }
} }
} }
.site-header { .site-header {
display: flex; display: flex;
// This container needs to wrap because when the navigation is open on small // This container needs to wrap because when the navigation is open on small
// screens, we want it to overflow into its own line. // screens, we want it to overflow into its own line.
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
// Relative positioning is required here so that we can fake the menu button's // Relative positioning is required here so that we can fake the menu button's
// location on mobile. // location on mobile.
position: relative; position: relative;
z-index: 10; z-index: 10;
background-color: colors.$main-background; background-color: colors.$main-background;
@include colors.block-shadow; @include colors.block-shadow;
> .site-logo { > .site-logo {
flex-grow: 1; flex-grow: 1;
margin: 0; margin: 0;
font-size: typography.$subheading-size; font-size: typography.$subheading-size;
text-decoration: none; text-decoration: none;
@include header-item; @include header-item;
} }
} }
.site-footer { .site-footer {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between; justify-content: space-between;
gap: layout.$normal-gap; gap: layout.$normal-gap;
.site-navigation a { .site-navigation a {
text-align: right; text-align: right;
} }
} }

View file

@ -21,48 +21,48 @@
// tabs is targeted by the URL's hash, it is shown instead and the default tab // tabs is targeted by the URL's hash, it is shown instead and the default tab
// is hidden. // is hidden.
.tabs-widget { .tabs-widget {
@extend %content; @extend %content;
display: flex; display: flex;
flex-direction: row-reverse; flex-direction: row-reverse;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
margin-top: #{-1 * layout.$large-gap}; margin-top: #{-1 * layout.$large-gap};
> a { > a {
display: block; display: block;
margin: 0 layout.$normal-gap; margin: 0 layout.$normal-gap;
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
} }
> .tab { > .tab {
flex: 100% 1; flex: 100% 1;
order: 9999; order: 9999;
display: none; display: none;
margin: layout.$large-gap 0; margin: layout.$large-gap 0;
padding: layout.$large-gap; padding: layout.$large-gap;
background: colors.$gray-50; background: colors.$gray-50;
@include colors.card-shadow; @include colors.card-shadow;
&:last-of-type, &:last-of-type,
&:target { &:target {
display: block; display: block;
+ a { + a {
font-weight: typography.$emphasized-weight; font-weight: typography.$emphasized-weight;
color: colors.$blue-800; color: colors.$blue-800;
} }
} }
&:target { &:target {
~ .tab { ~ .tab {
display: none; display: none;
+ a { + a {
font-weight: inherit; font-weight: inherit;
color: inherit; color: inherit;
} }
} }
} }
} }
} }

View file

@ -4,107 +4,108 @@
@use '../lib/motion'; @use '../lib/motion';
.timeline { .timeline {
$stampSize: 4rem; $stampSize: 4rem;
$lineWeight: 0.2rem; $lineWeight: 0.2rem;
$itemSpacing: layout.$normal-gap; $itemSpacing: layout.$normal-gap;
@extend %content-gutter; @extend %content-gutter;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@media screen and (min-width: layout.$breakpoint) { @media screen and (min-width: layout.$breakpoint) {
flex-direction: row; flex-direction: row;
align-items: flex-start; align-items: flex-start;
> .content { > .content {
position: relative; position: relative;
flex-grow: 1; flex-grow: 1;
margin-left: layout.$normal-gap; margin-left: layout.$normal-gap;
margin-bottom: $itemSpacing; margin-bottom: $itemSpacing;
&::before { &::before {
content: ''; content: '';
position: absolute; position: absolute;
top: 0; top: 0;
right: calc(100% + #{layout.$normal-gap + math.div($stampSize, 2)}); right: calc(100% + #{layout.$normal-gap + math.div($stampSize, 2)});
bottom: #{-1 * $itemSpacing}; bottom: #{-1 * $itemSpacing};
width: $lineWeight; width: $lineWeight;
background-color: colors.$gray-300; background-color: colors.$gray-300;
transform: translateX(50%); transform: translateX(50%);
transition: background-color motion.$subtle; transition: background-color motion.$subtle;
} }
> h3:first-child { > h3:first-child {
position: relative; position: relative;
margin-top: 0; margin-top: 0;
margin-left: layout.$large-gap; margin-left: layout.$large-gap;
line-height: $stampSize; line-height: $stampSize;
&::after { &::after {
content: ''; content: '';
position: absolute; position: absolute;
right: calc(100% + #{layout.$small-gap}); right: calc(100% + #{layout.$small-gap});
top: 50%; top: 50%;
width: 4rem; width: 4rem;
height: $lineWeight; height: $lineWeight;
background-color: colors.$gray-300; background-color: colors.$gray-300;
transform: translateY(-100%); transform: translateY(-100%);
transition: background-color motion.$subtle; transition: background-color motion.$subtle;
} }
} }
} }
} }
> .stamp { > .stamp {
display: inline-block; display: inline-block;
flex-shrink: 0; flex-shrink: 0;
width: $stampSize; width: $stampSize;
height: $stampSize; height: $stampSize;
z-index: 10; // To lift it above the line. z-index: 10; // To lift it above the line.
border: $lineWeight solid colors.$gray-300; border: $lineWeight solid colors.$gray-300;
border-radius: 100%; border-radius: 100%;
line-height: #{$stampSize - 2 * $lineWeight}; line-height: #{$stampSize - 2 * $lineWeight};
text-align: center; text-align: center;
background-color: colors.$gray-50; background-color: colors.$gray-50;
transition: border-color motion.$subtle, background-color motion.$subtle, color motion.$subtle; transition: border-color motion.$subtle, background-color motion.$subtle,
color motion.$subtle;
&.small { &.small {
background-color: colors.$gray-300; background-color: colors.$gray-300;
} }
} }
> .content { > .content {
> p { > p {
margin-left: 0; margin-left: 0;
margin-right: 0; margin-right: 0;
} }
} }
&:hover { &:hover {
> .stamp { > .stamp {
border-color: colors.$blue-800; border-color: colors.$blue-800;
background-color: colors.$blue-800; background-color: colors.$blue-800;
color: colors.$inverse-text; color: colors.$inverse-text;
} }
> .content { > .content {
&::before, &::before,
> h3:first-child::after { > h3:first-child::after {
background-color: colors.$blue-800; background-color: colors.$blue-800;
} }
} }
} }
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
> .content { > .content {
padding-bottom: layout.$normal-gap; padding-bottom: layout.$normal-gap;
} }
} }
+ .timeline { + .timeline {
margin-top: #{-1 * layout.$normal-gap}; margin-top: #{-1 * layout.$normal-gap};
} }
} }

View file

@ -10,115 +10,164 @@
@use 'lib/motion'; @use 'lib/motion';
.finish-hero { .finish-hero {
@keyframes finish-hero { @keyframes finish-hero {
0% { stroke-width: 3px; } 0% {
10% { stroke-width: 3px; } stroke-width: 3px;
20% { stroke-width: 5px; } }
60% { stroke-width: 5px; } 10% {
100% { stroke-width: 3px; } stroke-width: 3px;
} }
20% {
stroke-width: 5px;
}
60% {
stroke-width: 5px;
}
100% {
stroke-width: 3px;
}
}
display: block; display: block;
height: 15vmin; height: 15vmin;
margin: layout.$huge-gap auto; margin: layout.$huge-gap auto;
overflow: visible; overflow: visible;
stroke-linecap: round; stroke-linecap: round;
stroke-miterlimit: 10; stroke-miterlimit: 10;
animation: motion.$prominent 0s 1 normal both running finish-hero; animation: motion.$prominent 0s 1 normal both running finish-hero;
> .stroke-gradient { > .stroke-gradient {
$idle-color: colors.$gray-900; $idle-color: colors.$gray-900;
$colors: [ $colors: [ $idle-color $idle-color $idle-color colors.$blue-500
$idle-color colors.$teal-500 colors.$yellow-500 colors.$teal-500 colors.$gray-300
$idle-color colors.$yellow-500 colors.$blue-500 colors.$gray-300 $idle-color
$idle-color $idle-color $idle-color ];
colors.$blue-500
colors.$teal-500
colors.$yellow-500
colors.$teal-500
colors.$gray-300
colors.$yellow-500
colors.$blue-500
colors.$gray-300
$idle-color
$idle-color
$idle-color
];
@for $i from 1 through 4 { @for $i from 1 through 4 {
> stop:nth-of-type(#{$i}) { > stop:nth-of-type(#{$i}) {
@keyframes finish-stroke-gradient-#{$i} { @keyframes finish-stroke-gradient-#{$i} {
0% { stop-color: $idle-color; } 0% {
15% { stop-color: $idle-color; } stop-color: $idle-color;
// This is some which magic that chooses the correct colors for each }
// stop - don't change it unless you know what you are doing! In 15% {
// general, the output will look something like this: stop-color: $idle-color;
// <animation stop>: <color stop 1> <color stop 2> ... }
// 20%: 4 3 2 1 // This is some which magic that chooses the correct colors for each
// 25%: 5 4 3 2 // stop - don't change it unless you know what you are doing! In
// 35%: 6 5 4 3 // general, the output will look something like this:
// ... // <animation stop>: <color stop 1> <color stop 2> ...
// In order to achieve the 'moving' effect, we make sure that the // 20%: 4 3 2 1
// first and last three colors match the ones we are given at 15% and // 25%: 5 4 3 2
// 70% (before and after the core animation). // 35%: 6 5 4 3
@for $j from 1 to list.length($colors) - 3 { // ...
#{15% + $j * 5%} { // In order to achieve the 'moving' effect, we make sure that the
stop-color: list.nth($colors, 4 + $j - $i); // first and last three colors match the ones we are given at 15% and
} // 70% (before and after the core animation).
} @for $j from 1 to list.length($colors) - 3 {
70% { stop-color: $idle-color; } #{15% + $j * 5%} {
100% { stop-color: $idle-color; } stop-color: list.nth($colors, 4 + $j - $i);
} }
}
70% {
stop-color: $idle-color;
}
100% {
stop-color: $idle-color;
}
}
animation: animation: motion.$prominent
motion.$prominent 0s 1 normal both running finish-stroke-gradient-#{$i}; 0s
} 1
} normal
} both
running
finish-stroke-gradient-#{$i};
}
}
}
> .cable { > .cable {
@keyframes finish-hero-cable { @keyframes finish-hero-cable {
0% { transform: translateX(0.5rem); } 0% {
20% { transform: translateX(0.5rem); } transform: translateX(0.5rem);
70% { transform: translateX(0.5rem); } }
100% { transform: none; } 20% {
} transform: translateX(0.5rem);
}
70% {
transform: translateX(0.5rem);
}
100% {
transform: none;
}
}
animation: motion.$prominent 0s 1 normal both running finish-hero-cable; animation: motion.$prominent 0s 1 normal both running finish-hero-cable;
} }
> .plug { > .plug {
@keyframes finish-hero-plug { @keyframes finish-hero-plug {
0% { transform: translateX(-0.5rem); } 0% {
20% { transform: translateX(-0.5rem); } transform: translateX(-0.5rem);
70% { transform: translateX(-0.5rem); } }
100% { transform: none; } 20% {
} transform: translateX(-0.5rem);
@keyframes finish-hero-plug-transition { }
0% { fill: colors.$gray-900; } 70% {
100% { fill: colors.$gray-600; } transform: translateX(-0.5rem);
} }
@keyframes finish-hero-plug-idle { 100% {
0% { fill: colors.$gray-600; } transform: none;
25% { fill: colors.$teal-600; } }
50% { fill: colors.$yellow-600; } }
75% { fill: colors.$blue-800; } @keyframes finish-hero-plug-transition {
100% { fill: colors.$gray-600; } 0% {
} fill: colors.$gray-900;
}
100% {
fill: colors.$gray-600;
}
}
@keyframes finish-hero-plug-idle {
0% {
fill: colors.$gray-600;
}
25% {
fill: colors.$teal-600;
}
50% {
fill: colors.$yellow-600;
}
75% {
fill: colors.$blue-800;
}
100% {
fill: colors.$gray-600;
}
}
animation: motion.$prominent 0s 1 normal both running finish-hero-plug, animation: motion.$prominent 0s 1 normal both running finish-hero-plug,
motion.$gentle 0.7s 1 normal forwards running finish-hero-plug-transition, motion.$gentle 0.7s 1 normal forwards running finish-hero-plug-transition,
motion.$background 1s infinite normal none running finish-hero-plug-idle; motion.$background 1s infinite normal none running finish-hero-plug-idle;
} }
> .contacts { > .contacts {
@keyframes finish-hero-contacts { @keyframes finish-hero-contacts {
0% { transform: translateX(0rem); } 0% {
20% { transform: translateX(-0.7rem); } transform: translateX(0rem);
70% { transform: translateX(-0.7rem); } }
100% { transform: none; } 20% {
} transform: translateX(-0.7rem);
}
70% {
transform: translateX(-0.7rem);
}
100% {
transform: none;
}
}
animation: motion.$prominent 0s 1 normal both running finish-hero-contacts; animation: motion.$prominent 0s 1 normal both running finish-hero-contacts;
} }
} }

View file

@ -20,10 +20,10 @@ $main-background: $gray-50;
$inverse-text: $gray-50; $inverse-text: $gray-50;
@mixin block-shadow { @mixin block-shadow {
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
} }
@mixin card-shadow($base-color: $gray-900) { @mixin card-shadow($base-color: $gray-900) {
box-shadow: 0.1rem 0.4rem 0.4rem #{transparentize($base-color, 0.9)}, box-shadow: 0.1rem 0.4rem 0.4rem #{transparentize($base-color, 0.9)},
0.25rem 1rem 1rem #{transparentize($base-color, 0.9)}; 0.25rem 1rem 1rem #{transparentize($base-color, 0.9)};
} }

View file

@ -11,39 +11,39 @@ $content-width: 60rem;
$wide-content-width: 80rem; $wide-content-width: 80rem;
%narrow-content { %narrow-content {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
max-width: $narrow-content-width; max-width: $narrow-content-width;
} }
%narrow-content-gutter { %narrow-content-gutter {
margin: $normal-gap auto; margin: $normal-gap auto;
max-width: $narrow-content-width; max-width: $narrow-content-width;
} }
%content { %content {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
max-width: $content-width; max-width: $content-width;
} }
%content-gutter { %content-gutter {
margin: $normal-gap $normal-gap; margin: $normal-gap $normal-gap;
@media screen and (min-width: $breakpoint) { @media screen and (min-width: $breakpoint) {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
max-width: $content-width; max-width: $content-width;
} }
} }
%wide-content { %wide-content {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
max-width: $wide-content-width; max-width: $wide-content-width;
} }
%wide-content-gutter { %wide-content-gutter {
margin: $normal-gap auto; margin: $normal-gap auto;
max-width: $wide-content-width; max-width: $wide-content-width;
} }

View file

@ -1,4 +1,4 @@
$subtle: 0.1s cubic-bezier(0.56, 0.03, 0.35, 0.9); $subtle: 0.1s cubic-bezier(0.56, 0.03, 0.35, 0.9);
$gentle: 0.2s cubic-bezier(1, 0.11, 0.41, 0.69); $gentle: 0.2s cubic-bezier(1, 0.11, 0.41, 0.69);
$prominent: 0.7s cubic-bezier(.45,.16,.38,.7); $prominent: 0.7s cubic-bezier(0.45, 0.16, 0.38, 0.7);
$background: 8s cubic-bezier(.45,.16,.38,.7); $background: 8s cubic-bezier(0.45, 0.16, 0.38, 0.7);

View file

@ -11,45 +11,45 @@ $base-line-height: 1.5;
$heading-line-height: 1.3; $heading-line-height: 1.3;
$comfortaa-weights: ( $comfortaa-weights: (
'Light': 300, 'Light': 300,
'Regular': 400, 'Regular': 400,
'Medium': 500, 'Medium': 500,
'Semi-bold': 600, 'Semi-bold': 600,
'Bold': 700, 'Bold': 700,
); );
@mixin root-config { @mixin root-config {
@each $name, $weight in $comfortaa-weights { @each $name, $weight in $comfortaa-weights {
@font-face { @font-face {
font-family: 'Comfortaa'; font-family: 'Comfortaa';
font-style: normal; font-style: normal;
font-weight: $weight; font-weight: $weight;
font-display: swap; font-display: swap;
src: url('/assets/fonts/Comfortaa-#{$name}.ttf') format('truetype'); src: url('/assets/fonts/Comfortaa-#{$name}.ttf') format('truetype');
} }
} }
@font-face { @font-face {
font-family: 'Comfortaa Variable'; font-family: 'Comfortaa Variable';
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
src: url('/assets/fonts/Comfortaa-VariableFont_wght.ttf') format('truetype'); src: url('/assets/fonts/Comfortaa-VariableFont_wght.ttf') format('truetype');
} }
html { html {
$fallback-fonts: Roboto, Arial, sans-serif; $fallback-fonts: Roboto, Arial, sans-serif;
font-size: 125%; // Scale from 16px to 20px font-size: 125%; // Scale from 16px to 20px
font-family: Comfortaa, $fallback-fonts; font-family: Comfortaa, $fallback-fonts;
font-weight: $normal-weight; font-weight: $normal-weight;
@supports (font-variation-settings: normal) { @supports (font-variation-settings: normal) {
font-family: 'Comfortaa Variable', $fallback-fonts; font-family: 'Comfortaa Variable', $fallback-fonts;
} }
} }
body { body {
font-size: $base-size; font-size: $base-size;
line-height: $base-line-height; line-height: $base-line-height;
} }
} }