- packs sorted by ascending rouble price;
- values grouped hints-only -> no-ads-only -> no-ads+hints -> tournament
(the tournament group is empty until such products become sellable),
ascending chip price within each group;
- price columns right-aligned (GFM "---:" separators);
- tables span the full content width; the name column shrinks to its content
and never wraps;
- muted-but-visible cell borders on the dark theme, where the section rule
colour blends into the background.
Robokassa moderation requires the public offer to list every digital good with
its price. Move /offer/ off the static landing container to the render sidecar:
it splices the live catalog price list (§4.4) into the owner-edited
ui/legal/offer_ru.md and renders it with the shared ui/src/lib/offer.ts — one
renderer, no drift, always matching the current catalog with no redeploy.
- backend: /api/v1/internal/offer/pricing (internal, off the edge allow-list)
projects the active catalog into two markdown tables — chip packs priced per
rail (roubles / VK votes / Telegram Stars) and chip-priced values — through
payments.Money so no float reaches the page. Cached in memory: warmed at boot,
marked stale on every catalog mutation, so a served render issues no query.
- renderer: GET /offer/ fetches the tables and substitutes them at the
<#pricing_template#> marker, then renders; offer_ru.md is baked into the image
and marked is bundled from ui. GET /offer -> 301. Only /offer/ is edge-exposed.
- caddy: route /offer/ to the sidecar; drop the now-dead landing /offer/
handlers and the vite emit-offer plugin.
- offer: fill §4.3 (the chip-payment wording) and drop the in-page back link.
- landing footer: a feedback link (the offer's Telegram contact) beside the
offer link.
- docs (ARCHITECTURE, FUNCTIONAL +_ru, renderer README), CI /offer/ probe,
unit + integration + node tests.
The offer is the legal document a purchase accepts, needed before real-money
intake goes live. Convert the source PDF to an editable ui/legal/offer_ru.md and
render it to a standalone static dist/offer/index.html at build (a vite
emit-offer plugin using marked); the landing container serves it at /offer/,
with a bare /offer redirecting in. Add a small centered "Публичная оферта"
footer link on the landing (ru/en) and a CI probe asserting /offer/ serves the
page rather than silently falling through to the landing shell.