mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-07 19:58:24 +00:00
813c403fcc
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
264 lines
7.3 KiB
Plaintext
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>
|