Wszystkie realizacje LIFESTYLE APP

Noir · Kraków Coffee Atelier

Kraków Coffee Atelier — interactive lifestyle app

Nuxt 3 Vue 3 TypeScript 5 Nuxt Content v3 Tailwind CSS Cloudflare Pages
Zobacz live Porozmawiajmy o Twoim projekcie

Kraków Coffee Atelier — interactive lifestyle app

Miejsca z segmentu specialty coffee mają specyficzny problem: ich oferta jest złożona — ziarna różnią się profilem sensorycznym, metodą przetwarzania, wysokością uprawy — a standardowy sklep lub strona-wizytówka tego nie komunikuje. Noir rozwiązuje to przez interaktywny Flavor Explorer: użytkownik kalibruje cztery osie smakowe (czekolada, cytrus, ferment, floral) i w czasie rzeczywistym dostaje posortowane rekomendacje ziaren, odzwierciedlające jego preferencje. Oprócz tego aplikacja obsługuje rezerwacje stolików, preorder paczek w trzech wariantach oraz moduł "Pochodzenie" — edytorski content oparty na plikach Markdown zarządzanych przez Nuxt Content.

Stack technologiczny

Nuxt 3 z Vue 3 Composition API

Framework zapewnia file-based routing (każda podstrona to oddzielny plik .vue w katalogu pages/), SSR/SSG gotowy pod Cloudflare Pages oraz useSeoMeta() dla każdej trasy z osobna. Całość działa bez osobnego serwera backendowego.

TypeScript 5 z rygorystycznym typowaniem domenowym

W types/index.ts zdefiniowano 7 interfejsów (CoffeeBean, Event, Booking, PreorderItem, OriginStory, Package, FlavorProfile). Kluczowe pola używają union types zamiast string — np. processing: 'Washed' | 'Natural' | 'Honey' | 'Anaerobic' | 'Anaerobic Honey'. Błąd typowy jest niemożliwy do przekazania do formularza rezerwacji.

Nuxt Content v3

Treści edytorskie (origin stories czterech regionów kawowych) przechowywane jako pliki .md z frontmatterem YAML. Pozwala na aktualizację opisów bez dotykania kodu Vue — wystarczy edycja pliku tekstowego.

Composable useFlavorFilter

Logika filtrowania ziaren wyekstrahowana do reużywalnego composable opartego na reactive() i computed(). Algorytm oblicza dystans Manhattan między profilem użytkownika a profilem każdego ziarna w 4-wymiarowej przestrzeni smakowej, filtruje wyniki z progiem diff <= 12 i sortuje rosnąco. Całość reaktywna — żadnego ręcznego watch.

Composable useReservation

Walidacja pól przez iterację po tablicy (keyof Booking)[], co eliminuje ryzyko pominięcia pola przy rozbudowie interfejsu. Wystarczy dodać klucz do interfejsu Booking — walidator automatycznie go obejmie.

Tailwind CSS z rozszerzonym design tokenem

tailwind.config.ts definiuje semantyczną paletę (espresso, dark, surface, surface2, brass, cream) zamiast surowych kolorów. Zmiana całej palety projektu wymaga edycji jednego pliku.

Najciekawsze rozwiązania techniczne

1

System animacji oparty na IntersectionObserver z CSS-only fallbackiem

Instancja IntersectionObserver z progiem threshold: 0.12 obserwuje wszystkie elementy z klasą .reveal. W momencie wejścia w viewport dodaje klasę .in, co wyzwala transition CSS (opacity + translateY z cubic-bezier(.16,1,.3,1) — krzywa spring). Elementy po wejściu są od razu unobserve-owane — zero zbędnych callbacków. Staggering przez klasy .delay-1 do .delay-5 eliminuje potrzebę biblioteki animacji.

2

Magnetyczny efekt przycisków bez zewnętrznej biblioteki

Przyciski z klasą .mag-btn rejestrują mousemove i obliczają offset kursora względem środka elementu przez getBoundingClientRect(). Przesunięcie skalowane współczynnikami 0.18 (oś X) i 0.25 (oś Y) daje subtelny efekt przyciągania zamiast dosłownego podążania. Przy mouseleave transform jest zerowany — CSS transition: transform obsługuje powrót.

3

Kursor z interpolacją liniową (lerp) przez requestAnimationFrame

Dwa elementy kursora: kropka (reaguje natychmiastowo) i ring (podąża z opóźnieniem). Ring porusza się przez pętlę requestAnimationFrame z wzorem rx += (mx - rx) * 0.12 — interpolacja liniowa z tłumieniem 0.12. Efektem jest płynne, "wlokące się" zachowanie ringa sprawiające wrażenie fizycznej inercji bez żadnej biblioteki motion.

Podsumowanie

Projekt pokazuje świadome decyzje architektoniczne: logika reusowalna trafia do composables, model danych jest typowany union typami zamiast luźnymi stringami, animacje działają przez natywne API przeglądarki (IntersectionObserver, requestAnimationFrame, CSS transitions) bez dodatkowych zależności w package.json.

Twoja firma może być
moim kolejnym projektem

Umów 30-minutową rozmowę — opowiesz mi o firmie, a ja powiem, jak zbudowałbym dla Ciebie podobne rozwiązanie.

Porozmawiajmy →

Bez prezentacji sprzedażowej, bez zobowiązań