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
[*]
indent_style = space
indent_style = tab
indent_size = 2
end_of_line = lf
insert_final_newline = true

View file

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

View file

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

View file

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

View file

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

View file

@ -1,102 +1,102 @@
<!doctype html>
<html lang="{{ metadata.language }}">
<head>
<meta charset="utf-8">
<title>{{ title or metadata.title }}</title>
<head>
<meta charset="utf-8">
<title>{{ title or metadata.title }}</title>
<link rel="preload" href="{{ '/assets/css/base.css' | url }}" as="style">
{# 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
preloading either:
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">
{% for name in (extraStylesheets or []) %}
<link rel="preload" href="{{ '/assets/css/' + name + '.css' | url }}" as="style">
{% endfor %}
{% block headStart %}{% endblock %}
<link rel="preload" href="{{ '/assets/css/base.css' | url }}" as="style">
{# 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
preloading either:
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">
{% for name in (extraStylesheets or []) %}
<link rel="preload" href="{{ '/assets/css/' + name + '.css' | url }}" as="style">
{% endfor %}
{% block headStart %}{% endblock %}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<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.jsonfeed.path | url }}" type="application/json" title="{{ metadata.title }}">
<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.jsonfeed.path | url }}" type="application/json" title="{{ metadata.title }}">
<link rel="stylesheet" href="{{ '/assets/css/base.css' | url }}">
{% for name in (extraStylesheets or []) %}
<link rel="stylesheet" href="{{ '/assets/css/' + name + '.css' | url }}">
{% endfor %}
<link rel="stylesheet" href="{{ '/assets/css/base.css' | url }}">
{% for name in (extraStylesheets or []) %}
<link rel="stylesheet" href="{{ '/assets/css/' + name + '.css' | url }}">
{% endfor %}
{% block headEnd %}{% endblock %}
</head>
<body>
<header class="site-header">
<a class="site-logo" href="{{ '/' | url }}">{{ metadata.title }}</a>
{% block headEnd %}{% endblock %}
</head>
<body>
<header class="site-header">
<a class="site-logo" href="{{ '/' | url }}">{{ metadata.title }}</a>
<details class="site-mobile-navigation">
<summary>Menü</summary>
<nav class="site-navigation">
<ul>
{% for entry in collections.topNavigation | eleventyNavigation %}
<li{% if entry.url == page.url %} class="active"{% endif %}>
<a href="{{ entry.url }}">
{{ entry.title }}
</a>
</li>
{% endfor %}
</ul>
</nav>
</details>
<details class="site-mobile-navigation">
<summary>Menü</summary>
<nav class="site-navigation">
<ul>
{% for entry in collections.topNavigation | eleventyNavigation %}
<li{% if entry.url == page.url %} class="active"{% endif %}>
<a href="{{ entry.url }}">
{{ entry.title }}
</a>
</li>
{% endfor %}
</ul>
</nav>
</details>
<nav class="site-navigation horizontal">
<ul>
{% for entry in collections.topNavigation | eleventyNavigation %}
<li{% if entry.url == page.url %} class="active"{% endif %}>
<a href="{{ entry.url }}">
{{ entry.title }}
</a>
</li>
{% endfor %}
</ul>
</nav>
</header>
<nav class="site-navigation horizontal">
<ul>
{% for entry in collections.topNavigation | eleventyNavigation %}
<li{% if entry.url == page.url %} class="active"{% endif %}>
<a href="{{ entry.url }}">
{{ entry.title }}
</a>
</li>
{% endfor %}
</ul>
</nav>
</header>
<main id="content" {% if contentClass %} class="{{ contentClass }}"{% endif %}>
{% block content %}
{{ content | safe }}
{% endblock %}
</main>
<main id="content" {% if contentClass %} class="{{ contentClass }}"{% endif %}>
{% block content %}
{{ content | safe }}
{% endblock %}
</main>
<footer class="page-section footer">
<div class="page-content site-footer">
<div>
<p>
{{ metadata.author.name }}
{% for line in (metadata.author.address or []) %}
<br />
{{ line }}
{% endfor %}
</p>
<p>
{{ metadata.author.email }}
</p>
</div>
<footer class="page-section footer">
<div class="page-content site-footer">
<div>
<p>
{{ metadata.author.name }}
{% for line in (metadata.author.address or []) %}
<br />
{{ line }}
{% endfor %}
</p>
<p>
{{ metadata.author.email }}
</p>
</div>
<div>
<nav class="site-navigation">
<ul>
{% for entry in collections.bottomNavigation | eleventyNavigation %}
<li{% if entry.url == page.url %} class="active"{% endif %}>
<a href="{{ entry.url }}">
{{ entry.title }}
</a>
</li>
{% endfor %}
</ul>
</nav>
</div>
</div>
</footer>
</body>
<div>
<nav class="site-navigation">
<ul>
{% for entry in collections.bottomNavigation | eleventyNavigation %}
<li{% if entry.url == page.url %} class="active"{% endif %}>
<a href="{{ entry.url }}">
{{ entry.title }}
</a>
</li>
{% endfor %}
</ul>
</nav>
</div>
</div>
</footer>
</body>
</html>

