From 151bed230a020486c367588b6c73bb970e981a96 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 03:33:15 +0000 Subject: [PATCH 1/3] Match the site to the approved designs Denser star field, the page nebula, a favicon package, the filament banner without its box, the About portrait, Kashi and tea photos and Scorpius, the Store's own header, a header logo glow, a resume PDF that fills its page, one divider on the Library and Shelf, star-chart art for coverless favorites, Shelf Players, Play time and blog filters with a Show all fold, make kind pills on craft pages, and full-width closing bands. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx --- docs/site.md | 14 +- packages/schema/src/types.ts | 6 + packages/ui/src/assets/icons/download.svg | 1 + .../ui/src/assets/motifs/filament-banner.svg | 2 +- packages/ui/src/assets/motifs/scorpius.svg | 1 + .../src/assets/motifs/seven-metals-dipper.svg | 2 +- .../ui/src/components/ConstellationDust.astro | 27 ++- packages/ui/src/components/PageShell.astro | 17 +- packages/ui/src/components/SiteHeader.astro | 118 ++++++++---- packages/ui/src/components/nav.ts | 6 + packages/ui/src/styles/base.css | 5 + services/site/public/apple-touch-icon.png | Bin 0 -> 25904 bytes services/site/public/favicon-16.png | Bin 0 -> 6546 bytes services/site/public/favicon-32.png | Bin 0 -> 7762 bytes services/site/public/favicon-48.png | Bin 0 -> 9191 bytes services/site/public/favicon.ico | Bin 0 -> 6022 bytes services/site/public/favicon.svg | 1 + services/site/public/icon-192.png | Bin 0 -> 27829 bytes services/site/public/icon-512.png | Bin 0 -> 130477 bytes services/site/public/icon-maskable-192.png | Bin 0 -> 25236 bytes services/site/public/icon-maskable-512.png | Bin 0 -> 122437 bytes services/site/public/site.webmanifest | 33 ++++ .../site/src/components/home/Portrait.astro | 24 ++- .../components/library/Constellation.astro | 30 ++- .../src/components/library/GameCard.astro | 7 +- .../site/src/components/world/CtaBand.astro | 32 ++-- services/site/src/lib/library.ts | 59 +++++- services/site/src/lib/resume-pdf.ts | 82 +++++--- services/site/src/lib/store.ts | 9 + services/site/src/pages/about.astro | 53 +++++- services/site/src/pages/index.astro | 3 +- services/site/src/pages/library/index.astro | 12 +- services/site/src/pages/library/shelf.astro | 177 +++++++++++++----- services/site/src/pages/resume.astro | 2 +- services/site/src/pages/store/[slug].astro | 4 +- services/site/src/pages/store/index.astro | 4 +- services/site/src/pages/store/order.astro | 4 +- .../site/src/pages/store/order/sent.astro | 9 +- services/site/src/pages/tabletop.astro | 2 +- services/site/src/pages/workshop/[slug].astro | 31 ++- services/site/test/resume-pdf.test.ts | 38 ++++ tools/migrate/src/build.ts | 14 +- tools/migrate/src/seed.ts | 6 + .../Site/_templates/Page text.md | 1 + .../Site/_templates/Product line.md | 1 + 45 files changed, 671 insertions(+), 166 deletions(-) create mode 100644 packages/ui/src/assets/icons/download.svg create mode 100644 packages/ui/src/assets/motifs/scorpius.svg create mode 100644 services/site/public/apple-touch-icon.png create mode 100644 services/site/public/favicon-16.png create mode 100644 services/site/public/favicon-32.png create mode 100644 services/site/public/favicon-48.png create mode 100644 services/site/public/favicon.ico create mode 100644 services/site/public/favicon.svg create mode 100644 services/site/public/icon-192.png create mode 100644 services/site/public/icon-512.png create mode 100644 services/site/public/icon-maskable-192.png create mode 100644 services/site/public/icon-maskable-512.png create mode 100644 services/site/public/site.webmanifest create mode 100644 services/site/test/resume-pdf.test.ts diff --git a/docs/site.md b/docs/site.md index a59cf96..f2b6ba2 100644 --- a/docs/site.md +++ b/docs/site.md @@ -92,7 +92,8 @@ personal Greg Kilmer letterhead, the version without Patiun LLC branding letterheads; re-copy it when that letterhead changes) with pdf-lib, which keeps both as vectors. It reads top to bottom so applicant tracking systems take it in order, aims for one page (roles marked `earlier` print as a short list; skills share a row with education and -projects), uses the brand TTFs in +projects), and when a one-page resume ends well above the footer it is laid +out again with wider gaps (up to twice as wide) so the page reads as full. It uses the brand TTFs in `src/assets/pdf-fonts`, and is rebuilt only when the notes behind it change. The highlights row and Selected projects come from the `/resume` Page text note's `stats` and `projects`. Greg's Resume Interview answers live in @@ -111,6 +112,17 @@ The highlights row and Selected projects come from the `/resume` Page text note' | Blog, topics, posts, RSS | `pages/blog/`, `rss.xml.ts` | `lib/blog.ts`, `components/blog` | | Store, lines, order | `pages/store/` | `lib/store.ts`, `components/store` | +## Filters on world pages + +- **Shelf**: How it plays, Players, Play time, Owned, Want to play, Favorites + and Has a blog post, plus four sorts. Players and Play time read the + BoardGameGeek sync and only appear once some game has those values. With no + filter narrowing the list, the first 12 games show and "Show all" opens the + rest. +- **Craft pages**: when a craft's makes carry two or more `tags` (Dice, Rings, + and so on; `sword rack` does not count), pills above the grid filter them. + Makes without a tag fall under Other. + ## Forms The Inquiries form posts to `POST /api/inquiries` and the order form to diff --git a/packages/schema/src/types.ts b/packages/schema/src/types.ts index 2d47c79..bb56d4b 100644 --- a/packages/schema/src/types.ts +++ b/packages/schema/src/types.ts @@ -399,6 +399,7 @@ export const productLine = def({ channel: z.enum(["here", "Etsy", "Patiun.com", "coming soon"]).meta({ title: "Where to buy" }), channelUrl: z.url().meta({ title: "Shop link", description: "For Etsy or Patiun.com lines." }).optional(), summary: line({ title: "Summary" }), + menuName: line({ title: "Menu name", description: "A short name for the Store menu, like Proxy cards." }).optional(), icon: line({ title: "Icon" }).optional(), order: z.number().int().meta({ title: "Order" }).optional(), slots: z @@ -501,6 +502,11 @@ export const page = def({ schema: note("page", { page: line({ title: "Page", description: "The page address this text belongs to, like /about." }), lede: line({ title: "Lede", description: "The one line under the page title." }).optional(), + portrait: image({ title: "Portrait", description: "Greg's photo on the landing and About pages." }).optional(), + photos: z + .array(z.object({ file: image({ title: "Image" }), alt: line({ title: "Alt text" }), caption: line({ title: "Caption" }).optional() })) + .meta({ title: "Photos", description: "A row of captioned photos, like Kashi and the tea on About." }) + .optional(), quickFacts: z .array(z.object({ icon: line({ title: "Icon" }), text: line({ title: "Text" }) })) .meta({ title: "Quick facts" }) diff --git a/packages/ui/src/assets/icons/download.svg b/packages/ui/src/assets/icons/download.svg new file mode 100644 index 0000000..d0b1b29 --- /dev/null +++ b/packages/ui/src/assets/icons/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/filament-banner.svg b/packages/ui/src/assets/motifs/filament-banner.svg index 5f9de4d..a37ba46 100644 --- a/packages/ui/src/assets/motifs/filament-banner.svg +++ b/packages/ui/src/assets/motifs/filament-banner.svg @@ -1,2 +1,2 @@ - + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/scorpius.svg b/packages/ui/src/assets/motifs/scorpius.svg new file mode 100644 index 0000000..d500b1f --- /dev/null +++ b/packages/ui/src/assets/motifs/scorpius.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ui/src/assets/motifs/seven-metals-dipper.svg b/packages/ui/src/assets/motifs/seven-metals-dipper.svg index 03f556f..006a25d 100644 --- a/packages/ui/src/assets/motifs/seven-metals-dipper.svg +++ b/packages/ui/src/assets/motifs/seven-metals-dipper.svg @@ -1,2 +1,2 @@ -♄♃♂♀☿☽☉ +♄♃♂♀☿☽☉ POLARIS · PLATINUM \ No newline at end of file diff --git a/packages/ui/src/components/ConstellationDust.astro b/packages/ui/src/components/ConstellationDust.astro index cc87226..6f6cb04 100644 --- a/packages/ui/src/components/ConstellationDust.astro +++ b/packages/ui/src/components/ConstellationDust.astro @@ -1,14 +1,21 @@ --- -/** Sparse, static specks behind a page; about one in five gold. */ +/** + * Static specks behind a page; about one in five gold. Two tiles of different + * sizes repeat across the page, so long pages are as starry as short ones and + * the repeat is hard to spot. + */ import { dust, seedOf } from "../lib/ornaments.ts"; interface Props { - count?: number; seed?: string; } -const { count = 48, seed = Astro.url.pathname } = Astro.props; +const { seed = Astro.url.pathname } = Astro.props; +const base = seedOf(seed); --- - + diff --git a/packages/ui/src/components/PageShell.astro b/packages/ui/src/components/PageShell.astro index 84b0b0b..7d71439 100644 --- a/packages/ui/src/components/PageShell.astro +++ b/packages/ui/src/components/PageShell.astro @@ -12,7 +12,7 @@ import ConstellationDust from "./ConstellationDust.astro"; import EdgeSeals from "./EdgeSeals.astro"; import SiteFooter from "./SiteFooter.astro"; import SiteHeader from "./SiteHeader.astro"; -import type { NavKey } from "./nav.ts"; +import type { NavKey, StoreNav } from "./nav.ts"; import type { PageFamily, SealName } from "./seals.ts"; interface Props { @@ -22,10 +22,12 @@ interface Props { /** A craft page's own seal, overriding the family's. */ seal?: SealName; current?: NavKey; + /** The Store's own header, on Store pages. */ + store?: StoreNav; /** Leave the edge seals off, for a page led by a photo. */ noSeals?: boolean; } -const { title, description, family = "landing", seal, current, noSeals } = Astro.props; +const { title, description, family = "landing", seal, current, store, noSeals } = Astro.props; const fullTitle = title === "Greg Kilmer" ? title : `${title} · Greg Kilmer`; --- @@ -35,6 +37,10 @@ const fullTitle = title === "Greg Kilmer" ? title : `${title} · Greg Kilmer`; + + + + {fullTitle} {description && } @@ -44,7 +50,7 @@ const fullTitle = title === "Greg Kilmer" ? title : `${title} · Greg Kilmer`;
{!noSeals && } - +
@@ -58,6 +64,11 @@ const fullTitle = title === "Greg Kilmer" ? title : `${title} · Greg Kilmer`; position: relative; min-height: 100vh; overflow-x: clip; + /* The page's nebula: violet from the top right, teal from the bottom left. */ + background: + radial-gradient(ellipse at 100% 0%, rgba(106, 47, 224, 0.28), transparent 50%), + radial-gradient(ellipse at 0% 100%, rgba(20, 163, 154, 0.2), transparent 55%), + var(--gk-sky); } .gk-main { position: relative; diff --git a/packages/ui/src/components/SiteHeader.astro b/packages/ui/src/components/SiteHeader.astro index 2d53b4f..0b8abd0 100644 --- a/packages/ui/src/components/SiteHeader.astro +++ b/packages/ui/src/components/SiteHeader.astro @@ -2,65 +2,89 @@ /** * The site header: nebula and filament banner behind the primary nav, an * optional page hero (the `hero` slot), and the header divider. The filament - * banner and divider appear here only, never elsewhere on a page. + * banner and divider appear here only, never elsewhere on a page. Store pages + * pass `store` and get the Store's own header: Store.GregKilmer.com, its + * product lines, Order request and a way back to the main site. */ import monogram from "../assets/brand/gk-monogram.svg?url"; import Divider from "./Divider.astro"; import Star from "./Star.astro"; -import { main, tail, world, type NavKey } from "./nav.ts"; +import { main, tail, world, type NavKey, type StoreNav } from "./nav.ts"; import { filamentBanner } from "./seals.ts"; interface Props { /** Which nav entry the page belongs to. */ current?: NavKey; + store?: StoreNav; + /** Whether the page has a hero; PageShell always fills the slot, so it says. */ + hero?: boolean; } -const { current } = Astro.props; +const { current, store, hero = Astro.slots.has("default") } = Astro.props; const inWorld = world.some((w) => w.key === current); const aria = (key: NavKey) => (key === current ? "page" : undefined); ---
- { - Astro.slots.has("default") && ( + store ? ( + + ) : ( + + ) + } + { + hero && (
) } -
+ {hero &&
}
diff --git a/services/site/src/components/library/Constellation.astro b/services/site/src/components/library/Constellation.astro index 2e16eaa..ca712a4 100644 --- a/services/site/src/components/library/Constellation.astro +++ b/services/site/src/components/library/Constellation.astro @@ -1,9 +1,11 @@ --- /** * The favorites constellation: round covers strung on a dotted gold line, - * each linking to its row in the list. + * each linking to its row in the list. A favorite without a cover gets + * star-chart art in its circle. */ import Star from "@gk/ui/components/Star.astro"; +import StarChartArt from "@gk/ui/components/StarChartArt.astro"; import TickScale from "@gk/ui/components/TickScale.astro"; import { mediaSrc } from "../../lib/site.ts"; import type { Item } from "../../lib/library.ts"; @@ -35,7 +37,7 @@ const points = items.map((_, i) => `${(((i + 0.5) / n) * 1000).toFixed(1)},${lif items.map((item, i) => (
  • - + {item.title}
  • @@ -104,18 +106,32 @@ const points = items.map((_, i) => `${(((i + 0.5) / n) * 1000).toFixed(1)},${lif color: var(--gk-text); text-decoration: none; } - img { + a :global(.orb) { + flex: none; width: 80px; height: 80px; border-radius: 50%; - object-fit: cover; - background: var(--gk-midnight); box-shadow: 0 0 0 2px var(--gk-gold), 0 0 22px rgba(201, 162, 74, 0.45); transition: box-shadow 0.2s; } - a:hover img { + a :global(.orb .mat) { + display: none; + } + a :global(.orb .seal) { + width: 54px; + height: 54px; + opacity: 0.8; + } + a :global(.orb .photo) { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + } + a:hover :global(.orb) { box-shadow: 0 0 0 2px var(--gk-gold-bright), 0 0 30px rgba(201, 162, 74, 0.7); @@ -137,7 +153,7 @@ const points = items.map((_, i) => `${(((i + 0.5) / n) * 1000).toFixed(1)},${lif li { padding-top: 0; } - img { + a :global(.orb) { width: 64px; height: 64px; } diff --git a/services/site/src/components/library/GameCard.astro b/services/site/src/components/library/GameCard.astro index 07decc6..c81f343 100644 --- a/services/site/src/components/library/GameCard.astro +++ b/services/site/src/components/library/GameCard.astro @@ -11,7 +11,7 @@ import StarChartArt from "@gk/ui/components/StarChartArt.astro"; import StatusPill from "@gk/ui/components/StatusPill.astro"; import StatusChip from "../StatusChip.astro"; import { formatDate, mediaSrc, type SiteIndex } from "../../lib/site.ts"; -import { gamePlayers, gamePlayTime, type Game } from "../../lib/library.ts"; +import { gamePlayers, gamePlayTime, minutesOf, playersOf, type Game } from "../../lib/library.ts"; interface Props { game: Game; @@ -24,6 +24,8 @@ const plays = d.howItPlays ?? []; const status = site.statusFor(game); // Players and play time sync from BoardGameGeek; nothing shows until they have. const facts = [gamePlayers(game), gamePlayTime(game)].filter(Boolean); +const seats = playersOf(game); +const hasPost = site.backlinks(game, { type: "post" }).length > 0; ---
    - +
    @@ -60,6 +62,18 @@ const SOCIAL_ICONS: Record = { github: "web", linkedin: "briefca )) } + { + photos.length > 0 && ( +
    + {photos.map((ph) => ( +
    + + {ph.caption &&
    {ph.caption}
    } +
    + ))} +
    + ) + }