A QR code menu that can be read on a phone, in semi-darkness, in three languages — and that sets no cookies.
A venue’s menu is not a brochure: it has prices and volumes that must be exact, changes often, and is read by people who speak three languages. We build the menu as data, generate the page from it, and check it before every publication, including that prices go down in each category and that every photo exists.
Already builtTwo digital menus in production, externally verifiable today. The first: 119 products in 21 categories, with price, volume, composition and photo, plus a short presentation text and a set of pairing recommendations for each product; the generator has 402 lines, and the pre-publication check has 233 lines and 16 blocks. The second, the sister site with the same engine: 146 products in 31 categories, a 377-line generator, a 210-line check with 14 blocks, and continuous integration regenerates the page from sources and rejects the change if the result differs from the file in the repository. Both respond 200 and neither sets any cookie — checked by me on 06.09.2026. We also say they are two works with the same engine and the same owner, not two independent works.
A scanned PDF on a QR code does not solve anything a venue has to solve. It cannot be searched in, translated, properly enlarged on a phone, and changed without reprinting the code. And the menu changes often: the kitchen removes a dish, the bar gets another brand, the price of an item goes up. The menu is a set of data that changes weekly, not a document printed once.
That is why we treat it as data. The real sources are a few structured files — products, presentation texts, pairing recommendations, translations — and a generator written in Python composes a single page from them. In one of the venues: 119 products in 21 categories, each with price, volume, composition and photo, plus 119 short texts and 119 sets of ‘what goes with it’. In the other: 146 products in 31 categories. The page is not edited directly — it is regenerated.
Translation does not stop at buttons. Romanian is the base, and Russian and English cover the categories, names, volumes, compositions, the 82 taste labels and all the presentation texts. In a city where the next table orders in another language, a menu translated halfway is more annoying than one not translated at all: it makes you think you can read everything.
The part we consider the most important and which nobody asks for in the brief: the cart, favourites list and bill splitting run in the browser, without an account and without a server, and the site places no cookie and asks nothing from a third-party domain. This is not a promise from the privacy policy — it is a rule enforced by automated checks that run before every publication and reject the change if any tracker, external resource or font from someone else has appeared.
What it covers
What changes concretely in restaurants & horeca
The menu is a set of data, and the page is generated from it
The products, presentation texts, recommendations and translations are kept in structured files; a generator written in Python — 402 lines in one venue, 377 in the other — composes a single page from them. The published page is not edited by hand; when someone does so, continuous integration catches it, because it regenerates the page and compares the result.
Each item has price, volume, composition and photo
In a bar, volume is part of the price: the same name at 40 and at 100 millilitres are two different products, and the customer who does not see the volume thinks they were told something else. The data structure keeps all four fields as mandatory, not optional, precisely so that an incomplete item does not reach the table.
Three languages, down to the last taste label
Translation covers the interface, categories, names, volumes, compositions, the 82 taste labels and all the short presentation texts, each Romanian key having its pair in Russian and English. One of the pre-publication checks fails if the menu is not complete in all three languages.
The cart, favourites and bill splitting run in the browser
No account, no server, no data sent anywhere. The client composes their order on their phone and shows it to the waiter or uses it to split the bill. The technical consequence: there is no database of what your customers have ordered, because none is created.
Zero cookies and nothing from third-party domains
Checked externally: no cookie header on either site, and the content policy of one of them allows absolutely nothing from outside. The fonts are hosted on the same domain, not with an external service. The only external addresses on the page are the links to the venue's social networks — which the visitor clicks, not the page calls.
Checks before every publication, including the boring ones
Sixteen blocks of checks in one venue, fourteen in the other: no trackers, no resources loaded automatically from third parties, self-hosted fonts, valid structured files, every photo exists, the full menu in three languages, valid structured data on the page and — the one that surprises everyone — prices go down in every category. A price entered wrongly shows up as a break in the order of the list, not at the customer's complaint.
The published page always matches the sources
With every change, continuous integration regenerates the page from the sources and rejects the change if the result differs from the file in the repository. It is the only real guarantee that what is on the customer's screen comes from the data edited by the venue, and not from a manual intervention left there three months ago.
Server logs do not keep full IP addresses
In one venue's server configuration: masking on IP addresses, identifying headers removed from the log and limited retention, with explicit reference to the principle of storage limitation. Continuous integration rejects the change if these lines disappear from the configuration — so the protection cannot be silently lost in a server change.
What a digital menu does not do
It does not take the order in the kitchen and does not confirm a reservation: for that you need a connection to the venue's operating system, which is a separate job. And we do not publish functions that promise something that must be honoured at the till — a 'happy hour' discount or a kitchen schedule do not start without the venue's written agreement, because the person paying for the promise on the screen is the person at the counter.
Traseul
How a request passes through the system.
01
We structure the menu before drawing anything
The first delivery is the menu as data: categories, items, price, volume, composition, photo. This is where the gaps also emerge — items without volume, categories that are actually two, products without a photo. A digital menu built on incomplete data looks incomplete, no matter how good the design is.
02
We translate everything, not just the interface
The categories, names, volumes, compositions, taste labels and presentation texts get their pairs in Russian and English. We deliver the complete menu in three languages and the automatic check that fails if a key remains untranslated.
03
We put the checks before the first publication
No trackers, no third-party resources, self-hosted fonts, valid files, every photo present, prices in descending order by category, valid structured data. We deliver the verification script in the repository, so the venue can run it without us as well.
04
Automatic publishing from the repository, with a copy of the previous version
Publication happens at the merge of the change, after continuous integration has regenerated the page and compared it with the sources, and after a copy of the previous version has been kept. We deliver the access details and the rollback procedure — not just the site that is up.
The path, in 5 steps
The data
What we touch, where it lives, and how long it stays
Rules differ from one industry to another. These are the ones that apply in restaurants & horeca.
Nothing is collected about the client
No account, no cookie, no trackers, no resources requested from third-party domains. The basket and favourites live in the visitor’s browser. There is no list with what someone has browsed in the menu on our side or locally — not because we deleted it, but because it is not created.
What remains in the server logs
The usual requests of a web server, with the IP address masked, without identification headers, kept for a limited period. They are written in the server configuration, checked automatically and justified by the principle of storage limitation — not left at the program’s default values.
The menu, prices, recipes and photographs belong to the venue
We build the structure, the generator and the checks. The content is theirs and changes at their decision. We do not publish any price figure as an example in our materials, not even illustratively.
What happens when the venue also wants an assistant on the page
It is possible, but it means that the page content policy must allow that exact domain and nothing else. On one of the two sites this is visible from the outside: the policy allows a single external domain, that of the assistant. The difference between “we allow one domain” and “we allow everything” is the whole discussion about tracking.
Reservation data, if it appears, changes the rules
The moment the menu gets a reservation form, names, phone number and time appear — that is, personal data, with a retention period, with a place where it ends up and with someone who sees it. Then the published policy of the venue is written as well. Until then, the menu is a page that collects nothing, and it is simpler to keep it that way if reservations are made by phone.
A case
A check that requires prices to go down in each category
The situation
In a menu with over one hundred items, the most common mistake is not design, but typing: an extra digit in a price or an item left without a photo. Both are usually discovered when the client shows them to the waiter.
What we built
Before publication, we put in place a verification script — 233 lines in one local venue, 210 in the other — which runs sixteen, respectively fourteen, blocks: that no tracker has slipped in, that the page requests nothing from a third-party domain, that fonts are hosted locally, that data files are valid and each photo exists, that the menu is complete in all three languages, that the structured data on the page is valid and that prices descend in every category. Separately, continuous integration regenerates the page from sources at each change and rejects it if the result differs from the published file.
What came out
A typing error in a price breaks the descending order and stops publication, and a missing photo never reaches the screen at the table. The published page always matches the data edited by the local venue, because otherwise it is not published.
What the case does not say
The checks catch what can be checked mechanically. A price entered wrongly, but plausible — and in order — passes; for it there is no other mechanism than someone looking at it. The documentation for one of the projects says “14 checks” where the script has 16 blocks; the correct figure is the one in the script.
Questions
What someone in restaurants & horeca asks
Can we change prices ourselves and remove a dish from the menu?
Yes. The data is edited, not the page; the page is regenerated from it. Continuous integration checks regeneration on every change and rejects the change if the published page no longer matches the sources. That way there is no scenario in which someone has corrected a price directly on the page and the change has disappeared at the next publish.
Why does it matter that you do not use cookies?
Because a menu that does not collect anything does not need a consent banner, has nothing to leak, and has nothing to explain to a controller. The check is within anyone’s reach: a single request to the site shows that no cookie header is returned. And the content policy of one of the venues does not allow absolutely any resource from outside.
Can the menu take the order?
The basket works in the browser, as a list that the customer shows the waiter or uses to split the bill. Sending the order to the kitchen requires a connection to the venue’s system and is a separate task, with compatibility checked first. We do not call a basket that goes nowhere an „online order”.
Can we add a „happy hour” or an evening offer?
It can be built, but we do not launch it without the venue’s written agreement, because it is a promise fulfilled at the till. Our rule: functions that bind the venue to the customer — discounts, kitchen hours, availability — are not published on the basis of a verbal discussion.
In how many languages and how complete?
Three: Romanian as the base, Russian and English. Complete means categories, names, volumes, compositions, flavour labels and presentation texts, not just buttons. An automatic check fails if the menu is not complete in all three — that is why it does not reach the site half translated.
How do you make sure a wrong price does not appear?
Among the checks before publishing there is one that requires prices to go down in each category. It is not a commercial rule, it is a typo detector: an item entered with one digit too many or too few breaks the order and stops publishing. The other checks handle missing photos and invalid files.
Is the site fast on a phone, in the venue, on mobile data?
The result is a single generated page, with fonts hosted on the same domain and no resources requested from third-party domains. That means fewer connections to open and nothing waiting on an external service. We do not publish speed figures measured under conditions other than yours.
Can we add an assistant that answers questions about the menu?
Yes, and then the page content policy allows exactly that domain, nothing else. On one of the two venues this can be seen from the outside. It is also worth saying the reverse: at that point the page is no longer „without anything from outside”, so the decision is taken consciously, not as a bonus.
You have two nearly identical menus in the portfolio. Are they two projects?
They are two sites with the same engine, owned by the same owner, and we say that explicitly. The declared functional difference between them is the pairing recommendations per product, which the second one does not have. We could present them as independent projects; that would be a portfolio exaggeration.