Project Gallery gets dedicated splash cards, a new LFM page, a wider Context Vigilance hero, a componentized Timeline, and pinned tags on the Toolkit

Why Care?

Most of the projects we work on now have their own GitHub Pages splash sites with custom branding, OG-tuned banner images, and a real point of view. The lossless-site project gallery had no way to surface any of that — every project page was an island, and nothing on it told you "this project also has a splash you should go read."
This jam fixed that — and several other things that had been quietly accumulating tax across the gallery and the toolkit:
  • A live, OG-fetched splash card sits next to every project hero now (MemoPop, Astro Knots, Context Vigilance, Content Farm, LFM), so a reader who lands on /projects/gallery/<x> sees the splash exists and can jump to it.
  • Lossless Flavored Markdown got promoted out of an "also we made this package" footnote on the Astro Knots page into its own dedicated project page — the package is mature enough to deserve the real estate.
  • Context Vigilance got a tagline (A neurotic model of Human + AI Product Development.) and a hero that's allowed to break out of the body container, because heroes are heroes.
  • Both site-wide timelines (/toolkit/timeline and /more-about/timeline) collapsed into one shared component, gained a clear ADDED / UPDATED chip, and stopped silently mangling acronyms like EPLF and NLP into Eplf and Nlp.
  • The toolkit got pinned tags — a row of four featured filters (Check it Out, Must-Have, Influencer Favorites, Agentic AI) above the grid that compose with the existing tag picker, so a visitor can land on a curated slice instantly and still narrow it further.
If you visit the gallery and the toolkit now, the surfaces feel like coordinated, opinionated marketing — not a flat directory of pages. They didn't before.

What's New?

<SplashHeroCard> + fetchOgMetadata()

A new component pair that fetches the splash page's actual <meta> tags at build time and renders a card with the live OG image, title, description, and site name:
  • src/components/tool-components/SplashHeroCard.astro — the visual: 1200×630 image at top, badge, eyebrow site name, title, 3-line clamped description, "Open the splash page →" CTA. Whole card is a link. Hover lifts and scales the image.
  • src/utils/fetchOgMetadata.ts — a tiny util that hits a URL once at build, parses og:*, twitter:*, <title>, and <meta name=description> from the HTML, caches per build, and falls back to author-provided defaults if the splash is unreachable. No new dependencies, 8s timeout, build won't crash on a flaky splash.
Wired into five project pages so far:
ProjectSplash URL
MemoPop AIlossless-group.github.io/memopop-ai/
Astro Knotslossless-group.github.io/astro-knots/
Context Vigilancelossless-group.github.io/context-vigilance-kit/
Content Farmlossless-group.github.io/content-farm/
Lossless Flavored Markdownlossless-group.github.io/lossless-flavored-markdown-package/
The card supports an imageAspect prop for splashes whose OG image is portrait (the Context Vigilance one is 1024×1536, optimized for iMessage / WhatsApp unfurls). Setting imageAspect="4 / 3" crops to landscape with object-fit: cover so the card height stays consistent with the others.

A dedicated /projects/gallery/lossless-flavored-markdown page

Structure deduced directly from the LFM splash:
  1. Hero — eyebrow, title, "MDX power without MDX's opinions" sublede, polyglot lede, three stats (8+ plugins · 1 canonical AST · v0.3 latest), seven capability chips, an inline pnpm add jsr:@lossless-group/lfm CLI block, JSR + GitHub CTAs. SplashHeroCard floats right.
  2. STC Framework — three numbered cards with arrow connectors: Declare Syntax → Parse Trigger → Component Pipeline.
  3. Live Example — three cards showing how a YouTube embed travels: bare-URL syntax → remark-link-preview.tsYoutubeShareEmbed--Base.astro.
  4. What's in the box — eight-plugin grid (2-col): polyglot pipeline, remark-gfm, remark-directive, remark-callouts, remark-citations, link-preview/og-fetcher, bare-link catalog, remarkLosslessWikilinks. Each card has a status pill (Stable / Beta / New in 0.3.0) and tag chips.
  5. What shipped lately — three release cards, plus a link to the canonical CHANGELOG.md.
  6. Footer CTA — Install / Source / Spec.
Registered in src/config/project-gallery.json so it surfaces in the Projects dropdown alongside MemoPop, Astro Knots, and Context Vigilance.

Astro Knots and Context Vigilance got more content + better balance

Astro Knots: added five new sections to match MemoPop's content density — An Honest Reckoning (what survived / what we walked back), The One True Package: @lossless-group/lfm, Pattern Packages grid, Two Internal Reference Pages, Per Site (/brand-kit vs /design-system), and a Quick Start CLI block. The hero left side now carries a stats panel (12 sites, 3 theme modes, 1 published package), capability chips, and a GitHub repo CTA — so it visually balances the splash card on the right.
Context Vigilance: added the tagline A neurotic model of Human + AI Product Development. as an italic aquamarine sublede, three hero stats (583 context-v files · 28 projects · 4 directory roles), six capability chips, and lifted the hero out of the standard 1100px body container into a wider .hero-bleed wrapper that goes to 1500px — so the hero gets to be a hero.
The tagline also got pushed up to the splash itself: siteName in ai-labs/context-vigilance-kit/splash/src/lib/seo.ts was changed from 'Context Vigilance' (which was duplicating the title in the SplashHeroCard eyebrow) to the new tagline. The change won't reflect on the lossless-site card until the splash redeploys.

<Timeline> component, with an explicit ADDED / UPDATED chip

