Build stamp, live listening strip, OG cards, closer shiki ground
- 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>
This commit is contained in:
+115
-2
@@ -2,6 +2,7 @@
|
||||
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';
|
||||
|
||||
@@ -9,11 +10,18 @@ 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 = [] } = Astro.props;
|
||||
const { title, description, headings = [], ogSlug = 'index' } = Astro.props;
|
||||
const isHome = Astro.url.pathname === '/';
|
||||
const navTree = buildNavTree(await getCollection('garden'));
|
||||
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>
|
||||
@@ -24,6 +32,14 @@ const showToc = headings.filter((h) => h.depth <= 3).length >= 2;
|
||||
<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"
|
||||
@@ -61,6 +77,14 @@ const showToc = headings.filter((h) => h.depth <= 3).length >= 2;
|
||||
<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 />
|
||||
@@ -85,6 +109,19 @@ const showToc = headings.filter((h) => h.depth <= 3).length >= 2;
|
||||
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">
|
||||
@@ -119,6 +156,39 @@ const showToc = headings.filter((h) => h.depth <= 3).length >= 2;
|
||||
}
|
||||
</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
|
||||
@@ -390,6 +460,38 @@ const showToc = headings.filter((h) => h.depth <= 3).length >= 2;
|
||||
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 {
|
||||
@@ -637,6 +739,17 @@ const showToc = headings.filter((h) => h.depth <= 3).length >= 2;
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user