All notes

A cover shows
the real project

How our project cards went from AI concepts to real first screens in the reader’s language, without a single image library. And what to show when a project has no website yet.

Straight to the code

Concepts left when the real work arrived

Early versions of our homepage had concept cards with AI-made covers, labelled “Concept · not published”. Within a week, all three places belonged to published projects, and each card showed an illustration from that project’s own website: Piaskot’s workshop cat, the PPC4.biz cursor and KotoHosting’s Archie.

A generated scene presented as a real project would promise something that never happened. Generated images still have a place here: the shared portrait on the About page is an AI scene, and its caption says so.

The cover speaks the reader’s language

A few days later the work page became one grid of identical cards, and every cover became a screenshot of a live website’s first screen. Many of those websites have several languages, so a reader sees the version closest to their own: English on the English page, Polish or else English on the Polish page, Ukrainian on the Ukrainian page. Never Russian, even where a website still has it.

On the card, that takes two attributes. data-src-uk or data-src-pl names another screenshot, data-href-uk or data-href-pl another address. The site builder writes them into each language’s ready HTML. In the source files and in the offline copy, a few lines of JavaScript do the same.

HTML · projects.html

<img src="assets/img/work-kotohosting-en.webp" data-src-uk="assets/img/work-kotohosting.webp" …>
<a class="text-link" href="https://kotohosting.com/en/" data-href-uk="https://kotohosting.com/" …>

A website with one language needs nothing extra: its single screenshot and address serve all three versions of our page.

A screenshot in one DevTools call

We install nothing for this. Edge or Chrome runs without a window, and tools/shoot-cover.cjs talks to it over the DevTools protocol from plain Node.js: since version 22, Node has fetch and WebSocket built in.

The page opens in a 1440 × 960 window, the same 3:2 proportions as our cards. The browser then saves the screen straight to WebP and scales it by two thirds in the same call, so a 960 × 640 cover comes out without any image library.

JavaScript · tools/shoot-cover.cjs

await send('Emulation.setDeviceMetricsOverride', { width: 1440, height: 960, deviceScaleFactor: 1, mobile: false });
// Entrance animations settle into their final state instead of being caught halfway.
await send('Emulation.setEmulatedMedia', { features: [{ name: 'prefers-reduced-motion', value: 'reduce' }] });
await send('Page.navigate', { url });
await loaded();
const shot = await send('Page.captureScreenshot', {
  format: 'webp', quality,
  clip: { x: 0, y: 0, width: 1440, height: 960, scale: 2 / 3 },
});

Terminal · Kotka.dev

node tools/shoot-cover.cjs "https://koto-e.com/?nocache=1" assets/img/work-kotoecom.webp
  • Add a throwaway query such as ?nocache=1: a page cache on the server or in a CDN can hand you an old version, and a stale screenshot is easy to miss.
  • Watch the weight. Our covers weigh about 40–47 KB. A photographic first screen came out at 70 KB with quality 82, so it went down to 74 and 56 KB.
  • A replaced cover gets a new file name. Images are cached for 30 days and the builder does not rename them.

When there is no website to show

Our WooCommerce plugin, Koto Shop, has no public page yet. Its card got a composed cover instead: two phones with the plugin’s real order screens, taken on a local test stand with made-up orders, and Kotka looking on from the right.

Two details keep it honest. The screens are the plugin’s own; only the WordPress update notice was hidden, because it is not part of the plugin. And the card does not pretend to link anywhere: instead of a link, it says that a public version is on the way.

Staged, but never pretending

A cover is a small promise about what is behind the card. A screenshot keeps it by default; a composition keeps it when the words around it tell the truth. Kotka supervises both.

All notesAlso: what goes to the server