Romanize
Romanize is een converter en naslagwerk voor Romeinse cijfers, door ons bedacht, gebouwd en onderhouden als een van onze organische tool-sites. De site is statisch gegenereerd en rekent client-side: er is geen backend en geen database, en het rekenwerk van de converter en de tools gebeurt op het apparaat van de bezoeker zelf.
Bekijk live →
De uitdaging
De vraag achter deze niche is klein, constant en goed af te bakenen: iemand wil weten wat 2026 in Romeinse cijfers is, wat LXXXIV betekent, of hoe je een datum netjes op een plaquette zet. Zulke bezoekers komen voor één antwoord en zijn snel weer weg, wat de lat legt bij snelheid en correctheid in plaats van bij verblijfsduur. De moeilijkheid zit niet in de rekenkunde, die past in een handvol regels, maar in de vraag hoe je rond zo'n kort antwoord een site bouwt die honderden zoekopdrachten afzonderlijk bedient zonder in leeggelopen sjabloonpagina's te vervallen.
Onze aanpak
We hebben de rekenkern als pure, dependency-vrije TypeScript geschreven, zodat dezelfde functie het antwoord berekent tijdens de build en in de browser van de bezoeker. Daaromheen ligt een eigen designsysteem, MODERN ANTIQUITY, zonder CSS-framework en zonder template, plus een datalaag die bepaalt welke nummers en numeralen een eigen pagina verdienen. Sitemap, statische routes en interne links lezen uit diezelfde datasets, waardoor een pagina die aan de dataset wordt toegevoegd op alle drie de plekken tegelijk verschijnt.
Het resultaat
Er staat een site van 448 statische pagina's: 324 nummerpagina's, 102 numeraalpagina's, vier reeks-hubs en achttien met de hand geschreven pagina's, met gestructureerde data en een canonical op de routes. Naast de site genereert een prebuild-script 4.000 JSON-bestanden (3.999 genummerd plus een index) die samen een gratis, volledig statische API vormen, en levert de repo een zelfstandige iframe-widget in vanilla JavaScript. Drie controlescripts bewaken de kwaliteit: crawl.mjs controleert het linkgraaf op wezen en dode links, check-schema.mjs valideert de JSON-LD in de gebouwde HTML, en contrast-check.mjs sluit met een foutcode af zodra een kleurpaar onder de WCAG-norm zakt.
Resultaat
448 statische pagina's, 4.000 API-bestanden, 3 runtime-dependencies
Waarom uitgerekend Romeinse cijfers
De niche is bewust gekozen om wat er níet in zit. Er is geen account nodig, geen abonnement, geen doorlopende inhoudelijke redactie: de regels van het systeem liggen al eeuwen vast. Wat overblijft is een vraag die in honderden kleine varianten terugkomt. Wat is 2026 in Romeinse cijfers, wat betekent LXXXIV, hoe schrijf je een geboortedatum als je hem wilt laten graveren.
Dat maakt het een uitgesproken kort bezoek. Iemand komt binnen met één vraag, leest één regel, en is weer weg. Dat is geen probleem dat je oplost met langere teksten of met een zwaardere interface, maar met het tegenovergestelde: het antwoord moet er staan voordat de bezoeker doorheeft dat er een pagina geladen is. Alles wat daartussen komt, is verlies.
Onze aanname bij de start was dat de conversie zelf triviaal is en dat het echte werk zit in de vraag welke antwoorden een eigen pagina verdienen. Iemand die "84 in roman numerals" typt en iemand die "lxxxiv" typt stellen dezelfde vraag vanaf twee kanten, en verdienen twee verschillende pagina's die naar elkaar wijzen. Die spiegeling is de ruggengraat van de hele site geworden: de nummerkant en de numeraalkant zijn los opgebouwd uit dezelfde rekenkern en linken over en weer.
Modern Antiquity, een eigen designsysteem
Het merk heet in de repo MODERN ANTIQUITY, met als uitgangspunt "carved in stone, rendered in light". Er zit geen CSS-framework onder en geen template: de visuele taal staat in één stylesheet met een set tokens. De achtergrond is travertijn (#f5f0e6), warm getint in plaats van zuiver wit, met imperial bronze (#8c5a17) als accent. Pompeii-rood (#7a2c20) hoort bij de tattoo-generator en is in de CSS afgeschermd achter één scope, zodat die ene ceremoniële functie zich onderscheidt zonder een tweede merk te worden.
De belangrijkste keuze zit in de diepte. In plaats van elementen te laten zweven boven het vlak, met een blur en een schaduw eronder, werkt Romanize met een gegraveerde diepte: een haarlijn-incisie plus licht van bovenaf, zoals een letter die in steen gehakt is. De afronding blijft minimaal, in twee stappen die de hoek nauwelijks breken, wat de repo "chiseled" noemt. Dat is geen decoratie om de decoratie, het is dezelfde fysica als het onderwerp zelf.
Typografisch staan Cinzel (inscriptionele kapitalen) boven Lora, beide self-hosted als variabele woff2 met een latin-subset, dus zonder externe font-CDN. Om te voorkomen dat een warm, laag-contrast palet stilletjes wegzakt onder de leesbaarheidsnorm, ligt er een script dat de kleurparen uit de interface tegen WCAG 2.2 legt, met 4,5:1 voor lopende tekst en 3:1 voor grote tekst en bedieningsranden. Zakt een paar door de ondergrens, dan sluit het script af met een foutcode in plaats van dat het probleem ongemerkt live gaat.
Alles gebeurt in de browser
Onder de site liggen Next.js 16 met de App Router, React 19 en TypeScript in strict-modus. De dependency-lijst is opzettelijk kaal: next, react en react-dom, en verder niets. De rekenkern is pure, dependency-vrije TypeScript die zowel tijdens de build als in de browser draait, dus de statische pagina en de live converter berekenen hun antwoord met dezelfde functie in plaats van met twee implementaties die uit elkaar kunnen groeien.
Er is geen backend en geen database. Wie de converter, de kaart, de klok of de tattoo-stencil gebruikt, stuurt daarmee niets naar een server: het rekenwerk gebeurt op het apparaat zelf. Voor de bezoeker betekent dat twee dingen tegelijk. Het is direct, want er valt niets te wachten op een server. En het is privé, want wat iemand intikt, een geboortedatum bijvoorbeeld voor een stencil, verlaat het apparaat niet. De "recente inscripties" die de site onthoudt staan in localStorage en gaan nergens heen. De enige plek waar wel iets de deur uit gaat is het contactformulier, dat een bericht naar ons eigen endpoint stuurt, en dat is precies de bedoeling van een contactformulier.
Correctheid is dwingender geregeld dan gebruikelijk. De converter controleert zijn resultaat door het terug te vertalen, waardoor niet-canonieke vormen als IIII of IL geweigerd worden in plaats van beleefd geaccepteerd. Standaard Romeinse cijfers lopen van 1 tot 3999; daarboven toont de site een bracket-vorm als (V) voor 5000, de ASCII-tegenhanger van de vinculum-overstreping, met een bovengrens waarboven de tool netjes weigert. Dat is een eerlijker antwoord dan doen alsof het getal niet bestaat.
Vindbaarheid als constructie, niet als laagje
De site telt 448 statische pagina's: 324 nummerpagina's (1 tot 100, ronde sleutelgetallen, de jaren 1900 tot 2100 en acht historische jaartallen), 102 numeraalpagina's (elk canoniek numeraal van 1 tot 100, plus D en M), vier reeks-hubs en achttien met de hand geschreven pagina's. Sitemap, generateStaticParams en de interne links lezen alle drie uit dezelfde datasets, waardoor de sitemap op datzelfde totaal uitkomt zonder dat iemand hem apart bijhoudt. De routes dragen gestructureerde data en een canonical, waarbij de homepage die laatste uit de root layout erft in plaats van de regel te herhalen.
Programmatische pagina's vervallen makkelijk in variabelen-in-een-mal. Daar is bewust tegenin gewerkt: de proselaag schrijft per categorie (kernsymbool, klein getal, jaartal, rond getal, groot getal) een andere introstructuur en weeft er echte feiten in, zoals de opbouw van het numeraal en het getal voluit. Bij de numeraalpagina's zijn zeventien numeralen handmatig van historische context voorzien, de zeven kernsymbolen plus tien numeralen met echte gebruikskleur. De rest krijgt een notitie die uit de eigen additieve of subtractieve opbouw van dat numeraal wordt afgeleid, dus geen zin waarin alleen de getallen verwisseld zijn.
De SEO-plumbing is verder ongebruikelijk streng. OG-kaarten worden via next/og gegenereerd uit één vooraf gecomponeerde SVG met uitgelijnde paden, zodat er geen fonts geparsed hoeven worden. Eén datumconstante voedt zowel de lastmod in de sitemap als het dateModified in de JSON-LD, zodat beide versheidssignalen hetzelfde zeggen en niet bij elke build resetten. En twee scripts bewaken het resultaat: crawl.mjs controleert het linkgraaf op wees-pagina's en dode interne links, check-schema.mjs leest de JSON-LD terug uit de gebouwde HTML en toetst of de verplichte velden er staan.
Waar we bewust stoppen
De interessantste beslissing in deze repo gaat over de pagina's die er níet zijn. Romanize heeft een sterke pagina over de vraag welke Romeinse cijfers vermenigvuldigen tot 35. De verleiding is dan om die te lezen als lid van een familie raadsels en er broertjes bij te maken: vermenigvuldigen tot 50, tot 100, optellen tot een getal. Dat had gekund, technisch is het een middag werk.
Het klopt alleen niet. Die vraag is geen circulerend volksraadsel maar Rule 9 van The Password Game (Neal Agarwal, 2023), waarin het getal 35 hard in de spelcode staat. Er bestaat dus geen "multiply to 50"-vraag om te beantwoorden. In dat spel raken twee regels Romeinse cijfers, Rule 7 en Rule 9, en juist die twee hebben een eigen pagina gekregen, met daarnaast één overzichtspagina die de cluster bij elkaar houdt. Drie pagina's dus, en geen vierde. De redenering staat als commentaar boven de dataset, inclusief de norm voor wie er later een regel aan toe wil voegen: er moet een aantoonbare zoekvraag zijn, niet alleen de mogelijkheid om hem te beantwoorden.
Dat is geen preutsheid maar zelfbehoud. Pagina's die keurige antwoorden geven op vragen die nauwelijks gesteld worden zijn doorway-content, en ze verdunnen de site die er wel toe doet. Door de grens in de code te documenteren in plaats van in iemands hoofd, blijft de afweging vindbaar voor wie er later bij komt, ook als dat een jaar later een ander is.
Wat er nu staat
De site draait negen tools, de converter meegerekend. Naast de converter zijn dat er acht: getal-naar-woorden, een doorzoekbare kaart van 1 tot 1000, een live klok in Romeinse cijfers, een tattoo-stencilgenerator, de Password Game-hub, een uitleggids, een insluitbare widget en een gratis JSON-API. Die API is geen dienst maar een build-artefact: een prebuild-script schrijft 4.000 JSON-bestanden weg (1 tot en met 3999, plus een index), die als statische bestanden meeliften. Geen endpoint dat kan omvallen, geen rate limit, geen sleutel. De widget is een zelfstandig HTML-bestand in vanilla JavaScript dat je via een iframe insluit, zodat er geen script van ons in de pagina van een ander terechtkomt.
De site is jong, eerste commit 3 juli 2026, en we doen hier dus geen uitspraken over verkeer. Wat er wel staat is een compleet, afgewerkt geheel dat zichzelf laat controleren: drie scripts die het linkgraaf, de gestructureerde data en het kleurcontrast nalopen en met een foutcode afsluiten als er iets wegzakt. Ze draaien als losse commando's naast de build, waar alleen de API-generator automatisch meelift, zodat een controle een bewuste handeling blijft en niet iets dat je stilzwijgend wegklikt. Onderhoud is daarmee eerder een kwestie van bewust uitbreiden dan van reparaties achteraf.
Voor wie overweegt met ons te werken is Romanize vooral een demonstratie van reikwijdte. Het merk, het designsysteem, de rekenkern, de contentstrategie, de technische SEO en de kwaliteitsbewaking komen alle zes uit hetzelfde huis, en de site laat zien dat die lagen bij ons niet los van elkaar bedacht worden. Het is bovendien ons eigen werk, wat betekent dat we de keuzes erin ook op langere termijn zelf dragen.
Klaar om jouw merk te laten groeien?
Vertel ons over je project. We denken graag met je mee, vrijblijvend.
Vrijblijvend. Je hoort binnen één werkdag van Jamey.