View file

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

View file

@ -17,7 +17,7 @@ templateClass: tmpl-post
{%- if nextPost or previousPost %}
<hr>
<ul>
{%- 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 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 %}
</ul>
{%- endif %}

View file

@ -7,5 +7,5 @@
@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
// number of CTA-style buttons which lead to different parts of the site.
.page-actions {
padding: layout.$large-gap;
background-color: colors.$teal-300;
padding: layout.$large-gap;
background-color: colors.$teal-300;
> *:not(:last-child) {
margin-bottom: layout.$large-gap;
}
> *:not(:last-child) {
margin-bottom: layout.$large-gap;
}
> div {
> *:first-child {
margin-top: 0;
}
> div {
> *:first-child {
margin-top: 0;
}
> *:last-child {
margin-bottom: 0;
}
}
> *:last-child {
margin-bottom: 0;
}
}
> a {
display: flex;
flex-direction: column-reverse;
margin: layout.$large-gap 0;
padding: layout.$large-gap;
background: colors.$gray-50;
color: inherit;
text-decoration: none;
@include colors.card-shadow;
transition: motion.$subtle background-color, motion.$subtle transform;
> a {
display: flex;
flex-direction: column-reverse;
margin: layout.$large-gap 0;
padding: layout.$large-gap;
background: colors.$gray-50;
color: inherit;
text-decoration: none;
@include colors.card-shadow;
transition: motion.$subtle background-color, motion.$subtle transform;
> h3 {
margin: 0;
text-transform: uppercase;
}
> h3 {
margin: 0;
text-transform: uppercase;
}
> svg {
align-self: center;
margin-bottom: layout.$normal-gap;
height: 10rem;
}
> svg {
align-self: center;
margin-bottom: layout.$normal-gap;
height: 10rem;
}
&:hover {
background-color: colors.$gray-100;
transform: translateY(-0.5rem);
&:hover {
background-color: colors.$gray-100;
transform: translateY(-0.5rem);
> .action-icon {
@extend %action-icon-hover;
}
}
}
> .action-icon {
@extend %action-icon-hover;
}
}
}
@media screen and (min-width: layout.$breakpoint) {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: layout.$large-gap;
align-items: stretch;
@media screen and (min-width: layout.$breakpoint) {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: layout.$large-gap;
align-items: stretch;
> * {
grid-column: span 3;
> * {
grid-column: span 3;
&:not(:last-child) {
margin-bottom: 0;
}
}
&:not(:last-child) {
margin-bottom: 0;
}
}
> a {
margin: 0;
> a {
margin: 0;
&.first {
position: relative;
bottom: layout.$huge-gap;
}
&.first {
position: relative;
bottom: layout.$huge-gap;
}
&:not(.first) {
grid-column: span 2;
}
}
}
&:not(.first) {
grid-column: span 2;
}
}
}
}
.action-icon {
overflow: visible;
fill: colors.$main-text;
// This seems to prevent jittering in Firefox:
pointer-events: none;
overflow: visible;
fill: colors.$main-text;
// This seems to prevent jittering in Firefox:
pointer-events: none;
&:hover {
@extend %action-icon-hover;
}
&:hover {
@extend %action-icon-hover;
}
.emphasis-gradient > stop {
stop-color: colors.$blue-500;
transition: motion.$gentle stop-color;
}
.emphasis-gradient > stop {
stop-color: colors.$blue-500;
transition: motion.$gentle stop-color;
}
.emphasis {
transition: motion.$gentle transform;
.emphasis {
transition: motion.$gentle transform;
&.heart-left {
transform-origin: top left;
}
&.heart-left {
transform-origin: top left;
}
&.heart-right {
transform-origin: top right;
}
&.heart-right {
transform-origin: top right;
}
&.coin {
transform-origin: 58% 39%;
}
}
&.coin {
transform-origin: 58% 39%;
}
}
}
%action-icon-hover {
.emphasis-gradient {
@keyframes action-icon-emphasis-gradient-1 {
0% { stop-color: colors.$blue-500; }
40% { stop-color: colors.$yellow-500; }
80% { stop-color: colors.$teal-500; }
}
.emphasis-gradient {
@keyframes action-icon-emphasis-gradient-1 {
0% {
stop-color: colors.$blue-500;
}
40% {
stop-color: colors.$yellow-500;
}
80% {
stop-color: colors.$teal-500;
}
}
@keyframes action-icon-emphasis-gradient-2 {
0% { stop-color: colors.$blue-500; }
20% { stop-color: colors.$blue-800; }
60% { stop-color: colors.$yellow-500; }
100% { stop-color: colors.$teal-500; }
}
@keyframes action-icon-emphasis-gradient-2 {
0% {
stop-color: colors.$blue-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 {
> stop:nth-of-type(#{$i}) {
stop-color: colors.$teal-500;
animation:
motion.$prominent 0s 1 normal backwards running
action-icon-emphasis-gradient-#{$i};
}
}
}
@for $i from 1 through 2 {
> stop:nth-of-type(#{$i}) {
stop-color: colors.$teal-500;
animation: motion.$prominent
0s
1
normal
backwards
running
action-icon-emphasis-gradient-#{$i};
}
}
}
.heart-left {
$final-transformation:
translateX(-0.8rem) translateY(1.4rem) scale(1.5) rotate(-25deg);
.heart-left {
$final-transformation: translateX(-0.8rem) translateY(1.4rem) scale(1.5)
rotate(-25deg);
@keyframes action-icon-heart-left {
0% { transform: none; }
50% {
transform:
translateX(0.2rem) translateY(0.8rem) scale(1.2) rotate(-10deg);
}
100% { transform: $final-transformation; }
}
@keyframes action-icon-heart-left {
0% {
transform: none;
}
50% {
transform: translateX(0.2rem) translateY(0.8rem) scale(1.2)
rotate(-10deg);
}
100% {
transform: $final-transformation;
}
}
transform: $final-transformation;
animation:
motion.$gentle 0s 1 normal backwards running action-icon-heart-left;
}
transform: $final-transformation;
animation: motion.$gentle 0s 1 normal backwards running
action-icon-heart-left;
}
.heart-right {
$final-transformation:
translateX(1.4rem) translateY(-0.1rem) scale(1.6) rotate(15deg);
.heart-right {
$final-transformation: translateX(1.4rem) translateY(-0.1rem) scale(1.6)
rotate(15deg);
@keyframes action-icon-heart-right {
0% { transform: none; }
50% {
transform:
translateX(1.3rem) translateY(0.8rem) scale(1.4) rotate(30deg);
}
100% { transform: $final-transformation; }
}
@keyframes action-icon-heart-right {
0% {
transform: none;
}
50% {
transform: translateX(1.3rem) translateY(0.8rem) scale(1.4)
rotate(30deg);
}
100% {
transform: $final-transformation;
}
}
transform: $final-transformation;
animation:
motion.$gentle 0s 1 normal backwards running action-icon-heart-right;
}
transform: $final-transformation;
animation: motion.$gentle 0s 1 normal backwards running
action-icon-heart-right;
}
.coin {
$final-transformation: scale(0.8);
.coin {
$final-transformation: scale(0.8);
@keyframes action-icon-coin {
0% { transform: none; }
50% { transform: scale(1.1); }
100% { transform: $final-transformation; }
}
@keyframes action-icon-coin {
0% {
transform: none;
}
50% {
transform: scale(1.1);
}
100% {
transform: $final-transformation;
}
}
transform: $final-transformation;
animation: motion.$gentle 0s 1 normal backwards running action-icon-coin;
}
transform: $final-transformation;
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 .content which holds the actual text.
.page-banner {
display: flex;
flex-direction: column;
justify-content: space-around;
position: relative;
min-height: 60vh;
background-color: colors.$blue-800;
display: flex;
flex-direction: column;
justify-content: space-around;
position: relative;
min-height: 60vh;
background-color: colors.$blue-800;
> .background {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
> .background {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
> .content {
display: flex;
flex-direction: column;
align-items: flex-start;
position: relative;
padding: layout.$normal-gap max(#{layout.$large-gap}, 15vw);
> .content {
display: flex;
flex-direction: column;
align-items: flex-start;
position: relative;
padding: layout.$normal-gap max(#{layout.$large-gap}, 15vw);
> div {
display: inline-block;
padding: layout.$small-gap layout.$normal-gap;
font-size: typography.$subheading-size;
background-color: colors.$yellow-600;
> div {
display: inline-block;
padding: layout.$small-gap layout.$normal-gap;
font-size: typography.$subheading-size;
background-color: colors.$yellow-600;
> p:first-child {
margin-top: 0;
}
> p:first-child {
margin-top: 0;
}
> p:last-child {
margin-bottom: 0;
}
}
> p:last-child {
margin-bottom: 0;
}
}
> .title {
padding: 0 layout.$normal-gap;
line-height: 5rem;
font-size: typography.$title-size;
font-weight: typography.$emphasized-weight;
background-color: colors.$teal-500;
}
}
> .title {
padding: 0 layout.$normal-gap;
line-height: 5rem;
font-size: typography.$title-size;
font-weight: typography.$emphasized-weight;
background-color: colors.$teal-500;
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,115 +10,164 @@
@use 'lib/motion';
.finish-hero {
@keyframes finish-hero {
0% { stroke-width: 3px; }
10% { stroke-width: 3px; }
20% { stroke-width: 5px; }
60% { stroke-width: 5px; }
100% { stroke-width: 3px; }
}
@keyframes finish-hero {
0% {
stroke-width: 3px;
}
10% {
stroke-width: 3px;
}
20% {
stroke-width: 5px;
}
60% {
stroke-width: 5px;
}
100% {
stroke-width: 3px;
}
}
display: block;
height: 15vmin;
margin: layout.$huge-gap auto;
overflow: visible;
stroke-linecap: round;
stroke-miterlimit: 10;
animation: motion.$prominent 0s 1 normal both running finish-hero;
display: block;
height: 15vmin;
margin: layout.$huge-gap auto;
overflow: visible;
stroke-linecap: round;
stroke-miterlimit: 10;
animation: motion.$prominent 0s 1 normal both running finish-hero;
> .stroke-gradient {
$idle-color: colors.$gray-900;
$colors: [
$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
];
> .stroke-gradient {
$idle-color: colors.$gray-900;
$colors: [ $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 {
> stop:nth-of-type(#{$i}) {
@keyframes finish-stroke-gradient-#{$i} {
0% { stop-color: $idle-color; }
15% { 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
// general, the output will look something like this:
// <animation stop>: <color stop 1> <color stop 2> ...
// 20%: 4 3 2 1
// 25%: 5 4 3 2
// 35%: 6 5 4 3
// ...
// In order to achieve the 'moving' effect, we make sure that the
// 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 {
#{15% + $j * 5%} {
stop-color: list.nth($colors, 4 + $j - $i);
}
}
70% { stop-color: $idle-color; }
100% { stop-color: $idle-color; }
}
@for $i from 1 through 4 {
> stop:nth-of-type(#{$i}) {
@keyframes finish-stroke-gradient-#{$i} {
0% {
stop-color: $idle-color;
}
15% {
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
// general, the output will look something like this:
// <animation stop>: <color stop 1> <color stop 2> ...
// 20%: 4 3 2 1
// 25%: 5 4 3 2
// 35%: 6 5 4 3
// ...
// In order to achieve the 'moving' effect, we make sure that the
// 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 {
#{15% + $j * 5%} {
stop-color: list.nth($colors, 4 + $j - $i);
}
}
70% {
stop-color: $idle-color;
}
100% {
stop-color: $idle-color;
}
}
animation:
motion.$prominent 0s 1 normal both running finish-stroke-gradient-#{$i};
}
}
}
animation: motion.$prominent
0s
1
normal
both
running
finish-stroke-gradient-#{$i};
}
}
}
> .cable {
@keyframes finish-hero-cable {
0% { transform: translateX(0.5rem); }
20% { transform: translateX(0.5rem); }
70% { transform: translateX(0.5rem); }
100% { transform: none; }
}
> .cable {
@keyframes finish-hero-cable {
0% {
transform: translateX(0.5rem);
}
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 {
@keyframes finish-hero-plug {
0% { transform: translateX(-0.5rem); }
20% { transform: translateX(-0.5rem); }
70% { transform: translateX(-0.5rem); }
100% { transform: none; }
}
@keyframes finish-hero-plug-transition {
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; }
}
> .plug {
@keyframes finish-hero-plug {
0% {
transform: translateX(-0.5rem);
}
20% {
transform: translateX(-0.5rem);
}
70% {
transform: translateX(-0.5rem);
}
100% {
transform: none;
}
}
@keyframes finish-hero-plug-transition {
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,
motion.$gentle 0.7s 1 normal forwards running finish-hero-plug-transition,
motion.$background 1s infinite normal none running finish-hero-plug-idle;
}
animation: motion.$prominent 0s 1 normal both running finish-hero-plug,
motion.$gentle 0.7s 1 normal forwards running finish-hero-plug-transition,
motion.$background 1s infinite normal none running finish-hero-plug-idle;
}
> .contacts {
@keyframes finish-hero-contacts {
0% { transform: translateX(0rem); }
20% { transform: translateX(-0.7rem); }
70% { transform: translateX(-0.7rem); }
100% { transform: none; }
}
> .contacts {
@keyframes finish-hero-contacts {
0% {
transform: translateX(0rem);
}
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;
@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) {
box-shadow: 0.1rem 0.4rem 0.4rem #{transparentize($base-color, 0.9)},
0.25rem 1rem 1rem #{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)};
}

View file

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

View file

@ -1,4 +1,4 @@
$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);
$prominent: 0.7s cubic-bezier(.45,.16,.38,.7);
$background: 8s cubic-bezier(.45,.16,.38,.7);
$prominent: 0.7s cubic-bezier(0.45, 0.16, 0.38, 0.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;
$comfortaa-weights: (
'Light': 300,
'Regular': 400,
'Medium': 500,
'Semi-bold': 600,
'Bold': 700,
'Light': 300,
'Regular': 400,
'Medium': 500,
'Semi-bold': 600,
'Bold': 700,
);
@mixin root-config {
@each $name, $weight in $comfortaa-weights {
@font-face {
font-family: 'Comfortaa';
font-style: normal;
font-weight: $weight;
font-display: swap;
src: url('/assets/fonts/Comfortaa-#{$name}.ttf') format('truetype');
}
}
@each $name, $weight in $comfortaa-weights {
@font-face {
font-family: 'Comfortaa';
font-style: normal;
font-weight: $weight;
font-display: swap;
src: url('/assets/fonts/Comfortaa-#{$name}.ttf') format('truetype');
}
}
@font-face {
font-family: 'Comfortaa Variable';
font-style: normal;
font-display: swap;
src: url('/assets/fonts/Comfortaa-VariableFont_wght.ttf') format('truetype');
}
@font-face {
font-family: 'Comfortaa Variable';
font-style: normal;
font-display: swap;
src: url('/assets/fonts/Comfortaa-VariableFont_wght.ttf') format('truetype');
}
html {
$fallback-fonts: Roboto, Arial, sans-serif;
html {
$fallback-fonts: Roboto, Arial, sans-serif;
font-size: 125%; // Scale from 16px to 20px
font-family: Comfortaa, $fallback-fonts;
font-weight: $normal-weight;
font-size: 125%; // Scale from 16px to 20px
font-family: Comfortaa, $fallback-fonts;
font-weight: $normal-weight;
@supports (font-variation-settings: normal) {
font-family: 'Comfortaa Variable', $fallback-fonts;
}
}
@supports (font-variation-settings: normal) {
font-family: 'Comfortaa Variable', $fallback-fonts;
}
}
body {
font-size: $base-size;
line-height: $base-line-height;
}
body {
font-size: $base-size;
line-height: $base-line-height;
}
}