Generator ofert PDF w 30 minut z Claude AI - tutorial krok po kroku
Proposify kosztuje 140 zł miesięcznie. PandaDoc - około 80 zł za użytkownika. Fakturownia w pakietach wyższych - 39-79 zł. Każde z tych narzędzi sprzedaje to samo: szablon oferty z polami do wypełnienia i przycisk „Pobierz PDF”.
Zbudujesz dokładnie to samo. W 30 minut. Z Claude AI. Zero subskrypcji.
Za pół godziny będziesz mieć profesjonalny generator ofert handlowych. Z polskimi znakami, tabelą pozycji z dynamiczną kalkulacją VAT, kwotą słownie po polsku, logo Twojej firmy, historią ofert i eksportem do PDF. Jeden plik HTML. Otwierasz w przeglądarce. Działa.

→ Zobacz gotowe narzędzie - efekt finalny jest już online. Przeklikaj, wygeneruj testową ofertę PDF, zobacz jak to działa. Potem wróć i zbuduj własną wersję.
To nie jest artykuł o tym, jak zaoszczędzić 1 680 zł rocznie na Proposify (chociaż to efekt uboczny). To jest artykuł o tym, jak z Claude AI zbudować własne narzędzia biznesowe zamiast płacić miesięcznie za szablony.
TL;DR: 4 rzeczy, które wyniesiesz z tego artykułu:
- Działający generator ofert PDF - dane firmy, klient, pozycje z VAT, logo, historia
- 6 gotowych promptów CRISP - kopiujesz, wklejasz do Claude, dostajesz kod
- Polskie znaki w PDF bez tricków - podejście html2canvas robotnicze, nie hakerskie
- Wzorzec pracy z AI nad realnym narzędziem biznesowym - powtórzysz przy fakturach, umowach, raportach
Poziom 2 (średniozaawansowany) | Seria „Zbuduj to z AI” | 6 kroków | 6 promptów CRISP | 1 plik HTML | ~30 minut
Co zbudujemy
| Funkcja | Krok |
|---|---|
| Layout oferty w proporcjach A4 z live preview | #1 |
| Dane sprzedawcy, nabywcy, numeracja oferty (walidacja NIP) | #2 |
| Tabela pozycji z automatyczną kalkulacją VAT + kwota słownie | #3 |
| Generator PDF z polskimi znakami (html2canvas + jsPDF) | #4 |
| Upload logo firmy z auto-resize | #5 |
| Historia ofert + eksport/import JSON | #6 |
Stack: Jeden plik HTML. Dwie biblioteki z CDN (html2canvas 1.4.1, jsPDF 2.5.1). Reszta to czyste vanilla JavaScript + localStorage.
Zobacz gotowy generator ofert w akcji - to jest finalny efekt, który zbudujesz przechodząc przez 6 kroków tego tutoriala. Otwórz w nowej karcie, przeklikaj - zobacz jak wygląda pełna wersja. Potem wróć i buduj własny.
Krok 0: Przygotowanie (1 minuta)
Nie potrzebujesz żadnego klucza API. Nie potrzebujesz rejestracji. Jedyne biblioteki zewnętrzne (html2canvas i jsPDF) ładują się z CDN po pierwszym otwarciu - potem aplikacja może działać offline.
Czego potrzebujesz
- Przeglądarka - Chrome, Firefox, Edge, Safari (cokolwiek z ostatnich 3 lat)
- Edytor tekstu - Notatnik, VS Code, cokolwiek z podświetlaniem składni
- Claude - darmowe konto na claude.ai. Dla tego tutoriala wystarczy model Sonnet.
Przygotuj plik
Utwórz nowy plik tekstowy. Nazwij go oferta.html. To będzie jedyny plik, nad którym pracujemy.
Krok 1: Layout oferty A4 (Prompt #1)
Stan aplikacji: Pusty plik oferta.html. Zaczynamy od zera.
Czas na ten krok: ~5 minut
Pierwszy krok to fundament wizualny. Oferta handlowa w Polsce ma konkretną strukturę: header z numerem, dwie kolumny z danymi stron, tytuł, tabela pozycji, podsumowanie, warunki, stopka. Każdy z tych elementów ma swoje miejsce.
Budujemy layout w proporcjach A4 - to będzie nasz live preview. Dokładnie to, co ostatecznie trafi do PDF.
Dlaczego ten prompt działa
Prompt używa formatu CRISP - tego samego, co w tutorialu Kanban i Pomodoro:
| Element | Co robi | W tym prompcie |
|---|---|---|
| Context | Kontekst projektu | Generator ofert w jednym pliku HTML |
| Role | Rola AI | Designer dokumentów B2B, znający polskie standardy |
| Intent | Cel | Layout dwukolumnowy, preview w proporcjach A4 |
| Scope | Ograniczenia | Kolorystyka granat, Inter, zero bibliotek |
| Precision | Format odpowiedzi | Pełny plik HTML |
Pełne wyjaśnienie metody: Inżynieria promptów - Framework CRISP
Prompt do skopiowania
Otwórz Claude i wklej:
<context>
Buduję generator ofert handlowych PDF w jednym pliku HTML (z CSS
i JavaScript w środku), w ramach serii "Zbuduj to z AI". Aplikacja
ma działać po otwarciu pliku .html w przeglądarce. Użytkownik widzi
po lewej panel konfiguracji, po prawej live preview oferty w proporcjach
A4. W kolejnych krokach dodamy PDF, logo i historię. Teraz skupiamy się
na czystym wyglądzie.
</context>
<role>
Jesteś UI/UX designerem specjalizującym się w dokumentach B2B
i profesjonalnych szablonach handlowych. Znasz standardy ofert
handlowych stosowane w Polsce.
</role>
<intent>
Stwórz szkielet generatora ofert:
1. Layout dwukolumnowy (flex): 30% panel ustawień po lewej,
70% live preview po prawej. Na mobile - stacked.
2. Preview w proporcjach A4 portrait (210mm/297mm, skalowane),
cień pod dokumentem, białe tło.
3. Sekcje w preview: header (placeholder logo + "OFERTA NR 2026/04/001"),
meta-grid 2 kolumny (Sprzedawca / Nabywca), tytuł oferty,
placeholder tabeli pozycji, blok podsumowania netto/VAT/brutto
(wyrównany do prawej), warunki handlowe, stopka z podpisem.
4. Panel ustawień ma puste kontenery na kolejne kroki.
</intent>
<scope>
- Jeden plik HTML, lang="pl", meta charset UTF-8
- Typografia: font Inter z Google Fonts, weight 400/600/700
- Kolory B2B: akcent granat #1E3A5F, tekst grafit #2C3E50,
tło #F5F7FA, bordery #E0E6ED
- Użyj realistycznych danych-zaślepek (np. "Moja Firma Sp. z o.o."
jako sprzedawca, "ABC Consulting" jako klient) zamiast "Lorem ipsum"
- Zero bibliotek - tylko czyste HTML/CSS w tym kroku
- Responsywny, komentarze w kodzie po polsku
</scope>
<precision>
Zwróć KOMPLETNY plik HTML gotowy do zapisania jako oferta.html
i otwarcia w przeglądarce. Kod czytelny, z komentarzami po polsku
przy każdej sekcji CSS.
</precision>
Co się zmieni
Claude wygeneruje plik HTML z profesjonalnym layoutem. Po lewej szary panel z pustymi kontenerami. Po prawej biały dokument w proporcjach A4 z cieniem - wygląda jak wydrukowana kartka na biurku. Skopiuj kod, wklej do oferta.html, zapisz i otwórz w przeglądarce.
Weryfikacja
- Widzisz dwukolumnowy layout: panel po lewej, dokument po prawej
- Dokument ma proporcje pionowej kartki A4 z widocznym cieniem
- Header dokumentu zawiera miejsce na logo i numer „OFERTA NR 2026/04/001”
- Sekcje Sprzedawca i Nabywca są ułożone w dwie kolumny
- Na telefonie (zmniejsz okno) wszystko układa się pionowo, pozostaje czytelne
Co się tu uczysz
- CSS aspect-ratio - proporcje A4 (210/297) to jedna linijka CSS, a nie kalkulacje w JS
- CSS Grid dla metadanych - grid-template-columns: 1fr 1fr układa dwa bloki bez żadnego floatowania czy flexboxa
- Hierarchia typograficzna - Inter w trzech wagach (400 body, 600 labels, 700 nagłówki) daje profesjonalny efekt bez użycia wielu fontów
Krok 2: Dane firmy, klienta i localStorage profilu (Prompt #2)
Stan aplikacji: Piękny, ale statyczny layout. Panel po lewej ma puste kontenery. Dane w preview są zaślepkami - nic nie wpływa na nic.
Czas na ten krok: ~5 minut
Pora podłączyć formularz do live preview. Dane sprzedawcy (czyli Twoje) wpisujesz raz - zapisują się w localStorage i pojawiają w każdej kolejnej ofercie. Dane nabywcy wpisujesz per oferta. Numer oferty generuje się automatycznie. NIP ma walidację - nie tylko długości, ale pełną sumę kontrolną.
Prompt do skopiowania
Wklej do Claude (w tej samej rozmowie - Claude pamięta kontekst):
<context>
Mam gotowy layout generatora ofert z panelem po lewej i preview A4
po prawej. Sekcje preview mają zaślepki. Pora podłączyć formularz
do live preview - dane sprzedawcy, dane klienta, numer oferty, daty.
Profil firmy ma się zapisywać w localStorage, żeby nie wpisywać go
za każdym razem.
</context>
<role>
Jesteś frontend developerem specjalizującym się w formularzach B2B
i walidacji polskich danych firmowych (NIP, adresy).
</role>
<intent>
Podłącz formularz do live preview:
1. Panel "Sprzedawca" (zwijany, domyślnie schowany jeśli profil w localStorage):
nazwa firmy, adres, NIP, email, telefon, konto bankowe.
Dane zapisuj w localStorage pod kluczem "oferta_profil_firmy"
przy każdej zmianie (debounce 400ms). Przy starcie - załaduj.
2. Panel "Nabywca": nazwa firmy, NIP, osoba kontaktowa, email.
3. Panel "Oferta": numer (auto-generowany YYYY/MM/NNN z licznikiem
w localStorage "oferta_licznik_YYYY_MM"), data wystawienia (dziś),
termin ważności (dziś + 14 dni, edytowalny), tytuł oferty.
4. Walidacja NIP: dokładnie 10 cyfr, checksum algorytmem
(wagi 6,5,7,2,3,4,5,6,7, modulo 11). Czerwona ramka gdy nieprawidłowy.
5. Live preview aktualizuje się na każdy input (event listener 'input').
Pusty input = delikatny placeholder "[Uzupełnij dane]" w preview.
</intent>
<scope>
- Zaktualizuj istniejący plik HTML
- Formatuj daty w preview po polsku: "21 kwietnia 2026"
- NIP w preview wyświetlany z myślnikami: 123-456-78-90
- Zachowaj wszystkie style z kroku 1
- Przycisk "Edytuj profil firmy" rozwija panel Sprzedawcy
</scope>
<precision>
Zwróć KOMPLETNY, zaktualizowany plik HTML. Zachowaj istniejący
layout i kolorystykę. Dodaj komentarze po polsku przy nowej logice.
</precision>
Co się zmieni
Wpisujesz nazwę firmy - pojawia się w preview. Zmieniasz NIP - widzisz go sformatowanego z myślnikami. Wpisujesz błędny NIP (np. 1234567890) - czerwona ramka, algorytm wyłapał, że suma kontrolna się nie zgadza. Po odświeżeniu strony - Twoje dane firmy już są wczytane. Nabywca resetowany, bo to zmienna rzecz.
Weryfikacja
- Wpisujesz dane w panelu - preview aktualizuje się na żywo
- Nieprawidłowy NIP (np. 1111111111) - pole dostaje czerwoną ramkę
- Odświeżasz stronę - dane Twojej firmy zostają, dane klienta nie
- Numer oferty auto-generuje się w formacie 2026/04/001
- Data ważności = data dzisiejsza + 14 dni
Jeśli checksum NIP nie działa - Claude mógł użyć uproszczonej wersji. Wklej:
„Walidacja NIP przepuszcza błędne numery. Sprawdź, czy używasz algorytmu z wagami [6,5,7,2,3,4,5,6,7] i modulo 11, a ostatnia cyfra NIP to cyfra kontrolna.”
Co się tu uczysz
- Algorytm sumy kontrolnej NIP - to nie jest tajemna magia, tylko mnożenie cyfr przez wagi i dzielenie modulo 11. Ten sam wzorzec działa dla PESEL, REGON, IBAN
- Debounce - zapisy do localStorage co 400ms zamiast co keystroke. Chroni przed zapisywaniem 50 razy na sekundę przy szybkim pisaniu
- localStorage jako profil - oddzielenie „danych trwałych” (profil firmy) od „danych sesji” (bieżąca oferta). Podstawowy wzorzec każdej aplikacji z persystencją
Krok 3: Tabela pozycji z VAT i kwotą słownie (Prompt #3)
Stan aplikacji: Dane sprzedawcy i nabywcy wpisane, numer oferty się generuje. Jednak środek oferty - tabela pozycji - wciąż jest pusty.
Czas na ten krok: ~5 minut
To jest sedno oferty handlowej. Tabela z pozycjami, cenami, VAT. Automatyczne przeliczenia netto → VAT → brutto. Podsumowanie per stawka VAT. I to, co zawsze robi wrażenie na czytelnikach ofert: kwota słownie po polsku.
„Dwa tysiące trzysta czterdzieści pięć 50/100 PLN” to wymóg formalny w wielu polskich dokumentach handlowych. Zbudujemy algorytm, który to zrobi poprawnie.
Prompt do skopiowania
<context>
Generator ofert ma wypełnione dane sprzedawcy i klienta, numer oferty
i daty. Teraz serce oferty - tabela pozycji z automatycznymi
przeliczeniami VAT. Musi obsługiwać polskie stawki VAT i wyliczać
kwotę słownie po polsku (wymaganie formalne ofert handlowych).
</context>
<role>
Jesteś frontend developerem z doświadczeniem w narzędziach
rozliczeniowych i algorytmach zamiany liczb na tekst słowny
w języku polskim.
</role>
<intent>
Zbuduj dynamiczną tabelę pozycji oferty:
1. Kolumny tabeli w preview: Lp., Nazwa pozycji, Ilość, Cena netto,
Stawka VAT, Wartość netto, Wartość VAT, Wartość brutto.
2. W panelu ustawień: mini-formularz dodawania pozycji
(nazwa, ilość, cena netto, stawka VAT jako dropdown:
23%, 8%, 5%, 0%, "zw."), przycisk "Dodaj pozycję".
3. Każda pozycja w panelu ma przyciski: edytuj (inline),
usuń (X z confirm), strzałki góra/dół do zmiany kolejności.
4. Automatyczne przeliczenia: netto = ilość × cena_netto,
vat = netto × stawka (dla "zw." = 0), brutto = netto + vat.
Wszystko zaokrąglone do 2 miejsc po przecinku (grosze).
5. Podsumowanie pod tabelą w preview:
- Suma netto, suma VAT, suma brutto (bold, granat)
- Rozbicie VAT per stawka ("VAT 23%: X zł, VAT 8%: Y zł")
- Kwota słownie po polsku
(przykład: "dwa tysiące trzysta czterdzieści pięć 50/100 PLN")
6. Pozycje zapisuj w zmiennej state (tablica obiektów), renderuj
tabelę z tej tablicy. Po każdej zmianie - re-render.
</intent>
<scope>
- Zaktualizuj istniejący plik HTML
- Funkcja zamiany liczby na słowa: obsłuż zakres 0 do 999 999 999
(jedności, dziesiątki, setki, tysiące, miliony)
- Format groszy w kwocie słownie: ZAWSZE "XX/100 PLN" (nie "XX groszy").
Przykład: 2345.50 → "dwa tysiące trzysta czterdzieści pięć 50/100 PLN"
Przykład: 2345.00 → "dwa tysiące trzysta czterdzieści pięć 00/100 PLN"
- Format liczby w preview: "1 234,56 zł" (spacja jako separator
tysięcy, przecinek jako separator dziesiętny, "zł" na końcu)
- Dodaj 2 przykładowe pozycje jako dane startowe
- Zachowaj istniejącą funkcjonalność formularza firmy/klienta
</scope>
<precision>
Zwróć KOMPLETNY, zaktualizowany plik HTML z pełną logiką tabeli,
przeliczeń i funkcją zamiany liczby na słowa. Funkcja kwotaSlownie()
w osobnym bloku z komentarzem wyjaśniającym algorytm.
</precision>
Co się zmieni
Dodajesz pozycję „Usługa konsultingowa”, ilość 10, cena netto 500, VAT 23%. W tabeli pojawia się wiersz z wszystkimi przeliczeniami. Dodajesz drugą pozycję. Dodajesz trzecią. Pod tabelą rośnie podsumowanie z rozbiciem VAT per stawka. I słownie: „sześć tysięcy sto pięćdziesiąt 00/100 PLN”.
Weryfikacja
- Dodanie pozycji pojawia się w tabeli z automatycznymi przeliczeniami
- Zmiana ilości lub ceny aktualizuje wiersz i podsumowanie
- Usunięcie pozycji (X) wymaga potwierdzenia
- Podsumowanie rozbija VAT per stawkę (osobny wiersz dla 23%, 8% itd.)
- Kwota słownie jest poprawna gramatycznie (np. „dwa tysiące” nie „dwie tysiące”)
Algorytm kwoty słownie to klasyczny przypadek „proste z wyglądu, diabelnie trudne w szczegółach”. Algorytm kwoty słownie wygląda prosto z wyglądu, ale ma dużo szczegółów gramatycznych. Polski ma 7 przypadków i 3 rodzaje - jedyny sposób, żeby to zrobić dobrze, to tablica fleksji. Claude zrobi to poprawnie dla typowych kwot.
Co się tu uczysz
- State-driven rendering - pozycje żyją w tablicy JS, tabela jest odbiciem tej tablicy. Dokładnie ten sam wzorzec co w tablicy Kanban, tylko dla innej struktury danych
- Zaokrąglenia finansowe -
Math.round(x * 100) / 100daje grosze, aletoFixed(2)daje string. Obie techniki są przydatne w różnych kontekstach - Algorytm polskiej ortografii liczebnikowej - miliony to „miliony” (2-4) lub „milionów” (5+), tysiące to „tysiące” lub „tysięcy”. Te reguły dotyczą też list produktów, faktur i umów
Utknąłeś na tym etapie? Jeśli Claude zwraca błędny VAT albo niepoprawną kwotę słownie, wklej mu cały kod i opisz dokładnie, co nie działa. W 90% przypadków poprawia za pierwszym razem. Jeśli chcesz systemowo nauczyć się pracy z Claude - darmowy Kurs Claude AI ma 38 lekcji od zera do zaawansowanego.
Krok 4: Generator PDF z polskimi znakami (Prompt #4)
Stan aplikacji: Kompletna oferta w live preview. Dane, pozycje, VAT, kwota słownie. Działa jednak tylko w przeglądarce - nie da się wysłać klientowi.
Czas na ten krok: ~6 minut
To jest najważniejszy krok technicznie. Polskie znaki w jsPDF to klasyczny ból głowy - domyślne fonty (Helvetica, Times) nie mają glifów dla ą, ę, ś, ć. Standardowe rozwiązanie: dołączyć font TTF jako base64 (to 180 KB). Inne rozwiązanie: renderować HTML do obrazu i wkleić ten obraz do PDF.
Wybieramy drugie. Jest robotnicze, nie hakerskie, i działa zawsze.
Jak działa html2canvas + jsPDF?
Trzy fazy:
- html2canvas bierze element DOM (nasze preview) i renderuje go jako canvas HTML5 (w praktyce: screenshot zachowujący CSS)
- canvas.toDataURL() zamienia canvas na obraz PNG w base64
- jsPDF.addImage() wstawia ten obraz do PDF w proporcjach A4
Polskie znaki są częścią renderowanego CSS z fontem Inter - pojawiają się w PDF jako piksele, nie jako tekst. Tradeoff: PDF jest większy (2-3 MB zamiast 100 KB), nie jest „selektowalny”. Za to zawsze wygląda identycznie jak w przeglądarce.
Prompt do skopiowania
<context>
Mam kompletną ofertę w live preview - dane, pozycje, przeliczenia,
kwota słownie. Teraz najważniejszy krok: wygenerowanie pliku PDF
z tej oferty. Chcę zachować polskie znaki (ą, ę, ś, ć, ł, ń, ó, ź, ż)
w pełnej czytelności, dlatego wybieram podejście html2canvas + jsPDF
(PDF jako obraz) zamiast natywnych fontów jsPDF.
</context>
<role>
Jesteś frontend developerem z doświadczeniem w generowaniu dokumentów
PDF z HTML, znasz ograniczenia jsPDF z polskimi znakami i typowe
problemy z html2canvas (fonty, wysokość, paginacja).
</role>
<intent>
Dodaj generowanie PDF:
1. Wczytaj biblioteki z CDN w tagu head:
- https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
- https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js
2. Przycisk "Pobierz PDF" w panelu ustawień (duży, granatowy).
3. Logika generowania (async function):
- Pokaż loader na przycisku ("Generuję..." + spinner CSS)
- Zrób html2canvas elementu preview z opcjami:
scale: 2 (retina quality), useCORS: true, backgroundColor: "#ffffff"
- Utwórz jsPDF: format 'a4', unit 'mm', orientation 'portrait'
- Wymiary A4: 210mm × 297mm, marginesy 10mm wszystkie strony
- Przeskaluj canvas do szerokości 190mm (A4 minus marginesy)
- Paginacja: oblicz liczbę stron = Math.ceil(canvasHeight_mm / pageHeight_mm).
Dla każdej strony: addImage z offsetem Y = -i * pageHeight (ujemna pozycja
przesuwa obraz w górę). Między stronami: pdf.addPage().
NIE tnij canvasu na fragmenty - używaj pełnego obrazu z ujemnym
position Y dla kolejnych stron.
- Zapisz jako obraz PNG w PDF z kompresją 'FAST'
4. Nazwa pliku: "Oferta_NUMER_NAZWA-KLIENTA.pdf" gdzie:
- NUMER: numer oferty z myślnikami zamiast ukośników
- NAZWA-KLIENTA: sanitized (polskie znaki → ASCII, spacje → _,
usuń znaki specjalne, max 40 znaków)
5. Obsługa błędów: try/catch, w razie problemu alert
"Nie udało się wygenerować PDF: [szczegóły]" i ukryj loader.
</intent>
<scope>
- Zaktualizuj istniejący plik HTML
- Loader: CSS spinner (@keyframes rotate), wyłącz przycisk podczas
generowania, żeby nie wywołać podwójnie
- Przed wywołaniem html2canvas: upewnij się, że wszystkie fonty Inter
są załadowane (document.fonts.ready)
- Funkcja sanitize dla nazwy pliku - podmień polskie litery
(ą→a, ę→e, ś→s, ć→c, ł→l, ń→n, ó→o, ź→z, ż→z, + wielkie)
- Zachowaj całą dotychczasową funkcjonalność preview
</scope>
<precision>
Zwróć KOMPLETNY, zaktualizowany plik HTML z działającym generatorem
PDF. Funkcja generujPDF() z komentarzami wyjaśniającymi paginację
i dlaczego stosujemy podejście html2canvas zamiast natywnych fontów
jsPDF. Testowałem - po wygenerowaniu plik powinien być poprawnym PDF
z zachowanymi polskimi znakami.
</precision>
Co się zmieni
Klikasz „Pobierz PDF”. Przycisk zmienia się na „Generuję...” ze spinnerem. Dwie sekundy później - plik Oferta_2026-04-001_ABC-Consulting.pdf ląduje w pobranych. Otwierasz - wygląda dokładnie jak preview. Polskie znaki bez problemu. Logo na swoim miejscu. Klient może to od razu wydrukować lub podpisać elektronicznie.
Weryfikacja
- Przycisk „Pobierz PDF” pokazuje loader podczas generowania
- Plik PDF pobiera się z poprawną nazwą (numer oferty + nazwa klienta bez polskich znaków w nazwie pliku)
- Po otwarciu PDF - polskie znaki są prawidłowe
- Układ PDF zachowuje proporcje oferty z preview
- Długa oferta (20+ pozycji) dzieli się na dwie strony
Jeśli PDF wychodzi przycięty na dole:
„Po wygenerowaniu PDF ostatnie pozycje tabeli są przycięte. Sprawdź paginację - kiedy canvas jest wyższy niż 277mm, powinien być dzielony na kolejne strony PDF przez wywołanie addImage z ujemnym offsetem Y dla każdej kolejnej strony.”
Jeśli polskie znaki są krzakami w PDF:
„Polskie znaki wychodzą jako krzaki. Sprawdź: (1) Czy przed html2canvas wywołujesz document.fonts.ready. (2) Czy canvas ma scale: 2. (3) Czy w CSS font Inter jest poprawnie załadowany z Google Fonts.”
Co się tu uczysz
- html2canvas vs natywny jsPDF - dwa podejścia do generowania PDF. html2canvas daje WYSIWYG kosztem rozmiaru pliku. Natywne fonty jsPDF dają mały plik kosztem komplikacji z polskimi znakami. Dla ofert handlowych wybór jest oczywisty
- Paginacja canvas → PDF - canvas jest jednym obrazem, PDF ma wiele stron. Trzeba ciąć wysokość canvas na fragmenty odpowiadające stronom A4
- Fontfaces i document.fonts.ready - jeśli renderujesz do canvas zanim font się załaduje, dostaniesz fallback font (Arial). Czekanie na fonts.ready to 3 linijki, które ratują wygląd dokumentu
Krok 5: Logo firmy (Prompt #5)
Stan aplikacji: PDF działa, polskie znaki są, pozycje się dzielą na strony. W headerze wciąż jednak widnieje napis „LOGO” zamiast faktycznego logo.
Czas na ten krok: ~4 minuty
Logo firmy to szczegół, który odróżnia „szablon z internetu” od „dokumentu firmowego”. Będziesz go wrzucał raz - potem automatycznie pojawi się w każdej ofercie. Automatyczny resize do 400×200 pikseli, żeby wielkie firmowe logo 4K nie generowało PDF-ów po 15 MB.
Prompt do skopiowania
<context>
Generator ofert działa, PDF się pobiera. Brakuje mi możliwości
dodania logo firmy - bez tego oferta wygląda generycznie.
Chcę upload przez input file, automatyczny resize (żeby duże
pliki nie spowalniały PDF) i zapis w localStorage.
</context>
<role>
Jesteś frontend developerem z doświadczeniem w obsłudze plików
graficznych w przeglądarce (FileReader, Canvas API, base64).
</role>
<intent>
Dodaj upload i obsługę logo:
1. W panelu "Sprzedawca" (zwijanym): sekcja "Logo firmy"
- Input type="file" z accept="image/png, image/jpeg, image/svg+xml"
- Preview aktualnego logo (thumbnail 100px)
- Przycisk "Usuń logo" (jeśli jakieś jest)
2. Po wyborze pliku:
- FileReader.readAsDataURL → base64 string
- Narysuj obraz na ukrytym canvas, przeskaluj do max 400×200px
zachowując proporcje (aspect ratio)
- Wyeksportuj zresizowany canvas do base64 PNG
- Zapisz w localStorage pod kluczem "oferta_logo_base64"
3. W preview oferty: header zastępuje placeholder logo tagiem
<img src="[base64]"> - wyrównany do lewej, max-height: 60px.
4. Walidacja: max rozmiar pliku 2 MB przed resize
(alert jeśli większy), obsługa błędu FileReader.
5. Logo musi poprawnie trafiać do wygenerowanego PDF
(jest częścią preview, html2canvas je zrenderuje).
</intent>
<scope>
- Zaktualizuj istniejący plik HTML
- Jeśli logo jest SVG: wyświetl jako base64 data:image/svg+xml
(bez resize przez canvas - SVG skaluje się wektorowo)
- Jeśli logo jest PNG/JPG: zawsze przepuść przez canvas-resize
- Po usunięciu logo: localStorage.removeItem i powrót do placeholder
"LOGO" w headerze
- Zachowaj całą dotychczasową funkcjonalność
</scope>
<precision>
Zwróć KOMPLETNY, zaktualizowany plik HTML z obsługą logo.
Funkcja resizeLogoObraz() z komentarzem o zachowaniu proporcji.
Testowałem - logo musi widnieć w preview i w wygenerowanym PDF.
</precision>
Co się zmieni
Wrzucasz swoje logo firmowe (PNG, JPG albo SVG). Preview aktualizuje się natychmiast. Pobierasz PDF - logo się pojawia. Odświeżasz stronę - logo nie znika. Dodajesz nową ofertę - logo już tam widać. Raz ustawione, służy na zawsze.
Weryfikacja
- Wrzucenie pliku logo aktualizuje preview natychmiast
- Logo pojawia się w wygenerowanym PDF
- Odświeżenie strony zachowuje logo
- Przycisk „Usuń logo” wraca do placeholder „LOGO”
- Wrzucenie pliku > 2 MB wyświetla alert o za dużym rozmiarze
Co się tu uczysz
- FileReader.readAsDataURL - standardowy sposób wczytywania pliku z inputa do stringa base64. Działa dla obrazów, ale też dla dowolnych plików
- Canvas jako narzędzie resize - rysujesz obraz na canvas z nowymi wymiarami, eksportujesz canvas z powrotem do base64. Czysta JavaScript, bez bibliotek
- Zachowanie proporcji przy resize -
Math.min(400/width, 200/height)daje współczynnik skalowania, który zmieści obraz w docelowych wymiarach bez deformacji
Krok 6: Historia ofert + eksport JSON (Prompt #6)
Stan aplikacji: Generator działa w pełni. Po wysłaniu oferty klientowi - znika. Nie ma żadnej historii, żadnej bazy, żadnego sposobu, żeby do niej wrócić za tydzień.
Czas na ten krok: ~5 minut
Ostatni krok to transformacja narzędzia w system. Każda oferta trafia do localStorage z datą, numerem, nazwą klienta, kwotą brutto. Panel „Moje oferty” pokazuje całą historię. Duplikowanie pozwala użyć starej oferty jako szablonu do nowej. Eksport JSON daje backup i możliwość przenoszenia między komputerami.
Prompt do skopiowania
<context>
Generator jest w pełni funkcjonalny - layout, dane, pozycje, PDF, logo.
Ostatni element: zapisywanie ofert do późniejszego wykorzystania
(duplikowanie, edycja, przeglądanie historii). Chcę też eksport
i import wszystkich ofert do backupu.
</context>
<role>
Jesteś frontend developerem specjalizującym się w client-side
persistence i projektowaniu prostych mini-CRM.
</role>
<intent>
Dodaj historię ofert:
1. Przycisk "Zapisz ofertę" w panelu ustawień. Po kliknięciu:
- Zbierz cały state (sprzedawca, nabywca, meta, pozycje, logo-key)
- Dodaj timestamp zapisu i unikalne id (Date.now() + losowy string)
- Wrzuć do tablicy pod kluczem localStorage "oferta_historia"
(JSON.stringify całej tablicy)
- Toast/alert "Oferta zapisana"
2. Panel "Moje oferty" (zwijany, pod przyciskiem zapisu):
- Lista zapisanych ofert: numer oferty, data, nazwa klienta,
kwota brutto (z formatowaniem polskim)
- Sortowanie: najnowsze na górze
- Przy każdej pozycji 3 akcje: "Wczytaj", "Duplikuj", "Usuń"
3. Wczytaj: załaduj state do formularza i preview (nadpisz bieżący).
Poprzedź confirm'em "Zastąpić bieżącą ofertę?" jeśli są zmiany.
4. Duplikuj: skopiuj ofertę, wygeneruj nowy numer, wczytaj do formularza.
5. Usuń: confirm, usuń z tablicy, przepisz localStorage.
6. Pod listą - dwa przyciski tekstowe:
- "Eksportuj wszystkie oferty (.json)" - pobierz plik JSON
z całą zawartością "oferta_historia"
- "Importuj oferty (.json)" - input file, dopisuje do istniejących
(nie nadpisuje) po walidacji struktury.
</intent>
<scope>
- Zaktualizuj istniejący plik HTML
- Limit localStorage (5-10 MB) - ostrzeż użytkownika gdy zapisanych
ofert jest > 50 (toast: "Rozważ eksport do backupu")
- Import JSON: try/catch, walidacja że to tablica obiektów
z wymaganymi polami (id, sprzedawca, nabywca, pozycje)
- Nazwa pliku eksportu: "oferty_backup_YYYY-MM-DD.json"
- Toast'y: proste, CSS, znikają po 2 sekundach
</scope>
<precision>
Zwróć KOMPLETNY, FINALNY plik HTML generatora ofert. To jest ostateczna
wersja - zawiera layout, dane firmy/klienta, tabelę pozycji z VAT,
generator PDF, logo i historię z eksportem/importem. Pełny, gotowy
do produkcyjnego użycia kod.
</precision>
Co się zmieni
Zapisujesz ofertę - pojawia się w panelu „Moje oferty”. Zapisujesz drugą. Piątą. Setną. Zawsze widzisz historię. Klient z poprzedniego kwartału dzwoni z pytaniem - wczytujesz jego ofertę w 2 kliknięcia. Potrzebujesz copy-paste dla nowego klienta - duplikujesz i zmieniasz tylko to, co trzeba. Przenosisz się na nowy laptop - eksportujesz JSON, importujesz na drugim komputerze.
Weryfikacja
- Zapisanie oferty pojawia się w panelu „Moje oferty” z datą i kwotą
- Wczytanie oferty wypełnia wszystkie pola i preview
- Duplikowanie tworzy nową ofertę z nowym numerem; reszta zostaje identyczna
- Usunięcie wymaga potwierdzenia i natychmiast znika z listy
- Eksport pobiera plik JSON z datą w nazwie
- Import dopisuje (nie nadpisuje) istniejące oferty
Co się tu uczysz
- Mini-CRM w localStorage - tablica obiektów JSON + CRUD (create, read, update, delete). To samo podejście co w tablicy Kanban, tylko zamiast kart oferty
- Eksport pliku z przeglądarki -
Blob + URL.createObjectURL + ukryty link + click + removeChildto wzorzec pobierania dowolnego pliku generowanego po stronie klienta - Deduplikacja przy imporcie - sprawdzasz, czy id już istnieje w tablicy; jeśli tak, pomijasz. Chroni przed dublowaniem przy wielokrotnym imporcie tego samego backupu
Dlaczego to jest ważniejsze niż kolejny SaaS
Każdy miesiąc subskrypcji Proposify to 140 zł. Rok to 1 680 zł. 5 lat to 8 400 zł. A to jest JEDEN SaaS z 15-20, które subskrybuje typowy MŚP.
Policz swoje subskrypcje. Jeśli 3 z nich to „generator PDF / szablony / formularze” - zbudowałeś właśnie alternatywę dla jednej z nich. W 30 minut. Za 0 zł.
Nie chodzi o to, żeby wszystko zbudować samemu. Chodzi o to, żeby wiedzieć, KIEDY SaaS ma sens, a kiedy jest haraczem za problem, który rozwiązuje Claude za godzinę.
Efekt końcowy - co masz po 30 minutach
Otwórz działający generator ofert - to gotowa wersja, z którą możesz porównać swój wynik. Wgrałem ją na serwer, żeby każdy mógł przetestować przed budowaniem. Działa w 100% po stronie przeglądarki - wpisz swoje dane, dodaj pozycje, pobierz PDF.
Po przejściu 6 kroków masz plik oferta.html z:
| Funkcja | Status |
|---|---|
| Layout oferty w proporcjach A4 z live preview | Działa |
| Dane sprzedawcy w localStorage (wpisujesz raz) | Działa |
| Dane nabywcy, walidacja NIP z sumą kontrolną | Działa |
| Auto-numeracja ofert (YYYY/MM/NNN) | Działa |
| Dynamiczna tabela pozycji z kalkulacją VAT | Działa |
| Podsumowanie netto/VAT/brutto z rozbiciem stawek | Działa |
| Kwota słownie po polsku | Działa |
| Generator PDF z pełną obsługą polskich znaków | Działa |
| Paginacja PDF przy długich ofertach | Działa |
| Upload logo firmy z auto-resize | Działa |
| Historia ofert (zapis, wczytanie, duplikowanie) | Działa |
| Eksport/import JSON (backup i przenoszenie) | Działa |
| Działa offline (po pierwszym załadowaniu bibliotek) | Działa |
Jeden plik. Zero subskrypcji. Zero opłat. Otwierasz w przeglądarce i masz pełnoprawny generator ofert handlowych.
Czego się właśnie nauczyłeś
Cztery kompetencje, które pojawią się przy każdym kolejnym narzędziu biznesowym z tej serii:
1. Dokumenty PDF z polskimi znakami
Podejście html2canvas + jsPDF. Tradeoff: większy plik za pełną wierność wizualną. To samo rozwiązanie zadziała dla faktur, umów, raportów, certyfikatów. Natywne fonty jsPDF (jeśli kiedyś Ci się spodobają) to osobna ścieżka - dla CV, notatek, prostych list.
2. Formularze biznesowe z walidacją
Walidacja NIP z sumą kontrolną to ten sam wzorzec co PESEL, REGON, numer konta IBAN. Każdy polski identyfikator ma formalny algorytm sprawdzający. Claude zna je wszystkie - wystarczy o nie poprosić.
3. Mini-system persystencji w localStorage
Profil firmy (zapisany raz), oferta bieżąca (w zmiennej), historia (tablica w localStorage). Trzy warstwy stanu, każda z innym cyklem życia. Wzorzec przenośny na każdą aplikację, która potrzebuje więcej niż prosty „zapisz wszystko naraz”.
4. Biznesowa logika w czystym JS
Zaokrąglenia groszy, rozbicie VAT per stawka, kwota słownie. To nie są funkcje „zabawkowe”. Są wykorzystywane w produkcyjnych systemach B2B. Ty też możesz.
5 kierunków rozbudowy
Masz fundament. Każdy kierunek to 1-2 prompty do Claude:
1. Podpis elektroniczny (upload + umieszczenie w PDF)
„Dodaj sekcję 'Mój podpis'. Upload obrazu PNG podpisu z przezroczystym tłem, zapis w localStorage. W footerze PDF: tytuł 'Podpis sprzedawcy' + obrazek podpisu + imię i nazwisko pod spodem.”
2. Tryb oferty projektowej (wielostronicowej)
„Dodaj tryb 'Oferta projektowa' - zamiast tabeli pozycji, sekcje rozdziałów: Zrozumienie problemu, Rozwiązanie, Harmonogram, Cena, O nas. Każdy rozdział edytowalny inline. PDF paginowany rozdziałami.”
3. Szablony branżowe (gotowe presety)
„Dodaj dropdown 'Szablon branżowy' z 5 presetami: IT/software, marketing, consulting, szkolenia, hotelarstwo. Każdy preset wczytuje przykładowe pozycje i warunki dopasowane do branży.”
4. Email z ofertą (mailto: z załącznikiem)
„Dodaj przycisk 'Wyślij email' - otwiera klient pocztowy z wstępnie wypełnionym tematem ('Oferta nr X dla [nazwa klienta]') i treścią ('Dzień dobry, w załączeniu przesyłam ofertę...'). Adresat: email z pola Nabywca.”
5. Wersjonowanie ofert (historia zmian)
„Dodaj historię wersji oferty. Każdy zapis tej samej oferty nadpisuje poprzednią wersję, ale poprzednie wersje są dostępne do porównania. Różnice podświetlone w preview (co się zmieniło między v1 a v2).”
Najczęstsze problemy i rozwiązania
| Problem | Przyczyna | Rozwiązanie |
|---|---|---|
| Polskie znaki w PDF są krzakami | Font Inter się nie załadował przed html2canvas | „Przed wywołaniem html2canvas dodaj await document.fonts.ready„ |
| PDF jest przycięty na dole | Brak paginacji dla długich ofert | „Dodaj podział canvas na strony A4 jeśli wysokość > 277mm” |
| Nazwa pliku PDF ma krzaki zamiast polskich liter | Brak sanitizacji | „W nazwie pliku zamień polskie znaki na ASCII (ą→a, ę→e itd.)” |
| Dane firmy znikają po odświeżeniu | Brak zapisu do localStorage | „Po każdej zmianie inputu firmy wywołaj localStorage.setItem” |
| Walidacja NIP przepuszcza błędne numery | Uproszczony algorytm | „Sprawdź wagi [6,5,7,2,3,4,5,6,7], modulo 11, obsługa przypadku gdy reszta = 10” |
| Logo jest rozciągnięte w PDF | Brak zachowania proporcji | „Użyj min(400/szerokość, 200/wysokość) jako współczynnika skalowania” |
| Tabela pozycji ma rozjechane kolumny | Brak table-layout: fixed | „Dodaj table-layout: fixed do CSS tabeli pozycji„ |
Inny problem? Wklej treść błędu z konsoli (F12 → Console) do Claude i opisz, co się dzieje.
Dlaczego to nie jest tylko kolejny tutorial
Każda gotowa apka do ofert kosztuje pieniądze - 40 zł, 140 zł, 300 zł miesięcznie. Żadna z nich nie oferuje czegoś, czego nie da się zbudować. Wszystkie robią to samo: szablon z polami do wypełnienia i przycisk „Pobierz PDF”.
Zbudowałeś właśnie to samo narzędzie. Za 30 minut pracy i 0 zł miesięcznie. Przy okazji nauczyłeś się wzorca, który powtórzysz przy każdym kolejnym narzędziu biznesowym - fakturach uproszczonych, umowach szablonowych, zaproszeniach, certyfikatach, voucherach.
W tutorialu tablicy Kanban zbudowałeś narzędzie dla siebie. Dzisiaj zbudowałeś coś, czego używasz w pracy z klientami. To jest Poziom 2 serii - nie trudność kodu, tylko kontekst zastosowania.
Co wyniesie Twój księgowy
Jeśli masz księgowego, pokaż mu PDF z tego generatora. Powie Ci, że 90% oferty jest zgodne z wymogami formalnymi polskich dokumentów handlowych. Pozostałe 10% to specyficzne wymogi branżowe (np. oświadczenie o pochodzeniu towaru dla eksportu, klauzule dla usług objętych odwrotnym obciążeniem) - dodasz je promptem w 5 minut.
Twoi konkurenci używają Proposify, bo tak się nauczyli. Za 3 lata używaj narzędzi, które sam zbudowałeś - a wtedy SaaS będzie robił to, czego TY NIE CHCESZ budować, a nie to, co mógłbyś zbudować w pół godziny.
Co dalej
| Następny krok | Co zyskasz |
|---|---|
| Zbuduj kalkulator B2B vs UoP 2026 | Zobacz, ile tracisz na złej formie zatrudnienia - różnica roczna bywa większa niż koszt samochodu |
| Framework CRISP - inżynieria promptów | Nauczysz się pisać prompty, które dają działający kod za pierwszym razem |
| Vibe coding z Claude Code | Przejdziesz od jednego pliku HTML do realnych aplikacji z bazą danych |
| Kurs Claude AI - 38 lekcji, darmowy | 6 modułów od zera do zaawansowanego - CEO, marketing, sprzedaż, programowanie |
Jeśli jesteś nowy na blogu - Zacznij Tutaj. 3 ścieżki nauki dopasowane do Twojej roli.
Aktualność: Artykuł opublikowany 20 kwietnia 2026. Ceny konkurencji (Proposify, PandaDoc, Fakturownia) sprawdzone 18 kwietnia 2026 - zweryfikuj przed cytowaniem u klienta. Kod używa bibliotek html2canvas 1.4.1 i jsPDF 2.5.1 (LTS, brak planowanych breaking changes do 2027).
Ten artykuł to część serii „Zbuduj to z AI”. Zobacz też: Aplikacja pogodowa | Timer Pomodoro | Tablica Kanban | Tracker nawyków
Tagi
Powiązane artykuły
Co jeszcze warto przeczytać
Kalkulator B2B vs UoP 2026 - zbuduj własny z Claude AI w 30 minut
Zbuduj kalkulator porównujący UoP, B2B ryczałt, liniowy i skalę. Stawki kwiecień 2026, Chart.js, scenariusze what-if, eksport PDF. Jeden plik HTML, zero rejestracji.
Jak zbudować tablicę Kanban z AI - własne mini Trello w 30 minut
Praktyczny tutorial: budujemy tablicę Kanban (mini Trello) z Claude AI krok po kroku. 6 gotowych promptów CRISP, drag & drop, edycja kart, localStorage. Zero kodowania, jeden plik HTML.
Jak zbudować timer Pomodoro z AI - od zera do narzędzia produktywności w 20 minut
Praktyczny tutorial: budujemy timer Pomodoro z Claude AI krok po kroku. 5 gotowych promptów CRISP, alarm dźwiękowy, cykle 25/5, wykres produktywności, localStorage. Zero kodowania, jeden plik HTML.
Newsletter Strategic AI Implementation
Co tydzień jeden framework, jedno case study, zero spamu
Dołącz do listy. Dostajesz to, czego nie wrzucam na bloga: kulisy moich wdrożeń, sprawdzone prompty, błędy do uniknięcia. Wypisujesz się jednym kliknięciem.