mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-07 19:58:24 +00:00
Add an All makes page to the Workshop
Every make newest first with its craft, filterable by craft, linked as See all makes from Recently finished. Makes without a craft now link here instead of the Workshop page. 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:
@@ -22,6 +22,7 @@ export const sitePages: SitePage[] = [
|
||||
{ page: "/work", name: "Work", feeds: ["post", "experience"] },
|
||||
{ page: "/resume", name: "Resume", feeds: ["experience", "skills", "education", "site-settings"] },
|
||||
{ page: "/workshop", name: "Workshop", feeds: ["craft", "make", "station", "tool"] },
|
||||
{ page: "/workshop/makes", name: "All makes", feeds: ["make"] },
|
||||
{ page: "/library", name: "Library", feeds: ["library-item"] },
|
||||
{ page: "/library/shelf", name: "Shelf", feeds: ["board-game"] },
|
||||
{ page: "/tabletop", name: "Tabletop", feeds: ["tabletop-system", "campaign", "card-game", "deck"] },
|
||||
|
||||
@@ -87,7 +87,7 @@ export class SiteIndex {
|
||||
return `/workshop/${n.slug}`;
|
||||
case "make": {
|
||||
const craft = this.byName(d.craft as string);
|
||||
return craft ? `/workshop/${craft.slug}#${n.slug}` : `/workshop#${n.slug}`;
|
||||
return craft ? `/workshop/${craft.slug}#${n.slug}` : `/workshop/makes#${n.slug}`;
|
||||
}
|
||||
case "station":
|
||||
case "tool":
|
||||
|
||||
@@ -146,6 +146,9 @@ const nowItems: NowItem[] = statusesFor(s, "/workshop").map((st) => {
|
||||
{
|
||||
recent.length > 0 && (
|
||||
<WorldSection id="ws-recent" title="Recently finished">
|
||||
<a slot="action" href="/workshop/makes" class="gk-text all">
|
||||
See all {allMakes.length} makes
|
||||
</a>
|
||||
<ul class="recent">
|
||||
{recent.map((m) => {
|
||||
const found = s.byName(m.data.craft);
|
||||
@@ -406,6 +409,11 @@ const nowItems: NowItem[] = statusesFor(s, "/workshop").map((st) => {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.all {
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Sword rack */
|
||||
.rack {
|
||||
margin-top: 40px;
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
---
|
||||
import Chip from "@gk/ui/components/Chip.astro";
|
||||
import FilterGroup from "@gk/ui/components/FilterGroup.astro";
|
||||
import PageShell from "@gk/ui/components/PageShell.astro";
|
||||
import StarChartArt from "@gk/ui/components/StarChartArt.astro";
|
||||
import { pageSeals } from "@gk/ui/components/seals.ts";
|
||||
import CtaBand from "../../components/world/CtaBand.astro";
|
||||
import WorldSection from "../../components/world/WorldSection.astro";
|
||||
import Button from "@gk/ui/components/Button.astro";
|
||||
import type { Note } from "../../lib/content.ts";
|
||||
import { plural, site } from "../../lib/page.ts";
|
||||
import { formatDate } from "../../lib/site.ts";
|
||||
import { byDateDesc, byOrder, craftSeal, makePhotos } from "../../lib/world.ts";
|
||||
|
||||
const s = await site();
|
||||
const text = s.pageText("/workshop/makes");
|
||||
const intro = s.render(text?.body);
|
||||
|
||||
const craftOf = (m: Note<"make">) => {
|
||||
const found = s.byName(m.data.craft);
|
||||
return found?.type === "craft" ? (found as Note<"craft">) : undefined;
|
||||
};
|
||||
// Newest first; makes without a finish date follow, still in a stable order.
|
||||
const makes = byDateDesc(s.all("make")).sort((a, b) => Number(!!b.data.date) - Number(!!a.data.date));
|
||||
const crafts = byOrder(s.all("craft")).filter((c) => makes.some((m) => craftOf(m)?.path === c.path));
|
||||
const loose = makes.some((m) => !craftOf(m));
|
||||
const pills =
|
||||
crafts.length + (loose ? 1 : 0) >= 2
|
||||
? [{ label: "All", value: "", active: true }, ...crafts.map((c) => ({ label: c.title, value: c.slug })), ...(loose ? [{ label: "Other", value: "-" }] : [])]
|
||||
: [];
|
||||
---
|
||||
<PageShell title="All makes" family="workshop" current="workshop" description={text?.data.lede ?? "Everything I have made in the Workshop, newest first."}>
|
||||
<Fragment slot="hero">
|
||||
<div class="crumb"><a href="/workshop">Workshop</a> / Makes</div>
|
||||
<h1 class="title">{text?.title ?? "All makes"}</h1>
|
||||
<p class="lede">{text?.data.lede ?? "Everything I have made in the Workshop, newest first."}</p>
|
||||
</Fragment>
|
||||
|
||||
{intro && <div class="gk-prose intro" set:html={intro} />}
|
||||
|
||||
<WorldSection id="mk-all" title="Makes" intro={plural(makes.length, "make")}>
|
||||
{pills.length > 0 && <div slot="action" class="crafts" data-crafts hidden><FilterGroup label="Craft" items={pills} /></div>}
|
||||
{
|
||||
makes.length > 0 ? (
|
||||
<ul class="makes">
|
||||
{makes.map((m) => {
|
||||
const craft = craftOf(m);
|
||||
const photos = makePhotos(m);
|
||||
const log = s.backlinks(m, { type: "post" })[0];
|
||||
return (
|
||||
<li id={m.slug} class="make" data-c={craft?.slug ?? "-"}>
|
||||
<StarChartArt seal={(craft && craftSeal(craft)) ?? pageSeals.workshop} ratio="auto" src={photos[0]} alt={m.data.blurb ?? m.title} class="m-art" />
|
||||
<span class="m-txt">
|
||||
{craft && (
|
||||
<a href={s.href(craft) ?? undefined} class="m-craft">
|
||||
{craft.title}
|
||||
</a>
|
||||
)}
|
||||
<h3 class="m-name">{craft ? <a href={s.href(m) ?? undefined}>{m.title}</a> : m.title}</h3>
|
||||
{m.data.blurb && <span class="m-blurb">{m.data.blurb}</span>}
|
||||
{(m.data.date || log) && (
|
||||
<span class="m-meta">
|
||||
{m.data.date && <time datetime={m.data.date}>{formatDate(m.data.date)}</time>}
|
||||
{log && <Chip href={s.href(log) ?? undefined}>Build log</Chip>}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
) : (
|
||||
<p class="empty">Nothing finished yet.</p>
|
||||
)
|
||||
}
|
||||
</WorldSection>
|
||||
|
||||
<CtaBand title="Want one of these?" note="Some of it is in the Store. Anything else can start as an inquiry.">
|
||||
<Button href="/store">Visit the Store</Button>
|
||||
<Button href="/inquiries" variant="secondary">Start an inquiry</Button>
|
||||
</CtaBand>
|
||||
</PageShell>
|
||||
|
||||
<script>
|
||||
// The craft pills filter the grid; without a script every make shows.
|
||||
const box = document.querySelector<HTMLElement>("[data-crafts]");
|
||||
if (box) {
|
||||
const pills = [...box.querySelectorAll<HTMLButtonElement>("button[data-value]")];
|
||||
const makes = [...document.querySelectorAll<HTMLElement>(".makes > li[data-c]")];
|
||||
box.hidden = false;
|
||||
for (const b of pills) {
|
||||
b.addEventListener("click", () => {
|
||||
const v = b.dataset.value ?? "";
|
||||
pills.forEach((p) => p.setAttribute("aria-pressed", String(p === b)));
|
||||
for (const m of makes) m.hidden = !!v && m.dataset.c !== v;
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.crumb {
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.3em;
|
||||
font-weight: 600;
|
||||
color: var(--gk-gold-bright);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.crumb a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.crumb a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: clamp(34px, 6vw, 48px);
|
||||
letter-spacing: 0.08em;
|
||||
line-height: 1.05;
|
||||
}
|
||||
.lede {
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
color: var(--gk-soft);
|
||||
max-width: 62ch;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.intro {
|
||||
max-width: 70ch;
|
||||
padding-top: 28px;
|
||||
color: var(--gk-soft);
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.makes {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr));
|
||||
gap: 16px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.make {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--gk-midnight);
|
||||
border: 1px solid var(--gk-lines);
|
||||
border-radius: var(--gk-radius);
|
||||
overflow: hidden;
|
||||
scroll-margin-top: 24px;
|
||||
}
|
||||
.make:target {
|
||||
border-color: var(--gk-gold);
|
||||
}
|
||||
.make :global(.m-art) {
|
||||
height: 200px;
|
||||
border-radius: 0;
|
||||
}
|
||||
.m-txt {
|
||||
padding: 14px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.m-craft {
|
||||
align-self: flex-start;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.3em;
|
||||
font-weight: 600;
|
||||
color: var(--gk-muted);
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
}
|
||||
.m-craft:hover {
|
||||
color: var(--gk-gold-bright);
|
||||
}
|
||||
.m-name {
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
}
|
||||
.m-name a {
|
||||
color: var(--gk-text);
|
||||
text-decoration: none;
|
||||
}
|
||||
.m-name a:hover {
|
||||
color: var(--gk-gold-bright);
|
||||
}
|
||||
.m-blurb {
|
||||
font-size: 13px;
|
||||
color: var(--gk-soft);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.m-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: var(--gk-muted);
|
||||
}
|
||||
.empty {
|
||||
margin: 0;
|
||||
color: var(--gk-muted);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user