Case study · Bakery website · Samara

ТОРТ по любви

A website for a bakery in Samara, Russia, that makes custom wedding cakes. Visitors browse the cakes, fillings and prices and send a request for a cake or a tasting — the request is saved on the server and delivered to Telegram.

Client
ТОРТ по любви bakery (roughly “A Cake for Love”), Samara
Timeline
Aug 22 – Sep 16, 2026,
37 commits
Stack
HTML, CSS, JS with no bundler · PHP · Telegram Bot API · GitHub Actions
Links
asiyatort.ru ↗
code on GitHub ↗

First screen, 1440px wide.

Phone, 393px.

Request form on a phone.

01

Who it’s for and why

The baker makes wedding cakes to a custom sketch. The site has to show real cakes and fillings, state prices plainly and lead visitors to a request — with the wedding date, venue, number of guests, filling and reference photos — so none of it has to be dragged out in chat.

In September a new requirement appeared: customers’ personal data had to be stored in Russia. So the site moved from GitHub Pages to the asiyatort.ru domain and Russian hosting, and requests are now handled by a custom PHP script.

02

What was built

  1. First screen“A cake worth seeing before you eat it”, with “Order a cake” and “Book a tasting” buttons
  2. Meet the bakerher story in her own words, with a portrait
  3. Credoa short promise about the wedding cake as the finale of the celebration
  4. Our workreal cakes instead of placeholder cards
  5. How ordering worksfour steps from the first message to the cake at the venue; the sketch is included in the price
  6. Fillingsseven flavours
  7. Pricescakes by weight with decoration included, tasting and delivery, “Add to cart” buttons
  8. Anniversary cakesan offer beyond weddings
  9. Request“Order a cake”, “Tasting” and “Delivery” tabs
  10. Reviews & gallerywedding photos and reviews in one masonry grid
  • LayoutHTML, CSS and JS with no bundler. Colours and fonts live in one token file (tokens.css): Cormorant Garamond, Jost and the handwritten Marck Script for “по любви”.
  • RequestsTwo forms — for a cake and for a tasting — with reference photos and a hidden honeypot field against spam. A server-side handler forwards them to Telegram.
  • Extra pagesCheckout from the cart, a public offer and a personal data policy.
  • SearchPrices in static HTML, schema.org markup (Bakery, OfferCatalog, WebSite), robots.txt, sitemap.xml, canonical and Open Graph tags; the site is verified in Yandex Webmaster.

03

Decisions worth a look

  • A request isn’t lost even if Telegram doesn’t respond

    Each request is saved first and only then sent to Telegram. If sending fails, the customer’s name and phone number are already stored, and the next request also retries the undelivered ones. The site’s response distinguishes three outcomes: delivered; not delivered but saved; not saved — and only in the last case is the visitor offered a fallback through the VK community.

    At first a Cloudflare Worker did this. After the move to Russian hosting, a PHP handler follows the same scheme: write to disk, then Telegram, then retry.

  • Online payment was dropped in favour of a request

    At first the site had a cart and a demo payment flow modelled on YooKassa. The server calculated the total from its own catalogue, while the browser only sent items and quantities, so the price couldn’t be tampered with in the request.

    Later, payment on the site was dropped: customers pay by bank card transfer or in cash. The “Pay” button and the client-side payment code were removed, the cart now sends a regular request the same way as the forms on the home page, and email is no longer required — it was only needed for the receipt.

  • Mobile layout: bugs you can’t see on desktop

    Blocks overflowed the right edge by 32px at every width: when the tokens were moved to a separate file, the global box-sizing reset went missing, and overflow-x: clip hid the overflow so well that the “is there horizontal scrolling” check stayed silent. It was caught in a screenshot — the form switcher was cut off on the right.

    In the same pass, input text went from 15.5 to 16px, because below that iOS Safari zooms the page when a field gets focus. Buttons, tabs and footer links got a minimum 44px tap target.

  • The consent checkbox wouldn’t tick at 393px

    The form silently refused to submit because the consent checkbox wouldn’t tick. The cause was an earlier tap-target fix: the policy link sat inside the label, its hit area had been enlarged to 30px on a 17px line and overlapped neighbouring lines — Chromium snapped the tap to the link. At 360 and 375px everything worked; it depended on how the lines wrapped.

    The link was moved out of the sentence onto its own line with its own 44px target, so the whole label belongs to the checkbox. Verified by tapping six points of the label at each width.

  • The site editor lives in the same Telegram bot

    The bot that delivers requests can also edit the site through a button menu: 40 text fields, 6 links and 14 photo slots — contacts, product names and prices, reviews, section captions. Only the owner has access.

    The original texts stay in the HTML; the bot stores only the edits on top of them, and the page applies them on load. So an unavailable server doesn’t leave empty gaps, and search engines see the real text. After the move, the edits are still stored on the old server, but form data no longer goes there.

  • Wedding photos: from 19.1 MB down to 2.4 MB

    Fourteen source photos weighed 19.1 MB, one PNG alone 5.8 MB. A PowerShell script makes two copies of each: 800px for grid tiles and 1600px for full-screen viewing, rotates frames according to EXIF and writes their dimensions to a manifest. Thanks to srcset, the browser loads only the light copies into the grid, and width and height reserve space in advance so the tiles don’t jump while loading.

  • A custom domain and deployment without manual uploads

    The site moved to asiyatort.ru and REG.RU hosting. After a push to main, GitHub Actions deploys the files over FTPS — the password isn’t sent in plain text, credentials live in repository secrets, and deployment itself is switched on by the DEPLOY_ENABLED variable. The old server’s source code, utility scripts and the demo payment never reach the hosting.

    One more detail surfaced after the move: PHP only sees multiple files under the name refs[], but the form sent refs, so reference photos weren’t arriving. The handler now accepts both and notes in the request which photos failed to upload.

04

How to check

Get in touch

Got a project?
Drop me a line.