Files
com.gregkilmer.www/services/site/src/pages/now.astro
T
Claude 813c403fcc Make the background seals a little stronger in light mode
A --gk-ghost token scales the faint edge seals, panel seals and banners:
1 in dark, 1.6 in light, so their lines read on Starlight.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
2026-10-18 20:34:46 +00:00

264 lines
7.3 KiB
Plaintext

---
/**
* Now: every status in the log, grouped (making, at the table, playing,
* reading, watching, up next), each an anchor at its slug, with the
* Everything / Now / Up next filter.
*/
import FilterGroup from "@gk/ui/components/FilterGroup.astro";
import Motif from "@gk/ui/components/Motif.astro";
import PageShell from "@gk/ui/components/PageShell.astro";
import SectionHeading from "@gk/ui/components/SectionHeading.astro";
import Star from "@gk/ui/components/Star.astro";
import TickScale from "@gk/ui/components/TickScale.astro";
import { seals } from "@gk/ui/components/seals.ts";
import PageHero from "../components/PageHero.astro";
import StatusRow from "../components/home/StatusRow.astro";
import { groupStatuses, lastUpdated, viewStatus } from "../lib/home.ts";
import { site } from "../lib/page.ts";
import { formatDate } from "../lib/site.ts";
const s = await site();
const text = s.pageText("/now");
const groups = groupStatuses(s);
const updated = lastUpdated(s);
const lede = [text?.data.lede ?? "What I am making, reading, playing and watching right now.", updated && `Last updated ${formatDate(updated)}.`]
.filter(Boolean)
.join(" ");
const intro = s.render(text?.body);
const hasNext = groups.some((g) => g.key === "next");
const wrapped = s
.all("status")
.filter((x) => /finish|wrapped|completed|done|caught up/i.test(x.data.kind))
.sort((a, b) => b.data.date.localeCompare(a.data.date))
.slice(0, 5)
.map((x) => viewStatus(s, x));
---
<PageShell title="Now" family="now" current="now" description="What I am making, reading, playing and watching right now.">
<PageHero slot="hero" eyebrow="My world" title="Now" lede={lede} />
<div class="filter-bar" data-now-filter hidden>
<FilterGroup
label="Show"
items={[
{ label: "Everything", value: "all", active: true },
{ label: "Now", value: "now" },
{ label: "Up next", value: "next" },
]}
/>
<span class="order">Newest first · pinned on top</span>
</div>
<div class="layout">
<div class="groups">
{intro && <div class="intro" set:html={intro} />}
{
groups.length ? (
groups.map((g) => (
<section aria-labelledby={`n-${g.key}`} class="group" data-next={g.key === "next" ? "true" : "false"}>
<SectionHeading id={`n-${g.key}`}>
<Star size={12} />
{g.title}
</SectionHeading>
<ul class="rows">
{g.items.map((v) => (
<StatusRow view={v} site={s} />
))}
</ul>
</section>
))
) : (
<p class="empty">Nothing on the status log yet.</p>
)
}
{!hasNext && <p class="empty" data-next-empty hidden>Nothing queued up next right now.</p>}
</div>
<aside aria-label="About this page" class="aside">
<div class="panel">
<Motif src={seals.zodiac} alt="" aria-hidden="true" class="panel-seal" />
<span class="panel-kicker"><Star size={11} /><span>How this works</span></span>
<TickScale width={200} height={10} count={30} class="panel-ticks" />
<p class="panel-text">
Each line is a status note in my vault. When I or one of my agents update it, this page, the landing page and the
matching page all change together.
</p>
</div>
{
wrapped.length > 0 && (
<div class="panel">
<Motif src={seals["seven-metals"]} alt="" aria-hidden="true" class="panel-seal" />
<span class="panel-kicker"><Star size={11} /><span>Recently wrapped up</span></span>
<TickScale width={200} height={10} count={30} class="panel-ticks" />
<ul class="wrapped">
{wrapped.map((v) => (
<li>
{v.href ? <a href={v.href}>{v.title}</a> : <span>{v.title}</span>}
<time datetime={v.date}>{formatDate(v.date)}</time>
</li>
))}
</ul>
</div>
)
}
</aside>
</div>
</PageShell>
<script>
const bar = document.querySelector<HTMLElement>("[data-now-filter]");
if (bar) {
bar.hidden = false;
const buttons = [...bar.querySelectorAll<HTMLButtonElement>("button[data-value]")];
const groups = [...document.querySelectorAll<HTMLElement>(".group[data-next]")];
const apply = (value: string) => {
for (const b of buttons) b.setAttribute("aria-pressed", String(b.dataset.value === value));
for (const g of groups) {
const next = g.dataset.next === "true";
g.hidden = value === "now" ? next : value === "next" ? !next : false;
}
const empty = document.querySelector<HTMLElement>("[data-next-empty]");
if (empty) empty.hidden = value !== "next";
};
for (const b of buttons) b.addEventListener("click", () => apply(b.dataset.value ?? "all"));
}
</script>
<style>
.filter-bar {
padding-top: 28px;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.filter-bar[hidden] {
display: none;
}
.order {
font-size: 13px;
color: var(--gk-muted);
}
.layout {
padding-top: 20px;
padding-bottom: 48px;
display: flex;
flex-wrap: wrap;
gap: 40px;
align-items: flex-start;
}
.groups {
flex: 3 1 640px;
min-width: 0;
display: flex;
flex-direction: column;
gap: 32px;
}
.intro {
color: var(--gk-soft);
max-width: 68ch;
}
.group {
display: flex;
flex-direction: column;
gap: 10px;
}
.group[hidden] {
display: none;
}
.group :global(h2) {
display: flex;
align-items: center;
gap: 10px;
font-size: 22px;
}
.rows {
margin: 0;
padding: 0;
list-style: none;
border-bottom: 1px solid var(--gk-lines);
}
.empty[hidden] {
display: none;
}
.empty {
color: var(--gk-muted);
}
.aside {
flex: 1 1 260px;
max-width: 320px;
min-width: 0;
display: flex;
flex-direction: column;
gap: 16px;
}
.panel {
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
gap: 10px;
padding: 18px 20px;
background: var(--gk-midnight);
border: 1px solid var(--gk-lines);
border-radius: var(--gk-radius);
}
.panel-seal {
position: absolute;
right: -26px;
top: -26px;
width: 104px;
height: 104px;
opacity: calc(0.14 * var(--gk-ghost));
pointer-events: none;
}
.panel-kicker {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
letter-spacing: 0.3em;
font-weight: 600;
color: var(--gk-gold-bright);
text-transform: uppercase;
}
.panel :global(.panel-ticks) {
margin: -4px 0 2px;
}
.panel-text {
margin: 0;
font-size: 14px;
color: var(--gk-soft);
line-height: 1.6;
}
.wrapped {
margin: 0;
padding: 0;
list-style: none;
}
.wrapped li {
display: flex;
justify-content: space-between;
gap: 8px;
padding: 6px 0;
font-size: 14px;
border-top: 1px solid var(--gk-lines);
}
.wrapped a {
color: var(--gk-text);
text-decoration: none;
}
.wrapped a:hover {
color: var(--gk-teal-bright);
}
.wrapped time {
color: var(--gk-muted);
flex: none;
}
@media (max-width: 900px) {
.aside {
max-width: none;
}
}
</style>