- footer: 'built <ts> · <hash> · <n> notes', hash linking to Gitea (GARDEN_COMMIT passed in by build.sh; '+' marks dirty-tree builds) - homepage header: live strip fed by api.c0smere.net/spotify/summary (weekly aggregate + monthly top artist/genre), fail-closed like the odometer and header quote - /og/<slug>.png satori+resvg cards for every note + the homepage; og:/twitter: meta in Base - shiki github-dark-default (#0d1117 sits closer to the page ground) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
820 lines
23 KiB
Plaintext
820 lines
23 KiB
Plaintext
---
|
||
import 'katex/dist/katex.min.css';
|
||
import { getCollection } from 'astro:content';
|
||
import { buildNavTree } from '../lib/nav.mjs';
|
||
import { COMMIT, BUILT_AT } from '../lib/build-info.mjs';
|
||
import NavTree from '../components/NavTree.astro';
|
||
import Toc from '../components/Toc.astro';
|
||
|
||
interface Props {
|
||
title: string;
|
||
description?: string;
|
||
headings?: { depth: number; slug: string; text: string }[];
|
||
// which /og/<slug>.png card this page advertises; synthetic listing
|
||
// pages fall back to the site card
|
||
ogSlug?: string;
|
||
}
|
||
const { title, description, headings = [], ogSlug = 'index' } = Astro.props;
|
||
const isHome = Astro.url.pathname === '/';
|
||
const entries = await getCollection('garden');
|
||
const navTree = buildNavTree(entries);
|
||
const noteCount = entries.length;
|
||
const showToc = headings.filter((h) => h.depth <= 3).length >= 2;
|
||
const ogImage = new URL(`/og/${ogSlug}.png`, Astro.site);
|
||
const pageUrl = new URL(Astro.url.pathname, Astro.site);
|
||
---
|
||
|
||
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>{title}{isHome ? '' : ' // Wesley Ray'}</title>
|
||
<meta name="theme-color" content="#0b0d12" />
|
||
{description && <meta name="description" content={description} />}
|
||
<meta property="og:title" content={title} />
|
||
<meta property="og:type" content={isHome ? 'website' : 'article'} />
|
||
<meta property="og:url" content={pageUrl} />
|
||
{description && <meta property="og:description" content={description} />}
|
||
<meta property="og:image" content={ogImage} />
|
||
<meta property="og:image:width" content="1200" />
|
||
<meta property="og:image:height" content="630" />
|
||
<meta name="twitter:card" content="summary_large_image" />
|
||
<link
|
||
rel="preload"
|
||
href="/fonts/jetbrains-mono-v13-latin-regular.woff2"
|
||
as="font"
|
||
type="font/woff2"
|
||
crossorigin
|
||
/>
|
||
<link
|
||
rel="preload"
|
||
href="/fonts/jetbrains-mono-v13-latin-700.woff2"
|
||
as="font"
|
||
type="font/woff2"
|
||
crossorigin
|
||
/>
|
||
<script
|
||
defer
|
||
data-domain="garden.c0smere.net"
|
||
src="https://plausible.io/js/script.js"></script>
|
||
</head>
|
||
<body class={isHome ? 'home' : undefined}>
|
||
<div class="layout">
|
||
<div class="content-col">
|
||
<header>
|
||
<div class="site-head">
|
||
<a class="site-logo" href="/">Wesley Ray</a>
|
||
<nav class="site-nav">
|
||
<a href="https://blog.c0smere.net">blog</a>
|
||
<a href="https://git.c0smere.net/wes">git</a>
|
||
<a href="https://resume.c0smere.net/raywes88/easy-bronze-gazelle"
|
||
>resume</a
|
||
>
|
||
</nav>
|
||
</div>
|
||
<div id="hq" class="header-quote" hidden>
|
||
<span id="hq-text"></span>
|
||
<span id="hq-attr" class="hq-attr"></span>
|
||
</div>
|
||
{
|
||
isHome && (
|
||
<div id="live-stats" class="live-stats" hidden>
|
||
<span class="live-dot" aria-hidden="true" />
|
||
<span id="live-spotify" />
|
||
</div>
|
||
)
|
||
}
|
||
</header>
|
||
<main>
|
||
<slot />
|
||
</main>
|
||
<footer>
|
||
<div class="footer-quotes">
|
||
<p>
|
||
Failure is unique among sin, in that we can turn it into a
|
||
virtue when it drives us to succeed.
|
||
</p>
|
||
<p>The living diminish, but the machine endures...</p>
|
||
</div>
|
||
<p>
|
||
Wesley Ray · <a href="https://blog.c0smere.net">blog</a> ·
|
||
<a href="https://git.c0smere.net/wes">git</a> ·
|
||
<a href="https://resume.c0smere.net/raywes88/easy-bronze-gazelle">resume</a>
|
||
</p>
|
||
<div id="odometer" hidden>
|
||
<div class="odo-display" id="odo-digits" aria-live="off"></div>
|
||
<p class="odo-caption">
|
||
bytes in + out of the homelab since <span id="odo-since"></span> ·
|
||
ticking at the lifetime average
|
||
</p>
|
||
</div>
|
||
{
|
||
COMMIT && (
|
||
<p class="build-stamp">
|
||
built {BUILT_AT} ET ·{' '}
|
||
<a
|
||
href={`https://git.c0smere.net/wes/garden-astro/commit/${COMMIT.replace('+', '')}`}
|
||
>
|
||
{COMMIT}
|
||
</a>{' '}
|
||
· {noteCount} notes
|
||
</p>
|
||
)
|
||
}
|
||
</footer>
|
||
</div>
|
||
<aside class="sidebar">
|
||
<p class="sidebar-title"><a href="/">the garden</a></p>
|
||
<NavTree nodes={navTree} currentPath={Astro.url.pathname} />
|
||
</aside>
|
||
{
|
||
showToc && (
|
||
<aside class="toc-col">
|
||
<Toc headings={headings} />
|
||
</aside>
|
||
)
|
||
}
|
||
</div>
|
||
|
||
<script>
|
||
// Header quote: one random reading highlight per page view.
|
||
// Fail-closed: any error leaves the slot hidden.
|
||
try {
|
||
const res = await fetch(
|
||
'https://api.c0smere.net/highlights/random?max_len=280',
|
||
);
|
||
if (!res.ok) throw new Error(String(res.status));
|
||
const q = await res.json();
|
||
document.getElementById('hq-text')!.textContent = `“${q.text}”`;
|
||
document.getElementById('hq-attr')!.textContent = q.author
|
||
? `— ${q.author}, ${q.book}`
|
||
: `— ${q.book}`;
|
||
document.getElementById('hq')!.hidden = false;
|
||
} catch {
|
||
/* API down or blocked: slot stays hidden */
|
||
}
|
||
</script>
|
||
|
||
<script>
|
||
// Homepage live strip: weekly listening aggregates from the API.
|
||
// Fail-closed: any error (or non-home page) leaves the slot hidden.
|
||
const strip = document.getElementById('live-stats');
|
||
if (strip) {
|
||
try {
|
||
const res = await fetch('https://api.c0smere.net/spotify/summary');
|
||
if (!res.ok) throw new Error(String(res.status));
|
||
const s = await res.json();
|
||
const slot = document.getElementById('live-spotify')!;
|
||
const put = (text: string, strong = false) => {
|
||
const el = document.createElement(strong ? 'span' : 'span');
|
||
if (strong) el.className = 'v';
|
||
el.textContent = text;
|
||
slot.append(el);
|
||
};
|
||
put(String(s.week_minutes), true);
|
||
put(' min of listening this week');
|
||
if (s.top_artist_month) {
|
||
put(' · lately: ');
|
||
put(s.top_artist_month, true);
|
||
}
|
||
if (s.top_genre_month) {
|
||
put(' · ');
|
||
put(s.top_genre_month, true);
|
||
}
|
||
strip.hidden = false;
|
||
} catch {
|
||
/* API down or blocked: strip stays hidden */
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<script>
|
||
// Odometer: fetch totals once, then animate forward at the lifetime
|
||
// average rate. Looks live, but deliberately never exposes current
|
||
// throughput. Fail-closed: any error leaves the footer widget hidden.
|
||
const API = 'https://api.c0smere.net/bandwidth/odometer';
|
||
const section = document.getElementById('odometer')!;
|
||
const digitsEl = document.getElementById('odo-digits')!;
|
||
const sinceEl = document.getElementById('odo-since')!;
|
||
|
||
function renderDigits(total: bigint) {
|
||
const s = total.toString();
|
||
let html = '';
|
||
for (let i = 0; i < s.length; i++) {
|
||
if (i > 0 && (s.length - i) % 3 === 0)
|
||
html += '<span class="odo-sep"></span>';
|
||
html += `<span class="odo-digit">${s[i]}</span>`;
|
||
}
|
||
digitsEl.innerHTML = html;
|
||
}
|
||
|
||
try {
|
||
const res = await fetch(API);
|
||
if (!res.ok) throw new Error(String(res.status));
|
||
const data = await res.json();
|
||
const base = BigInt(data.total_bytes);
|
||
const rate = Number(data.avg_bytes_per_sec);
|
||
const asOf = Date.parse(data.as_of);
|
||
if (!Number.isFinite(rate) || !Number.isFinite(asOf))
|
||
throw new Error('bad payload');
|
||
|
||
sinceEl.textContent = new Date(data.since).toLocaleDateString(
|
||
undefined,
|
||
{ year: 'numeric', month: 'long', day: 'numeric' },
|
||
);
|
||
|
||
const tick = () => {
|
||
const elapsed = (Date.now() - asOf) / 1000;
|
||
renderDigits(base + BigInt(Math.floor(elapsed * rate)));
|
||
};
|
||
tick();
|
||
section.hidden = false;
|
||
setInterval(tick, 100);
|
||
} catch {
|
||
/* API down or blocked: widget stays hidden */
|
||
}
|
||
</script>
|
||
|
||
<script>
|
||
// mermaid: only load the (large) module on pages that need it
|
||
const blocks = document.querySelectorAll('pre > code.language-mermaid');
|
||
if (blocks.length) {
|
||
const { default: mermaid } = await import('mermaid');
|
||
blocks.forEach((code) => {
|
||
const pre = document.createElement('pre');
|
||
pre.className = 'mermaid';
|
||
pre.textContent = code.textContent ?? '';
|
||
code.parentElement?.replaceWith(pre);
|
||
});
|
||
mermaid.initialize({ startOnLoad: false, theme: 'dark' });
|
||
await mermaid.run();
|
||
}
|
||
</script>
|
||
|
||
<style is:global>
|
||
/* Same font files the Ghost blog (Source theme) serves — one property,
|
||
one set of faces: JetBrains Mono everywhere, Libre Baskerville for
|
||
quoted matter. */
|
||
@font-face {
|
||
font-family: 'JetBrains Mono';
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
font-display: swap;
|
||
src: url('/fonts/jetbrains-mono-v13-latin-regular.woff2') format('woff2');
|
||
}
|
||
@font-face {
|
||
font-family: 'JetBrains Mono';
|
||
font-style: normal;
|
||
font-weight: 700;
|
||
font-display: swap;
|
||
src: url('/fonts/jetbrains-mono-v13-latin-700.woff2') format('woff2');
|
||
}
|
||
@font-face {
|
||
font-family: 'JetBrains Mono';
|
||
font-style: normal;
|
||
font-weight: 800;
|
||
font-display: swap;
|
||
src: url('/fonts/jetbrains-mono-v13-latin-800.woff2') format('woff2');
|
||
}
|
||
@font-face {
|
||
font-family: 'JetBrains Mono';
|
||
font-style: italic;
|
||
font-weight: 400;
|
||
font-display: swap;
|
||
src: url('/fonts/jetbrains-mono-v13-latin-italic.woff2') format('woff2');
|
||
}
|
||
@font-face {
|
||
font-family: 'JetBrains Mono';
|
||
font-style: italic;
|
||
font-weight: 700;
|
||
font-display: swap;
|
||
src: url('/fonts/jetbrains-mono-v13-latin-700italic.woff2')
|
||
format('woff2');
|
||
}
|
||
@font-face {
|
||
font-family: 'Libre Baskerville';
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
font-display: swap;
|
||
src: url('/fonts/libre-baskerville-v14-latin-regular.woff2')
|
||
format('woff2');
|
||
}
|
||
@font-face {
|
||
font-family: 'Libre Baskerville';
|
||
font-style: italic;
|
||
font-weight: 400;
|
||
font-display: swap;
|
||
src: url('/fonts/libre-baskerville-v14-latin-italic.woff2')
|
||
format('woff2');
|
||
}
|
||
@font-face {
|
||
font-family: 'Libre Baskerville';
|
||
font-style: normal;
|
||
font-weight: 700;
|
||
font-display: swap;
|
||
src: url('/fonts/libre-baskerville-v14-latin-700.woff2') format('woff2');
|
||
}
|
||
|
||
/* Tokens lifted from blog.c0smere.net (Ghost Source theme + code
|
||
injection): bg #0b0d12, white text, 64%-white secondary, accent
|
||
#218107. --link is the same green lifted for AA contrast on the
|
||
dark ground; --accent stays the blog's exact brand green for
|
||
borders and fills. Fixed dark — the blog has no light mode. */
|
||
:root {
|
||
--bg: #0b0d12;
|
||
--fg: #ffffff;
|
||
--muted: hsla(0, 0%, 100%, 0.64);
|
||
--faint: hsla(0, 0%, 100%, 0.16);
|
||
--hair: hsla(0, 0%, 100%, 0.08);
|
||
--accent: #218107;
|
||
--link: #2fa50f;
|
||
--link-bright: #46c421;
|
||
--mono:
|
||
'JetBrains Mono', ui-monospace, 'Cascadia Mono', Menlo, Consolas,
|
||
monospace;
|
||
--serif: 'Libre Baskerville', Georgia, serif;
|
||
color-scheme: dark;
|
||
}
|
||
* {
|
||
box-sizing: border-box;
|
||
}
|
||
body {
|
||
margin: 0;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
font-family: var(--mono);
|
||
line-height: 1.7;
|
||
}
|
||
::selection {
|
||
background: rgba(33, 129, 7, 0.45);
|
||
}
|
||
.layout {
|
||
margin: 0 auto;
|
||
padding: 1.5rem 1rem 3rem;
|
||
max-width: 44rem;
|
||
}
|
||
/* mobile: sidebar flows after the footer as a full site index */
|
||
.sidebar {
|
||
margin-top: 2.5rem;
|
||
padding-top: 1rem;
|
||
border-top: 1px solid var(--hair);
|
||
}
|
||
/* TOC only exists as a right column on wide screens */
|
||
.toc-col {
|
||
display: none;
|
||
}
|
||
@media (min-width: 72rem) {
|
||
.layout {
|
||
display: grid;
|
||
grid-template-columns: 17rem minmax(0, 44rem);
|
||
gap: 3rem;
|
||
max-width: 66rem;
|
||
}
|
||
.sidebar {
|
||
order: -1;
|
||
margin-top: 0;
|
||
padding-top: 0;
|
||
border-top: none;
|
||
position: sticky;
|
||
top: 0;
|
||
align-self: start;
|
||
max-height: 100vh;
|
||
overflow-y: auto;
|
||
padding: 1.5rem 0.5rem 1.5rem 0;
|
||
}
|
||
}
|
||
@media (min-width: 88rem) {
|
||
.layout {
|
||
grid-template-columns: 17rem minmax(0, 44rem) 13rem;
|
||
max-width: 80rem;
|
||
}
|
||
.toc-col {
|
||
display: block;
|
||
position: sticky;
|
||
top: 0;
|
||
align-self: start;
|
||
max-height: 100vh;
|
||
overflow-y: auto;
|
||
padding: 1.5rem 0 1.5rem 0.5rem;
|
||
}
|
||
}
|
||
|
||
/* ---- links ---- */
|
||
a {
|
||
color: var(--link);
|
||
text-decoration: underline;
|
||
text-decoration-color: color-mix(in srgb, var(--link) 40%, transparent);
|
||
text-underline-offset: 3px;
|
||
}
|
||
a:hover {
|
||
color: var(--link-bright);
|
||
text-decoration-color: currentColor;
|
||
}
|
||
|
||
/* ---- site header (mirrors the blog's gh-head) ---- */
|
||
.site-head {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: baseline;
|
||
justify-content: space-between;
|
||
gap: 0.5rem 1.5rem;
|
||
margin-bottom: 1.25rem;
|
||
}
|
||
.site-logo {
|
||
font-size: 1.05rem;
|
||
font-weight: 800;
|
||
letter-spacing: -0.02em;
|
||
color: var(--fg);
|
||
text-decoration: none;
|
||
}
|
||
.site-logo:hover {
|
||
color: var(--link-bright);
|
||
}
|
||
.site-nav {
|
||
display: flex;
|
||
gap: 1.2rem;
|
||
font-size: 0.85rem;
|
||
}
|
||
.site-nav a {
|
||
color: var(--muted);
|
||
text-decoration: none;
|
||
}
|
||
.site-nav a:hover {
|
||
color: var(--fg);
|
||
}
|
||
.header-quote {
|
||
margin-bottom: 1.75rem;
|
||
padding-bottom: 1rem;
|
||
border-bottom: 1px solid var(--hair);
|
||
font-family: var(--serif);
|
||
font-style: italic;
|
||
font-size: 0.9rem;
|
||
line-height: 1.7;
|
||
color: var(--muted);
|
||
}
|
||
.header-quote .hq-attr {
|
||
font-family: var(--mono);
|
||
font-style: normal;
|
||
white-space: nowrap;
|
||
margin-left: 0.4rem;
|
||
font-size: 0.75rem;
|
||
}
|
||
.live-stats {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 0.55rem;
|
||
margin: -0.75rem 0 1.75rem;
|
||
padding-bottom: 0.85rem;
|
||
border-bottom: 1px solid var(--hair);
|
||
font-size: 0.78rem;
|
||
color: var(--muted);
|
||
}
|
||
.live-stats .v {
|
||
color: var(--fg);
|
||
}
|
||
.live-dot {
|
||
flex: none;
|
||
width: 7px;
|
||
height: 7px;
|
||
border-radius: 50%;
|
||
background: var(--link);
|
||
align-self: center;
|
||
animation: live-pulse 2.4s ease-in-out infinite;
|
||
}
|
||
@keyframes live-pulse {
|
||
50% {
|
||
opacity: 0.35;
|
||
}
|
||
}
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.live-dot {
|
||
animation: none;
|
||
}
|
||
}
|
||
|
||
/* ---- headings ---- */
|
||
h1, h2, h3, h4, h5, h6 {
|
||
line-height: 1.3;
|
||
letter-spacing: -0.02em;
|
||
scroll-margin-top: 1rem;
|
||
}
|
||
h1 {
|
||
font-size: 1.7rem;
|
||
font-weight: 800;
|
||
}
|
||
h2 {
|
||
font-size: 1.3rem;
|
||
font-weight: 700;
|
||
margin-top: 2.4em;
|
||
}
|
||
h3 {
|
||
font-size: 1.1rem;
|
||
font-weight: 700;
|
||
}
|
||
h4, h5, h6 {
|
||
font-size: 1rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
/* ---- sidebar ---- */
|
||
.sidebar-title {
|
||
margin: 0 0 0.5rem;
|
||
font-weight: 800;
|
||
letter-spacing: -0.01em;
|
||
}
|
||
.sidebar-title a {
|
||
color: var(--fg);
|
||
text-decoration: none;
|
||
}
|
||
.sidebar ul {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding-left: 0.85rem;
|
||
font-size: 0.8rem;
|
||
}
|
||
.sidebar > ul,
|
||
.sidebar-title + ul {
|
||
padding-left: 0;
|
||
}
|
||
.sidebar li {
|
||
margin: 0.15rem 0;
|
||
}
|
||
.sidebar a {
|
||
color: var(--muted);
|
||
text-decoration: none;
|
||
}
|
||
.sidebar a:hover {
|
||
color: var(--fg);
|
||
text-decoration: underline;
|
||
}
|
||
.sidebar a[aria-current='page'] {
|
||
color: var(--link);
|
||
font-weight: 700;
|
||
}
|
||
.sidebar summary {
|
||
cursor: pointer;
|
||
list-style: none;
|
||
}
|
||
.sidebar summary::before {
|
||
content: '›';
|
||
display: inline-block;
|
||
width: 0.9em;
|
||
color: var(--muted);
|
||
font-size: 0.8em;
|
||
transition: transform 0.15s;
|
||
}
|
||
.sidebar details[open] > summary::before {
|
||
transform: rotate(90deg);
|
||
}
|
||
.sidebar summary a {
|
||
font-weight: 700;
|
||
color: var(--fg);
|
||
}
|
||
|
||
/* ---- table of contents ---- */
|
||
.toc-title {
|
||
margin: 0 0 0.5rem;
|
||
font-weight: 700;
|
||
font-size: 0.8rem;
|
||
}
|
||
.toc ul {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
font-size: 0.75rem;
|
||
border-left: 1px solid var(--hair);
|
||
}
|
||
.toc li {
|
||
margin: 0.2rem 0;
|
||
}
|
||
.toc li a {
|
||
display: block;
|
||
padding-left: 0.75rem;
|
||
color: var(--muted);
|
||
text-decoration: none;
|
||
border-left: 2px solid transparent;
|
||
margin-left: -1px;
|
||
}
|
||
.toc li.toc-d1 a {
|
||
padding-left: 1.5rem;
|
||
}
|
||
.toc li.toc-d2 a {
|
||
padding-left: 2.25rem;
|
||
}
|
||
.toc li a:hover {
|
||
color: var(--fg);
|
||
}
|
||
.toc li a.toc-active {
|
||
color: var(--link);
|
||
border-left-color: var(--link);
|
||
}
|
||
|
||
/* ---- content ---- */
|
||
img {
|
||
max-width: 100%;
|
||
height: auto;
|
||
border-radius: 4px;
|
||
}
|
||
pre {
|
||
padding: 1rem 1.1rem;
|
||
border-radius: 6px;
|
||
border: 1px solid var(--hair);
|
||
overflow-x: auto;
|
||
font-size: 0.82rem;
|
||
line-height: 1.55;
|
||
}
|
||
:not(pre) > code {
|
||
background: var(--hair);
|
||
padding: 0.1em 0.35em;
|
||
border-radius: 4px;
|
||
font-size: 0.92em;
|
||
}
|
||
/* tables: transparent with hairline row borders, matching the blog's
|
||
table code injection */
|
||
table {
|
||
border-collapse: collapse;
|
||
display: block;
|
||
overflow-x: auto;
|
||
max-width: 100%;
|
||
font-size: 0.9rem;
|
||
}
|
||
th, td {
|
||
border: 0;
|
||
border-bottom: 1px solid var(--faint);
|
||
padding: 0.5em 0.85em;
|
||
text-align: left;
|
||
}
|
||
hr {
|
||
border: 0;
|
||
border-top: 1px solid var(--faint);
|
||
margin: 2.5rem 0;
|
||
}
|
||
.katex-display {
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
}
|
||
/* quoted matter goes serif-italic, like the blog's serif accents */
|
||
blockquote {
|
||
margin: 1.5rem 0;
|
||
padding: 0.1rem 0 0.1rem 1.25rem;
|
||
border-left: 3px solid var(--accent);
|
||
font-family: var(--serif);
|
||
font-style: italic;
|
||
font-size: 0.95rem;
|
||
line-height: 1.8;
|
||
color: var(--muted);
|
||
}
|
||
/* Obsidian callouts are blockquotes too — keep them mono/upright */
|
||
.callout {
|
||
font-family: var(--mono);
|
||
font-style: normal;
|
||
font-size: 1rem;
|
||
line-height: 1.7;
|
||
color: var(--fg);
|
||
border-left: 4px solid var(--accent);
|
||
border-radius: 4px;
|
||
background: rgba(33, 129, 7, 0.1);
|
||
padding: 0.1rem 1rem;
|
||
}
|
||
.callout-title {
|
||
color: var(--link);
|
||
}
|
||
.callout-warning, .callout-attention, .callout-important {
|
||
border-left-color: #c9803a;
|
||
background: rgba(201, 128, 58, 0.1);
|
||
}
|
||
.callout-warning .callout-title,
|
||
.callout-attention .callout-title,
|
||
.callout-important .callout-title {
|
||
color: #d89a55;
|
||
}
|
||
|
||
/* ---- footer ---- */
|
||
footer {
|
||
margin-top: 4rem;
|
||
padding-top: 1.25rem;
|
||
border-top: 1px solid var(--hair);
|
||
font-size: 0.8rem;
|
||
color: var(--muted);
|
||
}
|
||
footer a {
|
||
color: var(--muted);
|
||
}
|
||
footer a:hover {
|
||
color: var(--fg);
|
||
}
|
||
.footer-quotes {
|
||
font-family: var(--serif);
|
||
font-style: italic;
|
||
font-size: 0.85rem;
|
||
margin-bottom: 0.75rem;
|
||
}
|
||
.footer-quotes p {
|
||
margin: 0.25rem 0;
|
||
}
|
||
.odo-display {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 2px;
|
||
font-family: var(--mono);
|
||
font-size: 0.95rem;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.odo-display .odo-digit {
|
||
display: inline-block;
|
||
min-width: 1.3ch;
|
||
padding: 0.12em 0.08em;
|
||
text-align: center;
|
||
background: #11150e;
|
||
color: #56dd33;
|
||
border-radius: 3px;
|
||
border: 1px solid var(--hair);
|
||
border-bottom: 2px solid #000;
|
||
}
|
||
.odo-display .odo-sep {
|
||
width: 0.3ch;
|
||
}
|
||
.odo-caption {
|
||
margin-top: 0.35rem;
|
||
font-size: 0.72rem;
|
||
}
|
||
.build-stamp {
|
||
margin-top: 1.25rem;
|
||
font-size: 0.72rem;
|
||
color: hsla(0, 0%, 100%, 0.4);
|
||
}
|
||
.build-stamp a {
|
||
color: inherit;
|
||
}
|
||
.build-stamp a:hover {
|
||
color: var(--link-bright);
|
||
}
|
||
|
||
/* ---- homepage: typographic feed, like the blog's index ---- */
|
||
.home main h1 {
|
||
font-size: 1.35rem;
|
||
font-weight: 800;
|
||
margin: 2.4rem 0 0.75rem;
|
||
}
|
||
/* the vault-authored links row at the very top: render as a small
|
||
uppercase link bar, not a display heading */
|
||
.home main > h1:first-child {
|
||
margin: 0 0 0.5rem;
|
||
font-size: 0.8rem;
|
||
font-weight: 400;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
color: var(--muted);
|
||
}
|
||
.home main > h1:first-child a {
|
||
color: var(--muted);
|
||
text-decoration: none;
|
||
}
|
||
.home main > h1:first-child a:hover {
|
||
color: var(--link-bright);
|
||
text-decoration: underline;
|
||
}
|
||
.home main h2 {
|
||
font-size: 0.85rem;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.07em;
|
||
color: var(--muted);
|
||
margin: 1.9rem 0 0.4rem;
|
||
}
|
||
.home main hr {
|
||
margin: 2rem 0;
|
||
}
|
||
.home main > ul {
|
||
list-style: none;
|
||
margin: 0.5rem 0 1.5rem;
|
||
padding: 0;
|
||
}
|
||
.home main > ul > li {
|
||
padding: 0.6rem 0;
|
||
border-bottom: 1px solid var(--hair);
|
||
color: var(--muted);
|
||
font-size: 0.9rem;
|
||
line-height: 1.6;
|
||
}
|
||
.home main > ul > li:last-child {
|
||
border-bottom: 0;
|
||
}
|
||
.home main > ul > li > a {
|
||
font-weight: 700;
|
||
}
|
||
.home main ul ul {
|
||
list-style: none;
|
||
margin: 0.4rem 0 0;
|
||
padding-left: 1.25rem;
|
||
font-size: 0.85rem;
|
||
}
|
||
.home main ul ul li {
|
||
padding: 0.15rem 0;
|
||
border: 0;
|
||
}
|
||
</style>
|
||
</body>
|
||
</html>
|