Verslo logika
Koncepcija „Midnight Cellar“, paletė ir šriftai, 16 animacijos sluoksnių grynu CSS, GEO optimizacija LLM, turinio pipeline'as 200+ nuotraukų, technologijų stekas.
TL;DR
BRUNO Tbilisi — modern European fusion Vake rajone. Svetainė sukurta kaip editorial žurnalas: gilus ink-juodas fonas („Midnight Cellar"), kreminis teksto popierius, fire-orange žvakės akcentas, Fraunces Variable display su italic intarpais, IBM Plex Serif body tekstui. 16 animacijos sluoksnių realizuoti grynu CSS + IntersectionObserver — be framer-motion, be animacijų bibliotekų. Multi-page architektūra: 9 pagrindinio puslapio sekcijos + 8 routes. Built for GEO: JSON-LD Restaurant + Menu + FAQPage, llms.txt su answer-first santrauka, robots.txt su eksplicitiniu Allow GPTBot, ClaudeBot, PerplexityBot, Google-Extended botams. Turinio pipeline'as surenka 200+ nuotraukų iš 5 šaltinių (Instagram, Yandex Maps, Restaurant Guru, TripAdvisor, Google Business), apdorojimas per sharp + blurhash + pHash deduplikaciją. Diegimas — VPS + nginx + Cloudflare, domenas brunotbilisi.com.
Kontekstas ir auditorija
BRUNO — restoranas Vake rajone, premium Tbilisio kvartale. Tikslinis svečias: tarptautinis turistas 30–45 metų (vizitas 2–4 dienoms) arba ekspatas, ieškantis geros vietos su biodynamic wine ir padoriu brunch. Konkurencija Vake intensyvi: dešimtys vietų 500 m spinduliu, kiekviena su savo Instagram ir Resos puslapiu.
Užsakovas norėjo svetainės-parduotuvės, ne vizitinės kortelės. Tikslas — paversti organinę paiešką į rezervacijas ir Wolt pristatymo užsakymus. Tai reiškia:
- Atrandamumas LLM asistentuose — kai turistas klausia ChatGPT „where to get good brunch in Tbilisi", BRUNO turi patekti į top-3.
- Editorial tankis — mažai teksto, daug nuotraukų, viskas ritmiška. Ne „apie mus / meniu / kontaktai", o scena-nuotaika, kaip vyno žurnale.
- Adaptyvumas visiems trims svečių tipams — užsiėmęs verslo pietūs (reikia kainos + laiko + telefono), suplanuota vakarienė (reikia viso meniu + biodinaminių vynų), brunch savaitgaliais (reikia patiekalų nuotraukų ir darbo valandų).
Vaizdo apžvalga viršuje rodo tikrą puslapio ritmą: pauzė hero (žvakė mirga viršutiniame dešiniame kampe), išryškėjančios signature patiekalų kortelės su 3D tilt, /reels su įtaisytomis vaizdo kilpomis, masonry galerija su blurhash placeholder, ilgos žurnalinės citatos reviews skiltyje.
Koncepcija: Midnight Cellar
Užeiti į vyno restoraną vakare — tai įžengti į pustamsę salę, kur žvakės ant stalų, plytinės arkos, buteliai už stiklo ir prislopintas jazz. Aš perkėliau tai į digital — tiesiogine prasme. Paletė priešinga įprastam „restoraninės" svetainės stiliui (kur paprastai baltas fonas su pasteliniais akcentais): čia gilus ink-juodas su šiltai kreminiu teksto popieriumi. Žvakės ugnis — vienintelis dalykas, kuris šviečia.
Trys koncepcijos raktai:
- Ne skeuomorfizmas, o aliuzija. Aš nepiešiu žvakės — aš statau radialinį gradientą
radial-gradient(ellipse 55% 42% at 18% 15%, rgba(201,101,31,0.28) 0%, transparent 55%)su keyframe mirgėjimo animacija (6s ease-in-out infinite). Efektas — tarsi viršuje dešinėje degtų šiltos šviesos šaltinis, ir jis šiek tiek pulsuoja. To pakanka — smegenys pačios užbaigia žvakę. - Žurnalinis ritmas, ne lendinginis. Sekcijos plačios, hairline skirtukai 1px gradientu į skaidrumą (
linear-gradient(to right, transparent, rgba(245,238,224,0.16), transparent)), daug oro. Antraštės stambios, body 16-17px, line-height 1.7. Ne „parduodantis lendingas", o ilgas atvartas žurnale. - Editorial > marketing. Jokių „Užsakyk stalą dabar pat!" huge-buttons. Rezervacijos mygtukas ramus, mini-mono caps. Svarbiausia — nuotraukos ir tekstai.
Paletė
Iš app/globals.css :root — tokenai:
| Tokenas | HEX | Vaidmuo |
|---|---|---|
--ink-base | #0D0A07 | gilus ink-juodas, body fonas — tarp juodos ir tamsiai rudos |
--ink-raised | #15110B | pakeltas sluoksnis — kortelės, hover-state |
--ink-inset | #080605 | giliau už base — fokusinės sekcijos (Hero, Reviews) |
--ink-line | rgba(245,238,224,0.08) | ploni brūkšniai — skirtukai, borders |
--ink-line-strong | rgba(245,238,224,0.16) | hairline divider gradiento centras |
--cream | #F5EEE0 | pagrindinis tekstas — šiltas popierius, ne grynas baltas |
--cream-muted | #9A8E78 | antrinis tekstas — caption, meta |
--fire | #C9651F | ugninis oranžinis — žvakė, akcentas, single CTA |
--fire-glow | rgba(201,101,31,0.12) | švelnus pašvaitis candle-glow utilitams |
--fire-glow-strong | rgba(201,101,31,0.22) | sustiprintas pašvaitis hero |
--wine | #6B1E2A | retas emocinis akcentas (brunch, atsiliepimai) |
--wine-muted | #3A1419 | tamsus vynas, giliosioms sekcijoms |
Tik dvi akcento spalvos — fire ir wine. Žalios nėra, mėlynos nėra, violetinės nėra. Tai suteikia vizualinį monolitiškumą — net be logotipo svetainė atpažįstamai „BRUNO".
Tipografija
Trys šriftai, visi per next/font/google:
const fraunces = Fraunces({
subsets: ['latin'],
variable: '--font-fraunces',
axes: ['SOFT', 'WONK', 'opsz'],
display: 'swap',
});
const plexSerif = IBM_Plex_Serif({ ..., weight: ['400'] });
const plexMono = IBM_Plex_Mono({ ..., weight: ['400', '500'] });
- Fraunces Variable display antraštėms. Aktyvios trys ašys:
SOFT(serifų minkštumas 0–100),WONK(charakteringos „keistos" formos 0–1),opsz(optinis dydis 9–144). Italic variantas suWONK 1irSOFT 100atrodo kaip rankų raižinys — ne kaip įprastas pasvirimas. Tai suteikia retą charakterį, kurį sunku pakartoti. - IBM Plex Serif body tekstui — neutralus modernus serif, weight 400. Gerai skaitomas ant tamsaus fono ilgose ištraukose.
- IBM Plex Mono meta — caption, kainos, darbo valandos, telefonas. Mono editorial kontekste — užuomina į „etiketę", į rankų darbą.
Pagrindinės utilities:
.display {
font-family: var(--font-fraunces);
font-variation-settings: 'SOFT' 30, 'WONK' 0, 'opsz' 144;
letter-spacing: -0.035em;
line-height: 0.92;
}
.display-italic {
font-style: italic;
font-variation-settings: 'SOFT' 100, 'WONK' 1, 'opsz' 144;
}
Letter-spacing -0.035em ir line-height 0.92 display antraštėms — labai tankios, tai „grūdas". Body tekste — įprasti 1.7 line-height, patogu skaityti ilgose ištraukose.
16 animacijos sluoksnių
Pagrindinis skirtumas nuo kitų portfelio kazusų — čia animacijos gryname CSS + IntersectionObserver, be framer-motion, be animacijų bibliotekų. Tai reiškia:
- Bundle mažesnis ~80 KB gzipped
- SSR veikia idealiai (niekas „nešokinėja" per hidrataciją)
- Performance — visos animacijos GPU compositor, neperskaičiuojamos kiekviename rAF
Vaizdo įraše viršuje matomi 9 iš 16. Pilnas sąrašas:
Globalūs sluoksniai
- Film grain per
body::before. SVG-noise perfeTurbulence, opacity 0.04,mix-blend-mode: screen. Nuolatinė plėvelės tekstūra — momentinis foto-spaudos pojūtis, ne „makeup'o". - Scroll progress bar — fixed top, fire-orange juostelė, plotis =
scrollY / scrollHeight. Realizuota perElement.animate({ width: '...%' })su rAF-throttle. - Scroll velocity blur — esant greitam scroll body gauna
filter: blur(0..1.8px)per--scroll-blurCSS kintamąjį. Lerp glotninimas (current += (target - current) * 0.3) pašalina trūkčiojimą. Lėto scrollinimo metu blur = 0.
Hero
- Candle flicker — radial-gradient viršutiniame dešiniame kampe su keyframe
candleFlicker6sease-in-out infinite, keičiantis opacity į 0.85 → 1.0 → 0.92 → 1.0 nereguliariais intervalais. Panašu į tikrą žvakės liepsną. - Hero parallax per JS apvalkalą
HeroParallax: fonas slenka 0.3× nuo scrollY per CSS kintamąjį--parallax-y. - Gyro-drift mobilėje: pakreipus įrenginį fonas slenka ±2-3° pagal kampą. Per
DeviceOrientationEventAPI — ten, kur iOS suteikia permission. Viena iš nedaugelio Tbilisio svetainių, naudojančių gyroscope.
Sekcijos
- Scroll-reveal per
[data-reveal]atributą irIntersectionObserver. Elementas turiopacity: 0; transform: translateY(24px), patekęs į viewport gaunadata-reveal-in="true"→ opacity ir transform į nulį, transition 600ms cubic-bezier(0.22, 1, 0.36, 1). Stagger per--reveal-delayCSS kintamąjį. - Menu category stagger + 3D tilt. Puslapyje /menu ir Signatures pagrindiniame — kortelės reaguoja į mouse-over:
transform: perspective(1000px) rotateX(...) rotateY(...)priklausomai nuo žymeklio padėties. Subtilu, bet daro korteles „gyvas". Stagger atsiradimo metu — 80ms tarp kortelių. - Gallery wave — masonry blokai gauna reveal-delay pagal diagonal-bangą:
delay = (col * 50 + row * 80)ms. Sukuria „užplūstančios bangos" efektą vietoj nuobodaus vienalaikio atsiradimo. - Amber panel breathe — Brunch & Bar sekcija (atskirame puslapyje /brunch) kvėpuoja: šiltas gintarinis švytėjimas pulsuoja 8s alternate. Negalima pamatyti pagrindinio puslapio apžvalgoje — reikia užeiti į /brunch.
- Sunrise sweep — one-shot animacija /brunch panelėje. Pirmą kartą patekus į viewport (sessionStorage flag) panelė gauna class
sunrise-sweep, ir šviesos gradientas „prariedi" iš kairės į dešinę per 1.5s. „Saulėtekio" efektas.
Touch / Mobile
- Touch ripple — pasirinktinis prisilietimo feedback: per
touchstartatsiranda apskritimas su opacity-fade-out per 600ms. Geriau nei default tap-highlight. - Swipe hints — mobiliojoje Signatures karuselėje: plona rodyklė „pravažiuoja" pirmą kartą lankant sekciją, užsimindama apie swipe.
- Music note ping — užvedus pelę ant „cocktails" sekcijos desktop'e maža music-note ikona pingina su tiny scale 1.0 → 1.15 → 1.0.
Reels
- Autoplay-on-scroll videos /reels puslapyje: 8 vaizdo kilpos su interjeru ir patiekalais. Naudojamas
IntersectionObserversu threshold 0.5 — jei vaizdas viewport centre, jis groja, kitaip pauzuojamas. Be garso. Optimizacija — preload="metadata" visiems, vaizdas atsisiunčiamas tik scrollinant prie jo.
CountUp
- CountUp About sekcijoje: reitingai 4.9★, 4.8★, 4.6★, 4.7★ iš keturių šaltinių (Google/Yandex/TripAdvisor/Restaurant Guru) — animuojami nuo 0 iki tikslinės reikšmės per 1.2s patekus į viewport. Subtilu, bet suteikia „gyvą" atspalvį skaičiams.
Visi 16 sluoksnių gerbia prefers-reduced-motion: reduce. CSS:
@media (prefers-reduced-motion: reduce) {
.candle-glow::before { animation: none; }
[data-reveal] { transition: opacity 200ms; transform: none !important; }
/* ... */
}
JS — window.matchMedia('(prefers-reduced-motion: reduce)').matches patikra kiekvieno komponento-provaiderio starte, fallback į no-op.
Puslapio architektūra
Pagrindinis (app/page.tsx) — 9 sekcijos:
- Hero (
#top) — full-bleed salės nuotrauka + display antraštė „Bruno · Vake", paantraštė „Modern European fusion", hours + tel mini-mono caps - HomeQuoteStrip — trumpa citata iš gourmet apžvalgos, fire-orange wonk-italic
- HomeTriad — trys vertikalūs blokai: „Eat", „Drink", „Visit" su nuotraukomis
- Signatures (
#signatures) — 9 signature patiekalai su preview + 3D tilt grid - Reels (
#reels) — 4 įtaisytos vaizdo kilpos su autoplay-on-scroll - Gallery (
#gallery) — 57 nuotraukos masonry, blurhash placeholder - Reviews (
#reviews) — ilgos žurnalinės citatos iš 4 šaltinių (Google/Yandex/TripAdvisor/Guru) - HomeVisitCompact — žemėlapis + adresas + valandos + telefonas + booking link
- Footer — socialiniai tinklai, jur. informacija (BBB Restogroup LLC, 402360404)
Sticky Nav viršuje — fade-in po pirmo scrollinimo, blur backdrop, semi-transparent ink. Gilaus lygio puslapiuose (pvz. /menu/pasta) pridedamas Breadcrumb. Paslėpta ScrollProgress — fire-orange juostelė pačioje viršuje.
Vidiniai puslapiai
8 routes už pagrindinio ribų:
/menu+/menu/{category}— 11 kategorijų (starters, salads, pasta, pizza, mains, desserts, brunch, brunch-extras, kids, drinks, wine). Kiekviena subkategorija turi savo puslapį su full-bleed nuotrauka ir stagger patiekalų kortelėmis./brunch— atskiras nusileidimo puslapis tikslinei auditorijai „brunch iki 16:00 kasdien". Jame gyvena sunrise sweep — lėčiausia ir emocingiausia svetainės animacija./cocktails— atskiras puslapis su CocktailsPanel + muzikinis ping efektas ant ikonos./reels— autoplay-on-scroll režimas: 8 vaizdo kilpos, scrollini ir matai viską, kas vyksta salėje./reviews— pilni 127 atsiliepimai, 4 filtrai pagal šaltinį./visit— žemėlapis + trys būdai pasiekti + parkavimas + dress code./about— ilgas tekstas apie restorano filosofiją + reitingai CountUp.
GEO: built for LLM-discoverability
Svetainė buvo daroma antrajam paieškos kontūrui — LLM asistentams (ChatGPT, Claude, Perplexity). Surinkimo metu Tbilisyje niekas tuo neužsiėmė. Kas padaryta:
public/llms.txt
Answer-first santrauka plain-text formatu: 600 žodžių su atsakymais į 8 pagrindinius klausimus („where is BRUNO", „what's the cuisine", „opening hours", „brunch menu", „cocktail program", „vegan options", „booking", „address"). Sustruktūrinta kaip Q&A. LLM botai indeksuodami gauna koncentratą — nereikia parsinti HTML.
public/robots.txt
Eksplicitinis Allow: šiems botams:
User-agent: GPTBot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: Google-Extended
Allow: /
User-agent: Applebot-Extended
Allow: /
User-agent: Bytespider
Allow: /
Daugelis restoranų svetainių Tbilisyje blokuoja šiuos botus pagal nutylėjimą (per default CMS robots.txt). Mes — priešingai.
JSON-LD
app/layout.tsx įdiegta pilna schema:
- Restaurant — name, address, geo, phone, hours, priceRange, servesCuisine, acceptsReservations
- Menu — visos 11 kategorijų su MenuSection ir MenuItem (pavadinimas, aprašymas, kaina, allergen, image)
- FAQPage — 8 klausimai su answer-first atsakymais
Semantic HTML
<article>, <section>, <dl>/<dt>/<dd>, <figure>/<figcaption> — visur, kur tinka. LLM botai geriau ištraukia esybes iš teisingai sumarkuoto turinio.
Answer-first proza About / Visit / FAQ
Ne „BRUNO — tai vieta, kur...", o „BRUNO is a modern European fusion restaurant in Vake, Tbilisi. Address: Zakaria Paliashvili 11a. Phone: +995 551 07 78 49. Hours: Sun–Thu 11:00–23:00, Fri–Sat 11:00–02:00." — plikas atsakymas pirmame sakinyje, toliau detalės.
Rezultatas
Per 2 mėnesius po publikacijos BRUNO patenka į ChatGPT ir Perplexity esant užklausoms „brunch in Tbilisi", „european fusion Vake", „truffle pasta Tbilisi". Tai organinis kanalas, kurio nenupirksi ir kuris nepriklauso nuo mokamų kanalų.
Turinio pipeline'as 200+ nuotraukų
Pagrindinis restorano svetainės skausmas — nuotraukos. Užsakovas suteikė prieigą prie Instagram ir bendrą Yandex Maps aplanką. Surinkau pipeline'ą iš 5 šaltinių:
| Šaltinis | Įrankis | Nuotraukos |
|---|---|---|
| Instagram @brunotbilisi | instaloader (be login, viešas profilis) | 242 postai |
| Yandex Maps | Playwright headless chromium → /gallery/ → orig | 65+ |
| Google Business Profile | Playwright per g.page/r/... | ~30 |
| TripAdvisor | Playwright per review URL | ~40 |
| Restaurant Guru | Playwright per profile | 45 |
Po surinkimo — vieningas pipeline scripts/process-gallery.mjs:
- Resize per
sharp: long edge max 1600px (daugiau webui nereikia). - Convert į du formatus: WebP q=82 + AVIF q=70. Front-ende —
<picture>su<source type="image/avif">+ WebP fallback. AVIF sunkesnis CPU, bet dydis mažesnis 20-30%. - Blurhash kiekvienai nuotraukai — 32 simbolių placeholder, renderinamas iš karto į
<canvas>kol kraunamas galutinis vaizdas. Jokių „šokimų" layout, jokių grey rectangles. - pHash deduplikacija — tos pačios nuotraukos yra IG ir Yandex (restorano foto aplankas). pHash distance < 5 — laikomas dublikatu, paliekama tik geriausia pagal raišką.
- Manifest —
public/gallery/manifest.jsonsu metaduomenimis:[{ "src": "/gallery/2025-03-15_001.webp", "srcAvif": "/gallery/2025-03-15_001.avif", "blurhash": "L9AS}j%M00%M~q%M00%M", "width": 1600, "height": 2000, "caption": "...", "date": "2025-03-15", "source": "instagram", "tags": ["food", "interior", "drinks"] }] - Curation
manifest.curated.jsonfaile — hero kandidatų atranka (nuotraukos su luminosity ~0.48 — ne per tamsios, ne pereksponuotos), ir 3 subsekcijos pagrindinei galerijai: „interior", „food", „drinks".
Galutinė galerija — 57 nuotraukos iš ~250 surinktų, organizuotos į tris ištisus skyrius.
Stekas ir kodėl būtent toks
| Technologija | Kodėl |
|---|---|
| Next.js 14 | App Router. SSR-ready (dinaminiam Open Graph), bet statiniai puslapiai kešuojami. Image optimization per next/image |
| TypeScript | strict-mode, types restaurant data, manifest, gallery items |
| Tailwind CSS | dizaino tokenai per :root CSS kintamuosius, utility klasės. Bundle ~12 KB po purge |
| next/font/google | Fraunces Variable (axes: SOFT, WONK, opsz) + IBM Plex Serif + IBM Plex Mono — visi subset'inami, jokio FOUT |
| CSS animations + IntersectionObserver | Visi 16 sluoksnių grynu CSS, be framer-motion. Bundle mažesnis ~80 KB gzipped, performance — visos animacijos GPU compositor |
| sharp | image processing, ~5x greitesnis nei ImageMagick |
| blurhash | 32-char placeholder be layout shift |
| VPS + nginx + Cloudflare | self-hosted, jokių Vercel-tier apribojimų. Cloudflare Polish + WebP/AVIF auto-conversion |
Kas buvo sudėtinga
1. ScrollVelocityBlur be jankų
Tiesioginis sprendimas — kiekvieno scroll-event metu atnaujinti --scroll-blur. Tai sukelia jank greitai scrollinant — kadras praleidžiamas. Sprendimas:
useScrollEmitterhook —requestAnimationFramethunk, atsisakomas- Lerp glotninimas:
current += (target - current) * 0.3— net jei scroll-event truktelėjo, blur juda sklandžiai - Threshold:
currentBlur < 0.04 → 0— atkerta smulkmenas
DevTools FPS niekada nenukrenta žemiau 58.
2. Sunrise sweep lygiai vieną kartą
Užsakovas: „sunrise sweep — tai emocija, antrąkart pamatyta ji nusibosta". Sprendimas:
- Pirmą peržiūrą viewport'e — IntersectionObserver triggerina class
sunrise-sweep sessionStorage.setItem('bruno.sunrise.seen', '1')— sesijos flag- Pakartotinai užėjus į /brunch toje pačioje sesijoje — animacija nepaleidžiama
- Tarp sesijų (nauja kortelė, nauja diena) — vėl veikia
3. Gyroscope iOS — permission flow
DeviceOrientationEvent.requestPermission() prieinamas tik po user-gesture (mygtuko paspaudimo). iOS pre-13 permission nereikia, iOS 13+ reikalingas eksplicitinis užklausimas. Realizuota per onClick ant mini-floating ikonos „Įjungti paralaksą" kampe — paspaudus iškviečiama requestPermission(), po approve gyro-drift aktyvuojasi. Android — be jokio permission, gyro veikia iš karto.
4. Image pipeline su deduplication
pHash biblioteka veikia tik browseryje (Canvas API). Sprendimas — node-port pHash-image + sharp paruošimui. Laikas — ~20s 250 nuotraukoms VPS. Cache .scrape-cache/phash/ — pakartotiniai paleidimai apdoroja tik naujas nuotraukas.
5. JSON-LD Menu schema dydžio 11 kategorijų × N patiekalų
Restaurant Schema reikalauja Menu subschemos su MenuSection ir MenuItem. BRUNO — 11 kategorijų, 80+ patiekalų. Generuoju schema iš data/restaurant.ts runtime app/layout.tsx faile. JSON-LD dydis — ~30 KB unminified. Minifikacija per JSON.stringify be spaces — 18 KB. Įterpiama per <script type="application/ld+json" dangerouslySetInnerHTML>.
6. Editorial design mobilėje
Editorial svetainė mobilėje praranda 80% emocijos — mastelis kitas, ritmas griūva. Sprendimai:
- Display antraštės fluid per
clamp(2.5rem, 8vw, 6rem)— iPhone 13 Hero h1 — 64px, iPad — 96px, 4K — 128px - Hairline skirtukai pakeisti į
1px solid rgba(245,238,224,0.10)— mobilėje subtlesni - Padding y-direction sumažintas nuo
py-32ikipy-16 - 3D tilt išjungiamas mobilėje —
@media (hover: none) { .tilt-card:hover { transform: none; } }— nes nėra žymeklio
Diegimas
VPS, nginx, Let's Encrypt, Cloudflare — same setup, kaip ir kiti portfelio projektai. nginx konfigūracija standartinė, išskyrus vieną detalę:
# Long-term cache for hashed assets
location ~* \.(webp|avif|woff2|js|css)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Short cache for HTML (revalidate often)
location / {
expires 5m;
add_header Cache-Control "public, must-revalidate";
}
Cloudflare Polish — auto-WebP / AVIF bet kuriam įkeliamam vaizdui, riebus Brotli, DDoS protection. Hostingo kaina — $0/mėn (free Cloudflare + kapeikinis VPS).
Rezultatas
Svetainė išleista ir veikia brunotbilisi.com. Per pirmus du mėnesius po paleidimo:
- Lighthouse mobile: Performance 96, Accessibility 100, Best Practices 100, SEO 100
- ChatGPT ir Perplexity ranguoja BRUNO top-3 pagal pagrindines angliškas užklausas apie brunch ir fine dining Tbilisyje
- Rezervacijų konversija per svetainę — vienintelė metrika, kurią užsakovas dalinasi — išaugo ~40% lyginant su ankstesne svetaine (Squarespace template)
Stekas pasirinktas maksimaliam ilgaamžiškumui ir kontrolei: Next.js 14 — stabilus, ne bleeding-edge; švarios CSS animacijos — veiks po 5 metų net jei subliuks framer-motion ir Tailwind; turinys data/restaurant.ts — pataisyti telefoną ar patiekalo kainą užtrunka 30 sekundžių.