Small tools that run in a browser tab. Merging a PDF, stripping the GPS out of a photo, generating a QR code: the sort of thing you look up, use once, and close again.
Live at linu.li.
Search for "merge PDF" and every result wants the file uploaded first. For a scanned contract or a photo of somebody's passport that is an odd thing to agree to, and the sites that ask usually surround the button with three ads and a newsletter box.
Browsers have been able to do this work by themselves for years, so these do. The file you pick never goes anywhere. There are no accounts, no analytics and no cookies, and once you have opened the site it keeps working on a train with no signal.
The tools show up in English or German depending on the browser's language, and a small DE|EN switch at the bottom of every page flips between the two. The stage planner and the Bonn map stay in German because they are made for German users.
Five things do reach outside, and none of them carries your file. The EXIF tool and the Bonn map load map tiles from OpenStreetMap. The image compressor pulls its WebP and AVIF codecs from esm.sh. The timestamp tool fetches a date parser from jsDelivr the first time you ask it to read plain language. The unit converter asks two servers for exchange rates, always the whole table, so the request says nothing about what you are converting. The contact form posts to Formspree. The privacy policy names all five and says what each one receives.
- Merger combines files and lets you drag pages into order first
- Splitter cuts by page range, or explodes a file into one PDF per page
- Image extractor pulls out every image embedded in a PDF
- 2-up puts two pages on one sheet for booklets
- Compressor gets a PDF under a size limit you pick, and shows a page of the result first. It shrinks only the images unless you ask it to turn whole pages into pictures, which is also where the scan clean-up lives
- Compressor shrinks photos to WebP or AVIF with a before-and-after slider
- Bulk resizer resizes a whole batch and keeps the aspect ratio
- Social cropper crops to the ratios Instagram, Twitter and LinkedIn expect
- EXIF remover shows the location and camera data hidden in a photo before it strips it
- Favicon generator builds
.icoand.pngicons from text, an emoji or an image
- Diff checker highlights what changed between two texts, side by side or inline
- Markdown editor previews as you type and exports HTML
- Lorem generator makes filler text in Latin, tech jargon or corporate speak
- JSON tools validate, repair, minify and show a collapsible tree
- URL tools encode, decode, and strip tracking junk such as
utm_sourceandfbclid - Base64 converts text or a whole file, both directions
- Timestamp converter turns Unix epochs into dates and back, across time zones
- Hash generator gives you SHA-256, SHA-512 or MD5 of text or a file
- Password generator uses the Web Crypto API, so the randomness is real
- QR generator for links, WiFi credentials and contact cards
- Colour tools pick, convert between HEX, RGB and HSL, and check WCAG contrast
- Unit converter handles length, weight, temperature, speed, storage and currency
These still work and still answer at their old addresses, and they say so at the top of the page. They are off the front page and off the sitemap, and they do not get changes any more.
- Watermarker stamps text across every page
- SVG to PNG renders a vector file at whatever resolution you need
- Word counter counts words, characters and sentences, and estimates reading time
- List cleaner removes duplicates, sorts, shuffles and trims
- Case converter moves text between camelCase, snake_case, Title Case and the rest
- SQL formatter tidies up a query you inherited
- JWT decoder reads the header and payload and tells you whether it has expired
- Aspect ratio calculator works out the missing side
The Bühnenbild-Planer is a German planner for stage crews and the largest thing here. You build a running order of scenes, name the places a piece keeps returning to, and lay props out on a stage with wings and curtains. Out of that it prints two documents: A4 ground plans carrying nothing but the drawing, and an Umbauplan, a table of what gets struck, set up and moved between every pair of scenes. Props come with oblique illustrations as well as plan-view symbols. There is a guided setup on the first visit and an explanation behind every setting, because the people using it are not the people who built it.
The Bonn Sperrmüll map shows which streets get a bulky-waste collection on which day, from the city's open data. Its source, including the ETL pipeline that builds the geometry, is in src/sperrmuell/. The city publishes the schedule once a year, as a new file. Every Monday a workflow (.github/workflows/sperrmuell-termine.yml) looks for this year's and next year's; when one is new or revised, it rebuilds the data, puts it on main, and the usual tests and deploy follow, so the map runs on past December without anyone remembering to do it. It sits with the other tools but stays its own app, with its own build and its own service worker, so it comes up on a phone while you are standing in front of the pile with no signal.
Every tool answers at two of them: linu.li/tools/qr-creator/ and the short linu.li/qr-creator/, which redirects to the first. Nothing has to be registered for that. The server checks whether a directory of that name sits under tools/, so a new tool is reachable both ways the day it lands. The sitemap names one address per tool, the long one.
The Bonn map used to live at linu.li/sperrmuell/ and now answers under tools/ like everything else. The old address redirects, with a single exception: /sperrmuell/sw.js is rewritten internally, not redirected, to tools/sperrmuell/abgemeldet-sw.js, so it still answers 200 with a real script. That script's only job is to unregister the worker that address installed. A service worker script is never fetched through a redirect: the browser reads that as a failed update and keeps what it has, so without this the old worker would go on serving the old copy of the map out of its cache and never learn about the move. A worker's scope comes from the address it was requested from rather than from where the file sits, which is why the file can live inside the tool instead of in a directory of its own at the root.
Files handed out as plain links live at linu.li/f/<name>, and node src/publish-file.js <path>[=<name>] puts one there. They have to be committed, because the deploy mirrors the repository with --delete and would remove anything else. A link downloads the file, and the service worker leaves these addresses alone so a download never lands in the offline cache.
Plain HTML, CSS and JavaScript. No bundler, no framework, no build step: what is in the repository is what the server sends, and each tool is one self-contained index.html. The Bonn map is the exception, a Vite app.
Its source is in src/sperrmuell/, and only its data is committed, under tools/sperrmuell/data/. The rest of tools/sperrmuell/ is build output and ignored by git. The deploy workflow builds it with npm run build and npm run publish:site before uploading, and CI does the same before the tests, so both always see the map as the source describes it. The src/ folder itself never goes to the server. To see the map locally, run those two commands in src/sperrmuell/ once after npm ci.
Libraries sit in assets/vendor/ and are served from here rather than a CDN, so a tool keeps working on a day when someone else's CDN does not. pdf-lib, pdf.js and jsPDF do the PDF work. Cropper.js and piexif handle images, marked and DOMPurify render Markdown safely, jsdiff finds the differences between two texts, sql-formatter reprints queries, and JSONPath runs the queries in the JSON tool. Leaflet draws the map in the EXIF tool. Day.js does dates, JSZip packs up downloads of more than one file, and hashing goes through the Web Crypto API, with crypto-js and sha3 filling in algorithms the browser does not offer. Both fonts, Libre Baskerville and Space Grotesk, are self-hosted as well.
It has to be served over HTTP. Service workers and ES modules do not work from file://, and without the service worker a good half of the site behaves oddly.
git clone https://github.com/immineal/linu-li.git
cd linu-li
npx serve . --listen 3000Then open http://localhost:3000.
There are three test suites, and four conventions that will bite you if nobody warned you about them. Both are in CONTRIBUTING.md.
A push to main runs the tests. If they come back green, a second workflow mirrors the checkout to All-Inkl over FTP. The two used to run alongside each other, which meant a red test run never stopped anything from going live. Now the deploy waits.
It needs three secrets: FTP_SERVER, FTP_USER and FTP_PASSWORD. The rest is in .github/workflows/deploy.yml, including the step that stamps the deployed commit into the service worker. Without that stamp the worker is byte-identical after every deploy, the browser sees no reason to fetch a new one, and people keep running whatever they downloaded the first time. That is how this site spent the better part of a year serving code it had already fixed.
assets/
css/ global styles
fonts/ Libre Baskerville and Space Grotesk, self-hosted
js/ shared layout, service worker registration, update prompt,
language detection (i18n.js) and the footer language switch
vendor/ third-party libraries
manifest.json PWA manifest
f/ files handed out as plain links
tools/ one folder per tool, each a standalone page
sperrmuell/ the Bonn map: data committed, the rest built on deploy
src/
sperrmuell/ the map's source: the ETL for the data, the Vite app
tests/ the suites, see CONTRIBUTING.md
index.html the front page
sw.js service worker, has to sit at the root to cover the whole site
GNU GPL v3. That covers the code. The files in f/ are not part of it and carry their own terms wherever they are linked from.