Wszystkie projekty PROJEKT DEMONSTRACYJNY · LIFESTYLE APP

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.

Nuxt 3 Vue 3 TypeScript 5 Nuxt Content v3 Tailwind CSS Cloudflare Pages
Zobacz live Chcę coś podobnego

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

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.

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.

Porozmawiajmy →

Powiem też, czego nie warto u Ciebie robić