All checks were successful
Deploy / build-deploy (push) Successful in 4m46s
298 lines
21 KiB
C++
298 lines
21 KiB
C++
/*
|
||
catcrafts.net
|
||
Copyright (C) 2026 Catcrafts
|
||
|
||
The source code of this website is made available for viewing purposes only.
|
||
No permission is granted to copy, modify, distribute, or create derivative works.
|
||
*/
|
||
|
||
// The site's authored content, as code.
|
||
//
|
||
// This replaced content/{products,projects,legal,demos}.json and their
|
||
// runtime loaders. The reasoning: this codebase's whole style is making
|
||
// invalid states fail at COMPILE time (SafeHtml, integer money), while the
|
||
// JSON loaders did the opposite — a typo'd field silently dropped a variant,
|
||
// and one stray trailing comma once blanked every legal page at runtime while
|
||
// the server kept answering 200. Content edits go through git push and a full
|
||
// CI build regardless (there is no content-only deploy), so the "edit without
|
||
// a toolchain" argument bought nothing here. Now a broken product is a
|
||
// compile error, the wasm bundle ships four fewer files, and the duplicated
|
||
// keep-in-step loaders in main.cpp and the server module are gone.
|
||
//
|
||
// posts.json and rates.json remain data on purpose: shell pipelines write
|
||
// them at build time (fediverse fetch, ECB rates), and shell writes JSON,
|
||
// not C++.
|
||
//
|
||
// PRICING RULE (the user's): retail = supplier price + markup, exactly.
|
||
// Supplier prices are what the retailer currently charges (incl VAT);
|
||
// change one number when the supplier moves and the margin stays put.
|
||
|
||
export module Catcrafts.Shared:Content;
|
||
import std;
|
||
import :Model;
|
||
|
||
namespace Catcrafts::Content {
|
||
|
||
// The flat markup on every variant — "whatever it costs me + 50".
|
||
inline constexpr std::int64_t kMarkupMinor = 5000;
|
||
|
||
export const std::vector<Product>& Products() {
|
||
static const std::vector<Product> products = [] {
|
||
Product p;
|
||
p.slug = "fp6-pmos";
|
||
p.name = "Fairphone 6 with postmarketOS";
|
||
p.tagline = "A repairable Android phone, reflashed to run mainline Linux with a working IMS/VoLTE stack.";
|
||
// Launch day is this one line: "coming-soon" -> "available". The page
|
||
// shows launch prices either way; only the order form is held back.
|
||
p.status = "coming-soon";
|
||
p.shipNlMinor = 1500; // zone FALLBACKS — the live
|
||
p.shipEuMinor = 2500; // Sendcloud table overrides
|
||
p.shipWorldMinor = 5500; // these per country
|
||
p.image = "/fp6-pmos.jpg";
|
||
p.summary = "A Fairphone 6, reflashed by Catcrafts to run postmarketOS with the patches and IMS/VoLTE implementation Catcrafts maintains. All of that software is open source. You can download it and flash a Fairphone yourself, and you are welcome to. What you pay for here is the thing open source doesn't come with: real support. A phone that arrives working, and one email address that answers for it. Not a forum, not a git issue, but support like any other manufacturer offers. And the margin funds the development itself.";
|
||
p.warranty = "Two years from Catcrafts, worldwide, one counter: every claim goes to Catcrafts, whatever turns out to be broken. The software (postmarketOS, the patches, imsd) is Catcrafts' own work and is fixed by Catcrafts with updates, delivered over the air. Even a phone that no longer boots is normally recovered in place: as long as fastboot still comes up, Catcrafts walks you through reflashing it over a USB cable in minutes. Only a phone that shows nothing at all, not even fastboot, travels for a software fault. For a hardware fault Catcrafts takes the phone back and handles the manufacturer's process, including the temporary reflash to stock Android it requires, and returns it running postmarketOS. When a phone does have to travel, warranty shipping is paid by Catcrafts, both directions, worldwide. EU consumers hold their statutory rights on top of all this; nothing here limits them. The full terms are on the terms page.";
|
||
// The one claim on this page that is about safety rather than
|
||
// features. It stays until emergency calling has been verified on a
|
||
// real network — removing it is a decision, not a cleanup.
|
||
p.safetyNote = "Emergency calling (112/911) is implemented in imsd, including carrier-broadcast emergency numbers, but not yet verified against a live network.";
|
||
p.variants = {
|
||
// supplier €513.30 incl VAT
|
||
{ "green", "Forest Green", 51330 + kMarkupMinor },
|
||
// supplier €519.30 incl VAT
|
||
{ "black", "Black", 51930 + kMarkupMinor },
|
||
// supplier €604.88 incl VAT
|
||
{ "white", "White", 60488 + kMarkupMinor },
|
||
};
|
||
// The hardware, as Fairphone specifies it — this is a stock Fairphone
|
||
// 6, so its spec sheet is this product's spec sheet.
|
||
p.specs = {
|
||
{ "Display", "6.31″ OLED, 2484 × 1116 (FHD+), up to 120 Hz, Gorilla Glass 7i" },
|
||
{ "Processor", "Qualcomm Snapdragon 7s Gen 3, Adreno 810 GPU" },
|
||
{ "Memory", "8 GB RAM" },
|
||
{ "Storage", "256 GB, microSD slot up to 2 TB" },
|
||
{ "Rear cameras", "50 MP main with OIS + 13 MP ultra-wide" },
|
||
{ "Front camera", "32 MP" },
|
||
{ "Battery", "4415 mAh, user-replaceable, 30 W fast charging" },
|
||
{ "Connectivity", "5G, Wi-Fi 6E, Bluetooth 5.4, NFC" },
|
||
{ "SIM", "Dual: nano-SIM + eSIM" },
|
||
{ "USB", "USB-C 2.0" },
|
||
{ "Fingerprint reader", "Side-mounted, in the power button" },
|
||
{ "Durability", "IP55, 12 user-replaceable modules" },
|
||
{ "Dimensions", "156.5 × 73.3 × 9.6 mm, 193 g" },
|
||
};
|
||
// Same rule the loader used to apply: the from-price is the
|
||
// cheapest variant, derived so the two can never disagree.
|
||
if (const Variant* cheapest = p.CheapestVariant()) {
|
||
p.priceInclMinor = cheapest->priceInclMinor;
|
||
}
|
||
return std::vector<Product>{ std::move(p) };
|
||
}();
|
||
return products;
|
||
}
|
||
|
||
export const std::vector<Project>& Projects() {
|
||
static const std::vector<Project> projects = {
|
||
{
|
||
"imsd",
|
||
"IMS/VoLTE for the Fairphone 6's Qualcomm modem: calls on a mainline-Linux phone.",
|
||
"https://forgejo.catcrafts.net/Catcrafts/imsd",
|
||
"C++23",
|
||
true,
|
||
},
|
||
{
|
||
"Crafter.Graphics",
|
||
"A C++23 rendering engine: Vulkan with hardware ray tracing natively, WebGPU in the browser.",
|
||
"https://forgejo.catcrafts.net/Catcrafts/Crafter.Graphics",
|
||
"C++23 modules",
|
||
true,
|
||
},
|
||
{
|
||
"Crafter.Build",
|
||
"A build system with no DSL: the build description is a C++ file, compiled and run.",
|
||
"https://forgejo.catcrafts.net/Catcrafts/Crafter.Build",
|
||
"C++23 modules",
|
||
true,
|
||
},
|
||
{
|
||
"Crafter.Network",
|
||
"A QUIC and HTTP/3 stack; the same client code runs natively and in the browser.",
|
||
"https://forgejo.catcrafts.net/Catcrafts/Crafter.Network",
|
||
"C++23 modules",
|
||
false,
|
||
},
|
||
{
|
||
"catcrafts.net",
|
||
"This website, C++23 compiled to WebAssembly, server-rendered from the same renderers.",
|
||
"https://forgejo.catcrafts.net/Catcrafts/catcrafts.net",
|
||
"C++23 modules",
|
||
false,
|
||
},
|
||
{
|
||
"Crafter.Asset",
|
||
"Texture and mesh pipeline to GPU-friendly formats, decoded on the GPU where possible.",
|
||
"https://forgejo.catcrafts.net/Catcrafts/Crafter.Asset",
|
||
"C++23 modules",
|
||
false,
|
||
},
|
||
};
|
||
return projects;
|
||
}
|
||
|
||
export const std::vector<Demo>& Demos() {
|
||
static const std::vector<Demo> demos = {
|
||
{
|
||
.slug = "raytracer",
|
||
.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.",
|
||
.tech = "WebGPU compute + WGSL",
|
||
.needs = "WebGPU: Chrome 121+, Firefox 141+, Safari 26+",
|
||
.mountId = "webgpu-demo",
|
||
.aspect = "16 / 9",
|
||
.needsWasm = true,
|
||
},
|
||
};
|
||
return demos;
|
||
}
|
||
|
||
// The about page: the deliberate weld between the person and the company.
|
||
// Search and AI answer engines had already connected Jorijn to catcrafts.net
|
||
// through the kernel patch trail, but with nothing first-party to quote they
|
||
// guessed — one AI overview attributed the domain to a different developer
|
||
// entirely, several redirected visitors to the Minecraft server on the
|
||
// singular domain. This page is the authoritative answer to "who is behind
|
||
// Catcrafts", for people and for machines. Same headed-sections shape as the
|
||
// legal pages, so it reuses their renderer's markup and CSS.
|
||
export const LegalPage& AboutPage() {
|
||
static const LegalPage page{
|
||
.slug = "about",
|
||
.title = "About",
|
||
.updated = "2026-08-05",
|
||
.lede = "Catcrafts is a one-person company. This page is about the person, the mission, and the name.",
|
||
.sections = {
|
||
{ "Who",
|
||
{
|
||
"Catcrafts is the registered one-person company (KVK 78437059) of Jorijn van der Graaf, a Dutch software engineer better known in the Linux mobile world as TheMightyCat.",
|
||
} },
|
||
{ "The mission",
|
||
{
|
||
"Accessible open-source software and hardware, for the benefit of everyone. Software you can read, hardware you can repair, and phones that answer to their owner rather than to an advertising department. A real alternative to big tech not a manifesto about one, but things you can download, flash and buy today.",
|
||
"Everything Catcrafts makes is published as open source. The shop is how the development gets funded: buying a phone here pays for the stack it runs.",
|
||
} },
|
||
{ "The work",
|
||
{
|
||
"imsd is the IMS/VoLTE implementation that makes phone calls work on mainline-Linux phones — written from scratch and tested on a live Dutch network. It is the reason a Fairphone 6 running postmarketOS can ring. Jorijn maintains the Fairphone 6 port of postmarketOS and upstreams the hardware support into the Linux kernel: sensor bindings, Qualcomm audio, the unglamorous patches that make a phone a phone.",
|
||
"The Crafter suite is the other half: a C++ build system with no DSL, a graphics engine that ray-traces natively on Vulkan and in the browser through WebGPU, a QUIC and HTTP/3 networking stack, and more. This website is built with all of it — the same C++ renders each page on the server and again in your browser as WebAssembly.",
|
||
"And ofcourse all the upstreaming work that comes along, Catcrafts will upstream everything it makes whenever possible.",
|
||
} },
|
||
{ "The name",
|
||
{
|
||
"Catcrafts is not the Minecraft server. That is catcraft.net — singular — a domain Jorijn registered in 2019 and let lapse, on which a rather successful Minecraft community then grew. He has been explaining the difference ever since, mostly to search engines. The cat in the logo speaks shell, not creeper.",
|
||
} },
|
||
},
|
||
};
|
||
return page;
|
||
}
|
||
|
||
export const std::vector<LegalPage>& LegalPages() {
|
||
static const std::vector<LegalPage> pages = {
|
||
{
|
||
.slug = "privacy",
|
||
.title = "Privacy",
|
||
.updated = "2026-08-04",
|
||
.lede = "What this site collects, why, and how to get rid of it. Written to describe what the code actually does. If you find a discrepancy, the code is the bug and a report is very welcome.",
|
||
.sections = {
|
||
{ "Who is responsible",
|
||
{
|
||
"Catcrafts, Netherlands. Contact details are on the imprint page. Catcrafts is the controller for everything described here; no other party is involved.",
|
||
} },
|
||
{ "Orders",
|
||
{
|
||
"Placing an order stores what fulfilling it requires: your email address, the recipient name and shipping address, the country, and the order itself (product, amounts, timestamps, payment reference and status). Nothing else is asked for and nothing else is kept. The legal basis is the contract: this data is what shipping you a phone and issuing an invoice consist of.",
|
||
"Payment happens at Mollie, a Dutch licensed payment provider, on their pages. Catcrafts never sees card numbers or bank credentials. It learns only which order was paid, for how much, and by which method. What Mollie processes about you is between you and Mollie under their own privacy policy.",
|
||
"The order status page lives at an unguessable link. Anyone holding the link can read that order's status and totals, so treat it like a receipt on your desk and don't post it anywhere public.",
|
||
} },
|
||
{ "How long it is kept",
|
||
{
|
||
"Order records that belong to an invoice are kept for seven years. That is the Dutch fiscal retention obligation, and it is the one case where a deletion request cannot be honoured early. Everything in an order that the tax records do not need is deleted on request.",
|
||
"An order that is never paid lapses; its record is kept briefly for abuse spotting and then has no reason to exist.",
|
||
"The order file is stored on a machine Catcrafts runs, readable only by the service account, and encrypted before any backup leaves that machine.",
|
||
} },
|
||
{ "What this site does not do",
|
||
{
|
||
"No analytics. No cookies, none at all, which is why there is no cookie banner. No third-party scripts, no fonts loaded from anyone else's server, no embedded video, no social buttons, no advertising, no profiling, no automated decision-making.",
|
||
"Everything the browser loads comes from catcrafts.net. Following a link out (to a fediverse thread, to Forgejo, to the Mollie payment page) puts you on that site under its terms, and Catcrafts has no visibility into what happens there.",
|
||
} },
|
||
{ "Server logs",
|
||
{
|
||
"The web server keeps ordinary request logs. Those exist to debug faults and spot abuse, and are not connected to order records or used to build any kind of profile.",
|
||
} },
|
||
{ "Your rights",
|
||
{
|
||
"Under the GDPR you can ask for a copy of what Catcrafts holds about you, have it corrected or deleted, restrict how it is used, object to its use, or ask for it in a portable form. Please contact privacy@catcrafts.net for this purpose.",
|
||
} },
|
||
{ "Changes",
|
||
{
|
||
"This page has a date at the top. Anything that changes what is collected or why will be a new date and a note in the posts feed, not a silent edit.",
|
||
} },
|
||
},
|
||
},
|
||
{
|
||
.slug = "imprint",
|
||
.title = "Imprint & contact",
|
||
.updated = "2026-08-04",
|
||
.lede = "Who is behind this site and how to reach them.",
|
||
.sections = {
|
||
{ "Contact",
|
||
{
|
||
"Email info@catcrafts.net. One inbox answers everything: support, orders, privacy requests and anything else. The topical addresses named elsewhere on this site (privacy@catcrafts.net, security@catcrafts.net) reach the same person.",
|
||
"For anything about the code itself, an issue on Forgejo is usually better than email: it stays public and searchable for the next person with the same question.",
|
||
} },
|
||
{ "Business details",
|
||
{
|
||
"Catcrafts, owner Jorijn van der Graaf, KVK 78437059, VAT NL003329281B38.",
|
||
} },
|
||
{ "Security reports",
|
||
{
|
||
"If you find a vulnerability, please email at security@catcrafts.net before disclosing it publicly and you will be credited. This site is source-available on Forgejo, so you can read exactly what it does rather than guessing.",
|
||
} },
|
||
},
|
||
},
|
||
{
|
||
.slug = "terms",
|
||
.title = "Terms",
|
||
.updated = "2026-08-04",
|
||
.lede = "The terms for buying from this shop. Written to be read: short sections, no boilerplate imported from anywhere, and every claim checkable against what the site actually does.",
|
||
.sections = {
|
||
{ "Ordering and payment",
|
||
{
|
||
"Submitting the order form creates an order and a Mollie payment link. The order is an offer to buy; the contract forms when the payment arrives. Until then nothing is owed: an unpaid order simply lapses and can be ignored.",
|
||
"Prices are in euros, and euros are what is charged; any amount shown in another currency is indicative only, converted at the ECB reference rate of the date shown. Inside the EU the shown price includes 21% Dutch VAT. Outside the EU the sale is a zero-rated export at the derived ex-VAT price, and the price then excludes import duty, import VAT, tariffs and any carrier handling or brokerage fee. Those charges arise on arrival in your country, are levied by the carrier or your customs authority, and are solely a matter between you and them: Catcrafts does not collect them, cannot bindingly estimate them, is not a party to their assessment, and refusal to pay them does not undo the sale. Your bank or card sets the actual euro conversion rate for whatever you pay with.",
|
||
"Payment is handled by Mollie, a Dutch licensed payment institution. Catcrafts never sees your card number or bank credentials.",
|
||
"For support related to orders please contact orders@catcrafts.net"
|
||
} },
|
||
{ "Fulfilment",
|
||
{
|
||
"Devices are sourced, flashed and tested to order. There is no warehouse. Allow up to a week between payment and dispatch; the order page and email updates track it. If sourcing falls through, you get the money back, promptly and in full.",
|
||
"Shipping is tracked and insured. The tiers and prices are shown at checkout before you commit.",
|
||
} },
|
||
{ "Warranty",
|
||
{
|
||
"Everything sold here is warranted by Catcrafts for two years from delivery, worldwide. One counter: every claim goes to Catcrafts, and Catcrafts deals with whoever needs dealing with. You never have to work out whether a fault is hardware or software, or talk to a manufacturer.",
|
||
"Behind that counter the split is simple. Software Catcrafts wrote is fixed by Catcrafts with an update, delivered remotely like any other update. If a fault stops the product from starting, Catcrafts provides the tools and walks you through recovering it in place, so a software fault still does not mean sending anything anywhere. Hardware faults are handled through the manufacturer's or supplier's warranty: Catcrafts keeps the purchase paperwork those claims depend on and runs the process end to end. What that means for a specific product is described on its own page.",
|
||
"When a product does have to travel (a hardware fault, or a product so far gone it no longer responds to recovery tools at all), the shipping is paid by Catcrafts, both directions, worldwide. Every unit is tested before dispatch, so a genuine defect should be rare. When one happens anyway, it should not cost you anything. The one exception: if a returned product turns out to have no fault, or the damage is yours (a drop, water damage, a repair attempt gone wrong), the repair and the shipping are billed at cost, and you are told the price before any work happens.",
|
||
"If you are an EU consumer you additionally hold the statutory conformity guarantee: under Dutch law it lasts as long as a product of this kind may reasonably be expected to last, a defect appearing in the first year is presumed to have existed at delivery, and remedies under it are free. Nothing in this section limits those rights.",
|
||
"For warranty please contact warranty@catcrafts.net"
|
||
} },
|
||
{ "Returns",
|
||
{
|
||
"EU consumers can withdraw from the purchase within 14 days of delivery, no reason needed: send an email, send the product back, and the price plus the standard shipping you paid is refunded within 14 days of your notice, though the refund can wait until the product is back or you show it has been shipped. Return shipping is yours to arrange and pay. You may inspect the product as you would in a shop; value lost through use beyond that can be deducted from the refund.",
|
||
"Outside the EU, sales are final except for defects: the warranty above applies in full, but there is no change-of-mind window. Import duties and fees paid to your own authorities are between you and them and are never refunded by Catcrafts in any case.",
|
||
"A product that arrives broken is a warranty case, not a return: the warranty section applies, and the shipping is on Catcrafts.",
|
||
} },
|
||
},
|
||
},
|
||
};
|
||
return pages;
|
||
}
|
||
|
||
} // namespace Catcrafts::Content
|