mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-08 04:08:24 +00:00
Phase 5: every page of the site, the resume PDF and RSS
Landing, About, Now, Resume (with a generated PDF), Work and case studies, Inquiries and the sent page, Library and the Shelf, Workshop and craft pages, Tabletop, Blog with topics and posts, RSS, the Store, product lines, the order form and the 404, all rendered from the content service's notes. Schema: experience previousRoles, skills stack core items, page stats, make tags and deck favorite. Icon names from notes map to brand icons in one place, and HTML comments are dropped when Markdown renders. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
---
|
||||
/**
|
||||
* 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 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">
|
||||
<img 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">
|
||||
<img 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>
|
||||
Reference in New Issue
Block a user