changes
Some checks failed
Deploy / build-deploy (push) Has been cancelled

This commit is contained in:
Jorijn van der Graaf 2026-08-05 22:32:03 +02:00
commit 054a26475c
4 changed files with 33 additions and 37 deletions

View file

@ -96,42 +96,42 @@ export const std::vector<Project>& Projects() {
"imsd", "imsd",
"IMS/VoLTE for the Fairphone 6's Qualcomm modem: calls on a mainline-Linux phone.", "IMS/VoLTE for the Fairphone 6's Qualcomm modem: calls on a mainline-Linux phone.",
"https://forgejo.catcrafts.net/Catcrafts/imsd", "https://forgejo.catcrafts.net/Catcrafts/imsd",
"C++23", "C++",
true, true,
}, },
{ {
"Crafter.Graphics", "Crafter.Graphics",
"A C++23 rendering engine: Vulkan with hardware ray tracing natively, WebGPU in the browser.", "A C++ rendering engine: Vulkan with hardware ray tracing natively, WebGPU in the browser.",
"https://forgejo.catcrafts.net/Catcrafts/Crafter.Graphics", "https://forgejo.catcrafts.net/Catcrafts/Crafter.Graphics",
"C++23 modules", "C++ modules",
true, true,
}, },
{ {
"Crafter.Build", "Crafter.Build",
"A build system with no DSL: the build description is a C++ file, compiled and run.", "A build system with no DSL: the build description is a C++ file, compiled and run.",
"https://forgejo.catcrafts.net/Catcrafts/Crafter.Build", "https://forgejo.catcrafts.net/Catcrafts/Crafter.Build",
"C++23 modules", "C++ modules",
true, true,
}, },
{ {
"Crafter.Network", "Crafter.Network",
"A QUIC and HTTP/3 stack; the same client code runs natively and in the browser.", "A QUIC and HTTP/3 stack; the same client code runs natively and in the browser.",
"https://forgejo.catcrafts.net/Catcrafts/Crafter.Network", "https://forgejo.catcrafts.net/Catcrafts/Crafter.Network",
"C++23 modules", "C++ modules",
false, false,
}, },
{ {
"catcrafts.net", "catcrafts.net",
"This website, C++23 compiled to WebAssembly, server-rendered from the same renderers.", "This website, C++ compiled to WebAssembly, server-rendered from the same renderers.",
"https://forgejo.catcrafts.net/Catcrafts/catcrafts.net", "https://forgejo.catcrafts.net/Catcrafts/catcrafts.net",
"C++23 modules", "C++ modules",
false, false,
}, },
{ {
"Crafter.Asset", "Crafter.Asset",
"Texture and mesh pipeline to GPU-friendly formats, decoded on the GPU where possible.", "Texture and mesh pipeline to GPU-friendly formats, decoded on the GPU where possible.",
"https://forgejo.catcrafts.net/Catcrafts/Crafter.Asset", "https://forgejo.catcrafts.net/Catcrafts/Crafter.Asset",
"C++23 modules", "C++ modules",
false, false,
}, },
}; };
@ -143,7 +143,7 @@ export const std::vector<Demo>& Demos() {
{ {
.slug = "raytracer", .slug = "raytracer",
.name = "Real-time ray tracer", .name = "Real-time ray tracer",
.blurb = "Hardware-accelerated ray tracing through WebGPU, driven by the same C++23 WebAssembly module that renders this page. Four coloured lights, one shadow ray each, Reinhard tonemapping.", .blurb = "Hardware-accelerated ray tracing through WebGPU, driven by the same C++ WebAssembly module that renders this page. Four coloured lights, one shadow ray each, Reinhard tonemapping.",
.tech = "WebGPU compute + WGSL", .tech = "WebGPU compute + WGSL",
.needs = "WebGPU: Chrome 121+, Firefox 141+, Safari 26+", .needs = "WebGPU: Chrome 121+, Firefox 141+, Safari 26+",
.mountId = "webgpu-demo", .mountId = "webgpu-demo",

View file

@ -42,7 +42,7 @@ public:
SafeHtml(std::string_view) = delete; SafeHtml(std::string_view) = delete;
// Str() returns a reference (not a copy) because Format() feeds these // Str() returns a reference (not a copy) because Format() feeds these
// to std::make_format_args, which in C++23 binds Args&... and therefore // to std::make_format_args, which in C++ binds Args&... and therefore
// needs lvalues. // needs lvalues.
const std::string& Str() const noexcept { return v_; } const std::string& Str() const noexcept { return v_; }
std::string_view View() const noexcept { return v_; } std::string_view View() const noexcept { return v_; }

View file

@ -111,7 +111,7 @@ export SafeHtml RenderFooter() {
return Format( return Format(
R"(<div class="footer-content">)" R"(<div class="footer-content">)"
R"(<p class="footer-tagline">Open-source systems work from the Netherlands. )" R"(<p class="footer-tagline">Open-source systems work from the Netherlands. )"
R"(This site is C++23 compiled to WebAssembly.</p>)" R"(This site is C++ compiled to WebAssembly.</p>)"
R"(<p class="footer-links">)" R"(<p class="footer-links">)"
R"(<a{}>Forgejo</a><a{}>Source</a>)" R"(<a{}>Forgejo</a><a{}>Source</a>)"
R"(</p>)" R"(</p>)"
@ -189,22 +189,21 @@ export RenderedPage RenderHome(std::span<const Project> projects,
SafeHtml main = Format( SafeHtml main = Format(
R"(<section class="hero">)" R"(<section class="hero">)"
// No visible headline on purpose: a hero slogan reads as marketing, // No slogan on purpose: a hero headline reads as marketing, which is
// which is not the register this site wants. The h1 stays for the // not the register this site wants. Instead the mission sentence IS
// document outline (screen readers, crawlers) but is visually hidden; // the h1 — full weight in the document outline and for crawlers —
// the mission text below is the first thing anyone sees. // styled as body prose (see h1.hero__lede) so nothing on screen
R"(<h1 class="visually-hidden">Catcrafts</h1>)" // changes register. The old version hid a one-word h1 offscreen,
R"(<p class="hero__lede">Catcrafts makes accessible, open-source software )" // which spent the strongest heading on the page saying "Catcrafts"
R"(and hardware for the benefit of everyone. The goal: a real alternative )" // to nobody.
R"(<h1 class="hero__lede">Catcrafts makes accessible, open-source software )"
R"(and hardware for the benefit of everyone.</h1>)"
R"(<p class="hero__lede">The goal: a real alternative )"
R"(to big tech that anyone can use, not just Linux nerds.</p>)" R"(to big tech that anyone can use, not just Linux nerds.</p>)"
R"(<p class="hero__actions">)" R"(<p class="hero__actions">)"
R"(<a class="btn btn--primary"{}>Browse projects</a>)" R"(<a class="btn btn--primary"{}>Browse projects</a>)"
R"(<a class="btn"{}>Browse shop</a>)" R"(<a class="btn"{}>Browse shop</a>)"
R"(</p>)" R"(</p>)"
// The person-company weld, human-readable edition. The about page
// and the Organization founder record are the machine-readable one.
R"(<p class="hero__byline">Catcrafts is the one-person company of )"
R"(<a{}>Jorijn van der Graaf</a>.</p>)"
R"(</section>)" R"(</section>)"
R"(<section class="section">)" R"(<section class="section">)"
R"(<h2 class="section__title">Featured work</h2>)" R"(<h2 class="section__title">Featured work</h2>)"
@ -213,7 +212,6 @@ export RenderedPage RenderHome(std::span<const Project> projects,
R"({})", R"({})",
Url("href", "/projects"), Url("href", "/projects"),
Url("href", "/shop"), Url("href", "/shop"),
Url("href", "/about"),
cards.empty() ? Raw(R"(<p class="empty">No featured projects yet.</p>)") : Join(cards), cards.empty() ? Raw(R"(<p class="empty">No featured projects yet.</p>)") : Join(cards),
recent.empty() ? SafeHtml{} : Format( recent.empty() ? SafeHtml{} : Format(
R"(<section class="section">)" R"(<section class="section">)"
@ -227,7 +225,7 @@ export RenderedPage RenderHome(std::span<const Project> projects,
page.meta.title = "Catcrafts — open-source software and hardware"; page.meta.title = "Catcrafts — open-source software and hardware";
page.meta.description = page.meta.description =
"Catcrafts makes accessible, open-source software and hardware: an " "Catcrafts makes accessible, open-source software and hardware: an "
"alternative to big tech. A C++23 engine, build system and networking " "alternative to big tech. A C++ engine, build system and networking "
"stack from scratch, and Linux with IMS/VoLTE on the Fairphone 6."; "stack from scratch, and Linux with IMS/VoLTE on the Fairphone 6.";
page.meta.canonical = "/"; page.meta.canonical = "/";
// The identity record: name, registered VAT identity and the Forgejo // The identity record: name, registered VAT identity and the Forgejo
@ -241,7 +239,7 @@ export RenderedPage RenderHome(std::span<const Project> projects,
R"("founder":{"@type":"Person","name":"Jorijn van der Graaf",)" R"("founder":{"@type":"Person","name":"Jorijn van der Graaf",)"
R"("url":"https://catcrafts.net/about"},)" R"("url":"https://catcrafts.net/about"},)"
R"("description":"Catcrafts makes accessible, open-source software and hardware: )" R"("description":"Catcrafts makes accessible, open-source software and hardware: )"
R"(the Crafter C++23 suite, the imsd IMS/VoLTE implementation, and Fairphone 6 )" R"(the Crafter C++ suite, the imsd IMS/VoLTE implementation, and Fairphone 6 )"
R"(handsets sold with postmarketOS preinstalled.",)" R"(handsets sold with postmarketOS preinstalled.",)"
R"("sameAs":["https://forgejo.catcrafts.net/Catcrafts/"]})"; R"("sameAs":["https://forgejo.catcrafts.net/Catcrafts/"]})";
page.main = std::move(main); page.main = std::move(main);
@ -271,7 +269,7 @@ export RenderedPage RenderProjects(std::span<const Project> projects) {
RenderedPage page; RenderedPage page;
page.meta.title = "Projects — Catcrafts"; page.meta.title = "Projects — Catcrafts";
page.meta.description = page.meta.description =
"The Crafter suite: a C++23 build system, graphics engine, networking " "The Crafter suite: a C++ build system, graphics engine, networking "
"stack, and an IMS/VoLTE implementation for the Fairphone 6."; "stack, and an IMS/VoLTE implementation for the Fairphone 6.";
page.meta.canonical = "/projects"; page.meta.canonical = "/projects";
page.main = Format( page.main = Format(

View file

@ -268,16 +268,21 @@ treatment it replaces, which read as the wrong category for the work.
.nav-link--active { color: var(--accent); } .nav-link--active { color: var(--accent); }
.nav-link--active:hover { color: var(--accent-hover); } .nav-link--active:hover { color: var(--accent-hover); }
/* hero no headline by choice: the h1 is visually hidden (see RenderHome) /* hero no VISIBLE headline by choice: a slogan-shaped heading is the
and this paragraph opens the page, so it gets full text colour and a size marketing register this site refuses. The mission sentence itself is the
step up from body copy rather than the muted supporting-text treatment it h1 (document outline, crawlers) but wears body-prose clothes: same size
had when a title sat above it. */ step, weight and rhythm as the paragraph that completes it, so the two
render as one block of text. */
.hero { max-width: var(--measure); } .hero { max-width: var(--measure); }
.hero__lede { .hero__lede {
font-size: var(--step-1); font-size: var(--step-1);
color: var(--text); color: var(--text);
text-wrap: balance; text-wrap: balance;
} }
h1.hero__lede {
font-weight: 400;
line-height: inherit;
}
.hero__actions { .hero__actions {
margin-top: var(--s1); margin-top: var(--s1);
display: flex; display: flex;
@ -902,13 +907,6 @@ treatment it replaces, which read as the wrong category for the work.
/* ─── utilities ─────────────────────────────────────────────────────── */ /* ─── utilities ─────────────────────────────────────────────────────── */
@layer utilities { @layer utilities {
.visually-hidden {
position: absolute;
width: 1px; height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.text-muted { color: var(--text-muted); } .text-muted { color: var(--text-muted); }
} }