A snow-trail theme for Gokapi.
Self-hosted file sharing dressed in snow and glacier light: a download-safe admin
overlay and a view-only watermarked viewer. A theme that rides Gokapi's customDir
seam, never a fork.
Live demo → · MIT licensed
A shared file leaves a trail: the watermark, the expiry, the short link, the
provenance stamp. fichrs (fichiers + traces) leans into that. It is pure CSS
and JavaScript you drop into Gokapi's customDir, plus a standalone view-only
viewer page. No build step, no fork, no AGPL source-offer liability. It rides the
supported seam and survives upgrades.
![]() |
![]() |
| The view-only viewer: a password-gated dark stage with the watermark baked into the pixels. | The download-safe admin: hover to preview, without spending a download. |
tokens.css: the snow-trail design system. One OKLCH palette, two stages (light chrome for managing, a dark stage for viewing). Every fill and text pair is contrast-verified.custom.css+admin.js: the Gokapi admin overlay. Two safety behaviours:- Download-safe preview. Hotlinks count against a file's download limit in
Gokapi (
ServeFile(..., increaseCounter=true)). The hover preview fetches an image only when its downloads are unlimited; limited files show a cost note and never spend a download. - Accidental-delete guard. Gokapi deletes on a single unconfirmed click. A
capture-phase
confirm()names the file first; Cancel suppresses the native handler before it ever runs.
- Download-safe preview. Hotlinks count against a file's download limit in
Gokapi (
viewer/index.html: a standalone view-only viewer. It prompts for the file's password, renders the document to<canvas>behind a diagonal watermark (recipient, IP, access time), and strips the obvious save affordances.
The watermark overlaps the content on purpose and offsets alternate rows, so there is no clean removal corridor: inpainting it away means repainting the whole document. But pixels on screen are bytes in the browser. Treat it as provenance and a deterrent (a careless re-share carries the recipient's name and the time), not DRM. Real protection stays password plus expiry.
fichrs has two deploy targets, because the viewer is not a customDir asset.
Point Gokapi at a custom directory (env GOKAPI_CUSTOM_DIR, or see the Gokapi
docs), then drop in the overlay. Gokapi serves custom.css and admin.js
automatically, per request, so a content change needs no restart.
cp custom.css admin.js /path/to/gokapi/customDir/The viewer is plain static HTML; serve it and rewrite a path to it. Example for nginx in front of Gokapi:
# every /v/<id> serves the viewer; the page reads <id> from the URL itself
location ^~ /v/ {
alias /var/www/fichrs-viewer/;
try_files /index.html =404;
}
# optional: lets the watermark stamp the recipient's IP
location = /whoami { return 200 $remote_addr; add_header content-type text/plain; }cp viewer/index.html /var/www/fichrs-viewer/index.htmlThen share https://your-host/v/<gokapi-file-id>?n=<filename>&w=<recipient label>.
The viewer talks to Gokapi's own endpoints (GET /downloadFile, POST /d); the
/whoami endpoint is optional (absent it, the watermark simply omits the IP).
The viewer's
:rootmirrors thetokens.cssdark stage on purpose: it is a security-sensitive, single-file page you want to audit whole.tokens.cssremains the source of truth for the themeable surfaces.
Built in OKLCH so the ramps stay perceptually even. Two stages share one palette; the accent's role flips with the background:
| Role | Light chrome | Dark stage |
|---|---|---|
| Primary fill | glacier-deep + white text (5.5:1) |
glacier + graphite ink (8.3:1) |
| Link / accent | glacier-deep (5.3:1) |
glacier (8.3:1) |
| Caution | amber + graphite ink (7.4:1) |
same |
The trap worth naming: bright glacier on snow-white is 2.2:1, a fail. On
light surfaces the accent must be glacier-deep. The reverse holds on the dark
stage. When in doubt, run the numbers, don't eyeball them.
The two admin behaviours are load-bearing and easy to break, so they ship with a jsdom harness that drives the real DOM contract (capture-phase suppression, the no-fetch-on-limited rule, the non-image guard):
npm install
npm testfichrs themes the files. Its sibling in the same self-hosted setup is suivr, a friendly fork of Polr that adds the link-management API the stock project lacks. A view-only link is more shareable behind a short slug, and slug-aware trash needs to re-point or disable a link when a file moves. The endpoints that make that possible:
| Endpoint | Why fichrs' workflow wants it |
|---|---|
action/list |
find the slug pointing at a given file id |
action/rename |
rename a slug in place |
action/update |
re-point a slug after a file is restored to a new URL |
action/toggle |
disable a slug when its file is trashed (no dangling dead link) |
action/delete |
drop the slug when the file is purged |
Stock Polr's API can only shorten, look up, and report analytics. suivr adds the five above, with ownership checks and tests. See its API docs.
MIT. fichrs is your own work to take and adapt; it overlays
Gokapi (AGPL-3.0) through the supported
customDir seam, as a theme rather than a derivative.

