Noir · Kraków Coffee Atelier
Sklep, który dobiera produkt zamiast wyświetlać listę
To projekt demonstracyjny, nie wdrożenie u klienta. Zbudowałem go, żeby pokazać ten mechanizm w działaniu.
W skrócie
Dla kogo
Firmy z ofertą, której klient nie umie sam ocenić: kawa, wino, kosmetyki, suplementy, usługi w wielu wariantach.
Problem
Klient wchodzi na listę dwudziestu produktów, nie widzi różnicy między nimi i wychodzi bez zakupu. Opis „nuty czekoladowe, przetwarzanie washed” nie mówi mu nic.
Co to zmienia
Zamiast listy dostaje kilka pytań o to, co lubi, i wychodzi z dwiema propozycjami zamiast dwudziestu. Rezerwacja stolika i przedsprzedaż są w tym samym miejscu.
Sklep, który dobiera produkt zamiast wyświetlać listę
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.
Co z tego wynika dla Twojej firmy
Wzorzec działa wszędzie tam, gdzie klient potrzebuje pomocy w wyborze. Zamiast opisywać całą ofertę i liczyć, że sam się połapie, zadajesz trzy pytania i pokazujesz to, co do niego pasuje. Przy ofercie dłuższej niż kilkanaście pozycji to zwykle tańsza zmiana niż dopisywanie kolejnych opisów.
DLA ZAINTERESOWANYCH TECHNICZNIE Na czym to zbudowałem i dlaczego akurat tak. Kliknij, żeby rozwinąć.
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
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.
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.
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.
Twoja firma może być
moim kolejnym projektem
Opowiedz mi o swojej firmie, a powiem, który z tych mechanizmów miałby u Ciebie sens, a który byłby wydatkiem bez pokrycia.
Powiem też, czego nie warto u Ciebie robić