All notes

A website to go:
HTML without the internet

Pack the cat, the fonts and the notebook. Leave the server behind — and be clear about what an offline copy can and cannot do.

Straight to the code

What do we mean by “offline”?

Kotka.dev started as plain HTML, CSS and JavaScript. We wanted a copy that could be unpacked and shown without a running server: the same fonts, cat photographs, notes and language switch. Saving only the HTML would leave its linked images and scripts behind.

Our solution is a delivery copy, not a new source format. Each page embeds its own assets; a ZIP keeps all the pages together. The editable source files stay separate. This fits a small static website. It does not turn a server application into an offline application.

Pack the dependencies, not just the page

  1. Inline styles and scripts
    The builder inserts our stylesheet into a style element and each classic script into a script element. Their order stays the same: note data, interface, notebook.
  2. Embed local images and fonts
    The image bytes become data URLs. The same happens to font and texture URLs inside CSS. Check the favicon and images referenced in data attributes too, not only the visible hero.
  3. Rewrite internal destinations
    A source link to notes.html must point to kotka-notes-standalone.html. Article paths stored in JavaScript need the same rewrite. Keep language parameters and section anchors.

A data URL includes a media type and the encoded content, for example data:image/webp;base64,…. It is not a remote image request. The type should describe the actual file, not a misleading name from a download URL. MDN: data URLs

The small helper that mattered

While checking the PPC4 cover in our built HTML, we found its WebP bytes labelled application/octet-stream. That browser still displayed it, but relying on that fallback was unnecessary. The builder now sets modern image types explicitly.

This is the actual helper from tools/build-standalone.py, with the imports needed to use it on its own. Call data_uri(pathlib.Path("assets/img/project-ppc4-hero.webp")) to obtain a string for an image source. This helper embeds one file; it does not rewrite a whole website by itself.

Python 3 · data_uri()

import base64, mimetypes, pathlib

def data_uri(path: pathlib.Path) -> str:
    # Windows MIME registry entries may omit modern image types.
    known_types = {".woff2": "font/woff2", ".webp": "image/webp", ".avif": "image/avif"}
    mime = known_types.get(path.suffix.lower()) or mimetypes.guess_type(path.name)[0] or "application/octet-stream"
    return f"data:{mime};base64,{base64.b64encode(path.read_bytes()).decode()}"

Python’s mimetypes database can depend on the operating system, including the Windows registry. An explicit mapping makes these three asset types predictable in our bundle. It still assumes the files have correct extensions. Python: mimetypes

In the Kotka.dev project directory, run the two commands below. Building needs Python 3; the regression checks need Node.js. Neither command installs third-party packages. The builder replaces the generated HTML and ZIP in dist, so edit the source pages, not those generated files.

Terminal · Kotka.dev

python tools/build-standalone.py
node tools/check-notebook.cjs

Unpack dist/kotka-dev-offline.zip, keep its HTML files in the same folder, and open kotka-dev-standalone.html. Each page carries its own assets, but the linked pages are still separate files. Sending only the homepage loses those destinations.

Check the copy you are going to send

  • Check generated HTML for leftover local asset paths and source-page names. Our tests also compare embedded project-image bytes with the originals.
  • Open the unpacked homepage in the target browser without a network connection. Follow the archive, article and back links; look for missing fonts or images.
  • Switch UA / EN / PL and follow a link. Offline, our links carry ?lang= (on the website the language is part of the address, like /pl/); optional local storage must not be the only way to preserve the language.
  • Keep expectations honest: English article text is static HTML, while translation, search and notebook switching require JavaScript.

Our automated checks validate generated paths, embedded assets and script syntax. Browser checks over localhost verify layout and interactions. That is useful, but not identical to testing a file opened from disk: a file-mode check in the recipient’s browser remains a separate final step.

A useful copy, not a universal packer

Our builder knows the exact markup and asset paths of Kotka.dev. Its targeted replacements do not cover arbitrary websites, module imports, every CSS URL syntax or server routes. Embedded assets are also repeated between pages. For a larger site, a folder of shared assets or a build pipeline may be more appropriate.

For this small portfolio, the trade-off is intentional: a predictable package we can inspect and pass along. Kotka travels inside it. Her live links do not magically bring the internet with them.

All notesAlso: the latest four notes