/toolkit/timeline and /more-about/timeline were ~85% the same code. Now they share a single <Timeline> component (src/components/timeline/Timeline.astro) that owns:
  • Month → Week bucketing with weekly range labels
  • Stats row (N events · N added · N updated)
  • Search input filtering across title, id, meta, tags
  • Per-kind toggle chips (Added / Updated)
  • Per-collection toggle chips, generated from a collections prop
  • A clear ADDED / UPDATED chip on every event, after the date — addresses the embarrassment of Perplexity showing up under "May 2026" looking like it was just discovered when actually it was just updated
  • Collection chip styling driven by data — caller passes { color, bg }, no CSS edit needed to add a new collection
Each page now reduces to ~85 lines, mostly data-mapping. The toolkit timeline gained search + filter chips + collapse-by-month + the ADDED/UPDATED chip it didn't have before. It also gained internal links: each title links to /toolkit/{slug} (the detail page), and when an entry has a url / github_repo_url, the title opens that in a new tab.

The casing-destruction bug

A subtle one. The reference timeline was rendering EPLF NLP Lab as Eplf Nlp Lab. Why? Astro's default glob loader passes filenames through its slug generator, which lowercases them — so by the time we read entry.id, EPLF-NLP-Lab.md had become eplf-nlp-lab. We were then calling toProperCase(), which capitalizes the first letter of each word and keeps the rest lowercase.
Astro does, however, expose entry.filePath — the original on-disk path, with original casing intact. The fix is two lines in each timeline page:
ts
function originalFilename(entry: any): string {
  const path: string = entry.filePath || entry.id;
  const base = path.split('/').pop() || path;
  return base.replace(/\.(md|mdx)$/i, '').replace(/[-_]+/g, ' ');
}
Read from filePath, drop the extension, turn -/_ into spaces. No case transform. Acronyms preserved.
This is one of those "Title Case" foot-guns that has no clean built-in fix in JavaScript: there is no standard case mode that capitalizes word starts while preserving runs of uppercase. Most libraries either ship a preserveAcronyms: true flag or punt. We punted by not transforming at all — the source filename is already correct, that's why we wrote it that way.

Toolkit: pinned tags + case-insensitive filtering

The toolkit landing page (/toolkit) now opens with a row of four pinned tag chips above the card grid:
  • Check it Out — promising sleeper hits we want a reader to peek at
  • Must-Have — the shortlist; tools we keep coming back to
  • Influencer Favorites — what the people we follow are using
  • Agentic AI — agents, orchestrators, autonomous workflows
Each pin shows a live count and behaves exactly like clicking a tag chip on a tool card — it toggles that tag through the existing Choices.js multi-select in the sidebar. Which means pins compose with the rest of the filter UI: a reader can click Must-Have to land on the shortlist, then add RAG from the sidebar to narrow further. Two filters, one direction.
The implementation reuses the existing .tool-tag document-level click handler in TagColumn.astro — the pinned chips just carry both class="tool-tag pinned-tag" and data-tag="...", so the existing handler routes them through the Choices instance for free. Active state on the pins is driven by listening to TagColumn's existing filtersChanged custom event; no new wiring inside TagColumn was needed.
While we were in there we also made the whole tag-filter pipeline case-insensitive, end to end:
  • The corpus has both Check-it-Out and Check-It-Out (capital I) variants in different tool files. Strict equality was making them look like two different tags — clicking the pin would only filter the canonical-case ones, leaving the variant outliers invisible.
  • Three places now lowercase before comparing: the main filter (selectedTags.every(...)), the per-card chip highlight (which lights up the matching chip on every visible card), and the chip-click toggle (so clicking Check-It-Out on a card removes a previously-selected Check-it-Out instead of treating them as different tags).
  • The pinned-tag count was already case-insensitive, so the pipeline is now case-blind end to end.
We considered a one-time normalization script across content/tooling/ to fix the drift at the source. We'd been here before — those scripts are the kind of thing that "almost works" until they overwrite the wrong file. Three lines of String#toLowerCase() in the filter were strictly safer.

One bonus fix: the mobile hamburger menu

While we were in the project pages, we noticed the mobile hamburger menu didn't drop down from any of them — only from the homepage. Same root cause as the recent "fix double footers" commit (81044cf): four project pages were rendering <Header /> a second time inside Layout.astro's slot, while Layout.astro already mounts a Header in <body>. With duplicate id="mobile-menu" and class selectors, the toggle script wired itself to the first Header in the DOM, but the second (later in DOM, same position: fixed; top: 0) was the one visually receiving clicks. Removed the redundant <Header /> (and unused Footer / separator imports) from:
  • src/pages/projects/development.astro:11
  • src/pages/projects/gallery/context-vigilance.astro:54
  • src/pages/projects/gallery/memopop-ai.astro:43
  • src/pages/projects/gallery/astro-knots.astro:46

What this enables next

  • Adding new project pages now costs less. Six lines of frontmatter + an <aside> slot gets you a live splash card. The pattern is well-trodden enough that we should consider extracting a <ProjectHero> wrapper next.
  • The Timeline component is reusable for any collection pair with date_created / date_modified fields. Sources timeline. Organizations timeline. A per-client timeline. Each is now a 50-line page.
  • The OG fetcher is reusable. Any time we want a card that mirrors an external page's metadata (a study, a referenced product, a partner site), the util is there.
  • The casing fix quietly applies to anywhere else we were using entry.id as a display fallback. Worth grepping for.
A reader's job is to scan, decide if they care, and either go deeper or move on. The gallery is finally giving them enough surface area at first glance to make that decision.