Files
com.gregkilmer.www/services/site/src/pages/now.astro
T
Claude 7aa2910b12 Add light mode that follows the device setting
Dark stays the default. A device set to light gets the picks from the
light motif board: the Dawn chart sky (dawn wash over faint star chart
lines, atlas marks in the hero), deep gold line seals, Midnight, violet and
gold dust, the deep gold to teal filament and the morning sky placeholder.
Large standalone seals (404, Inquiries, Inquiry sent, the blog's lead) are
gold foil in both modes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
2026-10-18 18:54:56 +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: 0.14;
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>