/* 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. */ // Content types, and loaders for the two files that stay data. // // The split: content a person authors (products, projects, legal pages, // demos) is compiled in — see :Content — so a typo in it is a build error, // not a silently blank page. Content a machine writes at build time stays // JSON under content/: posts.json (fetched from the fediverse by CI) and // rates.json (ECB rates). Those change without anyone editing C++, so their // loaders live here. // // Both hosts call the same loaders on the same bytes; only where the bytes // come from differs. The wasm build gets the files through Crafter.Build's // VFS (cfg.files -> files.json -> fetched before _start, readable at the // bundle root); the native server reads content/ off disk. So the loaders // take text, not paths. // // Loading never fails hard. These files are generated by our own CI, so a // missing or wrong-typed field is a generator bug, and a page rendering with // one bad card beats a page that refuses to render at all. Malformed JSON is // the one exception — that yields an empty list, because a half-parsed // document is worse than none. export module Catcrafts.Shared:Model; import std; import :Json; namespace Catcrafts { // One image or video belonging to a post. // // `src` is a local path under /media once tools/fetch-media.sh has mirrored it. // It can still be an absolute URL if that download failed, which is why the // renderer puts it through Url() rather than assuming it is site-relative. // // Width and height come from ffprobe at mirror time and exist to stop layout // shift: without them the browser cannot reserve space, and the text below every // card jumps as each file arrives. export struct PostMedia { std::string src; std::string kind; // "image" | "video" // Still frame for a video, from the instance's own thumbnail. Without it a //