PageSourceSearch

https://syst-m-v-kupu-k-vova-np5a.bolt.host/widget.js

js syst-m-v-kupu-k-vova-np5a.bolt.host collected 2026-10-03 03:10:58 UTC 82,114 bytes, 2,011 lines download raw bytes

1(function() {
2  'use strict';
3
4  class CoffeeBuybackWidget {
5    constructor(options) {
6      this.containerId = options.containerId;
7      this.supabaseUrl = options.supabaseUrl || 'https://your-project.supabase.co';
8      this.supabaseKey = options.supabaseKey || 'your-anon-key';
9      this.debugMode = options.debugMode || false;
10      this.supabase = null;
11      this.step = 1;
12      this.selectedBrand = null;
13      this.selectedType = null;
14      this.selectedDefect = null;
15      this.selectedCondition = null;
16      this.finalPrice = null;
17      this.sellerData = {
18        firstName: '',
19        lastName: '',
20        email: '',
21        phone: '',
22        street: '',
23        city: '',
24        postalCode: '',
25        country: '',
26        iban: '',
27        pickupDate: '',
28        gdprConsent: false
29      };
30      this.contractData = null;
31      this.contractId = null;
32      this.isLoading = false;
33      this.isDownloading = false;
34      this.isSendingEmail = false;
35      this.downloadSuccess = false;
36      this.emailSuccess = false;
37      this.errorMessage = null;
38      this.formErrors = {};
39      this.loadingStatus = '';
40      this.data = {
41        brands: [],
42        types: [],
43        defects: [],
44        conditions: [],
45        priceRules: [],
46        translations: []
47      };
48      this.language = 'sk';
49      this.currency = 'EUR';
50      this.basePrice = null;
51      this.init();
52    }
53
54    log(...args) {
55      if (this.debugMode) {
56        console.log('[CoffeeBuybackWidget]', ...args);
57      }
58    }
59
60    error(...args) {
61      if (this.debugMode) {
62        console.error('[CoffeeBuybackWidget ERROR]', ...args);
63      }
64    }
65
66    async init() {
67      this.detectLanguage();
68      await this.loadData();
69      this.render();
70      this.injectStyles();
71    }
72
73    detectLanguage() {
74      const htmlLang = document.documentElement.lang?.toLowerCase();
75      const supportedLanguages = ['sk', 'cs', 'en', 'de'];
76      if (htmlLang && supportedLanguages.includes(htmlLang.split('-')[0])) {
77        this.language = htmlLang.split('-')[0];
78      }
79    }
80
81    async loadData() {
82      this.loadingStatus = this.getUIText('loadingConnecting');
83      this.log('Starting data load...');
84
85      try {
86        const isSupabaseConfigured = this.supabaseUrl && this.supabaseKey &&
87            this.supabaseUrl !== 'https://your-project.supabase.co' &&
88            this.supabaseKey !== 'your-anon-key';
89
90        if (!isSupabaseConfigured) {
91          this.log('Supabase not configured, using fallback data');
92          this.data = this.getFallbackData();
93          return;
94        }
95
96        try {
97          this.log('Loading Supabase client...');
98          const { createClient } = await import('https://esm.sh/@supabase/supabase-js@2');
99          this.supabase = createClient(this.supabaseUrl, this.supabaseKey);
100          this.log('Supabase client initialized');
101
102          this.loadingStatus = this.getUIText('loadingData');
103          this.log('Fetching data from database...');
104
105          const [brandsRes, typesRes, defectsRes, conditionsRes, pricesRes, translationsRes] = await Promise.all([
106            this.supabase.from('brands').select('*').order('name'),
107            this.supabase.from('machine_types').select('*').order('name'),
108            this.supabase.from('defects').select('*').order('price_multiplier', { ascending: false }),
109            this.supabase.from('conditions').select('*').order('price_multiplier', { ascending: false }),
110            this.supabase.from('price_rules').select('*'),
111            this.supabase.from('translations').select('*')
112          ]);
113
114          if (brandsRes.error) {
115            this.error('Brands fetch error:', brandsRes.error);
116            throw new Error(`Chyba pri načítaní značiek: ${brandsRes.error.message}`);
117          }
118          if (typesRes.error) {
119            this.error('Types fetch error:', typesRes.error);
120            throw new Error(`Chyba pri načítaní typov: ${typesRes.error.message}`);
121          }
122          if (defectsRes.error) {
123            this.error('Defects fetch error:', defectsRes.error);
124            throw new Error(`Chyba pri načítaní závad: ${defectsRes.error.message}`);
125          }
126          if (conditionsRes.error) {
127            this.error('Conditions fetch error:', conditionsRes.error);
128            throw new Error(`Chyba pri načítaní stavov: ${conditionsRes.error.message}`);
129          }
130          if (pricesRes.error) {
131            this.error('Prices fetch error:', pricesRes.error);
132            throw new Error(`Chyba pri načítaní cien: ${pricesRes.error.message}`);
133          }
134
135          const directData = {
136            brands: brandsRes.data || [],
137            types: typesRes.data || [],
138            defects: defectsRes.data || [],
139            conditions: conditionsRes.data || [],
140            priceRules: pricesRes.data || [],
141            translations: translationsRes.data || []
142          };
143
144          this.log('Data loaded successfully:', {
145            brands: directData.brands.length,
146            types: directData.types.length,
147            defects: directData.defects.length,
148            conditions: directData.conditions.length,
149            priceRules: directData.priceRules.length,
150            translations: directData.translations.length
151          });
152
153          if (directData.brands.length === 0) {
154            this.log('No brands found in database, using fallback data');
155            this.data = this.getFallbackData();
156          } else {
157            this.data = directData;
158            this.log('Using live database data');
159          }
160        } catch (supabaseError) {
161          this.error('Supabase connection failed:', supabaseError);
162          this.log('Falling back to demo data');
163          this.data = this.getFallbackData();
164        }
165      } catch (error) {
166        this.error('Error loading data:', error);
167        this.log('Using fallback data due to error');
168        this.data = this.getFallbackData();
169      }
170
171      this.loadingStatus = '';
172    }
173
174    getFallbackData() {
175      return {
176        brands: [
177          { id: 'fallback-1', name: 'DeLonghi (Demo)' },
178          { id: 'fallback-2', name: 'Krups (Demo)' },
179          { id: 'fallback-3', name: 'Philips (Demo)' },
180          { id: 'fallback-4', name: 'Sage (Demo)' }
181        ],
182        types: [
183          { id: 'fallback-1', name: 'Automaticky espresso (Demo)', brand_id: 'fallback-1' },
184          { id: 'fallback-2', name: 'Pakovy espresso (Demo)', brand_id: 'fallback-1' },
185          { id: 'fallback-3', name: 'Kapslovy kavovar (Demo)', brand_id: 'fallback-2' },
186          { id: 'fallback-4', name: 'Filtrovy kavovar (Demo)', brand_id: 'fallback-3' }
187        ],
188        defects: [
189          { id: 'fallback-1', name: 'Ziadne - plne funkcny (Demo)', price_multiplier: 1.0 },
190          { id: 'fallback-2', name: 'Mensie opotrebovanie (Demo)', price_multiplier: 0.8 },
191          { id: 'fallback-3', name: 'Potrebuje opravu (Demo)', price_multiplier: 0.5 },
192          { id: 'fallback-4', name: 'Nefunkcny (Demo)', price_multiplier: 0.2 }
193        ],
194        conditions: [
195          { id: 'fallback-1', name: 'Vyborny (Demo)', description: 'Bez viditelneho poskodenia', price_multiplier: 1.0 },
196          { id: 'fallback-2', name: 'Dobry (Demo)', description: 'Mierne znaky pouzitia', price_multiplier: 0.85 },
197          { id: 'fallback-3', name: 'Prijatelny (Demo)', description: 'Viditelne znaky pouzitia', price_multiplier: 0.7 },
198          { id: 'fallback-4', name: 'Zly (Demo)', description: 'Vyrazne poskodenie', price_multiplier: 0.5 }
199        ],
200        priceRules: [
201          { id: 'fallback-1', brand_id: 'fallback-1', machine_type_id: 'fallback-1', base_price: 150 },
202          { id: 'fallback-2', brand_id: 'fallback-1', machine_type_id: 'fallback-2', base_price: 120 },
203          { id: 'fallback-3', brand_id: 'fallback-2', machine_type_id: 'fallback-3', base_price: 80 },
204          { id: 'fallback-4', brand_id: 'fallback-3', machine_type_id: 'fallback-4', base_price: 60 }
205        ],
206        translations: []
207      };
208    }
209
210    getTranslation(entityType, entityId, fallbackName, fallbackDescription) {
211      if (this.language === 'sk') {
212        return { name: fallbackName, description: fallbackDescription };
213      }
214      const translation = this.data.translations.find(
215        t => t.entity_type === entityType && t.entity_id === entityId && t.language === this.language
216      );
217      return {
218        name: translation?.name || fallbackName,
219        description: translation?.description || fallbackDescription
220      };
221    }
222
223    getUIText(key) {
224      const texts = {
225        sk: {
226          widgetTitle: 'Výkup Kávovarov',
227          widgetSubtitle: 'Zistite cenu vášho kávovaru',
228          selectBrand: 'Vyberte značku',
229          selectType: 'Vyberte typ',
230          selectDefect: 'Vyberte závadu',
231          selectCondition: 'Vyberte stav',
232          continue: 'Pokračovať',
233          back: 'Späť',
234          estimatedPrice: 'Odhadovaná cena',
235          finalPrice: 'Výkupná cena',
236          yourMachine: 'Váš kávovar',
237          brand: 'Značka',
238          type: 'Typ',
239          defect: 'Závada',
240          condition: 'Stav',
241          continueToForm: 'Predať kávovar za túto cenu',
242          newCalculation: 'Nový výpočet',
243          sellerInfo: 'Údaje predávajúceho',
244          sellerInfoSubtitle: 'Vyplňte prosím vaše kontaktné údaje',
245          firstName: 'Meno',
246          lastName: 'Priezvisko',
247          email: 'Email',
248          phone: 'Telefón',
249          street: 'Ulica a číslo',
250          city: 'Mesto',
251          postalCode: 'PSČ',
252          country: 'Krajina',
253          iban: 'IBAN',
254          pickupDate: 'Dátum vyzdvihnutia',
255          gdprConsent: 'Súhlasím so spracovaním osobných údajov',
256          generateContract: 'Vygenerovať zmluvu',
257          contractPreview: 'Náhľad zmluvy',
258          contractPreviewSubtitle: 'Skontrolujte si údaje pred stiahnutím',
259          downloadContract: 'Stiahnuť zmluvu (PDF)',
260          sendEmail: 'Odoslať na email',
261          contractSuccess: 'Zmluva bola úspešne stiahnutá!',
262          emailSuccess: 'Zmluva bola úspešne odoslaná na email!',
263          loading: 'Načítavam...',
264          loadingConnecting: 'Pripájam sa k databáze...',
265          loadingData: 'Načítavam dáta...',
266          generating: 'Generujem zmluvu...',
267          downloading: 'SÅ¥ahujem...',
268          sending: 'Odosielam...',
269          errorNoData: 'Chyba: Žiadne údaje o zmluve',
270          errorRequired: 'je povinné',
271          errorInvalidEmail: 'Neplatný formát emailu',
272          errorInvalidPhone: 'Neplatný formát telefónu',
273          errorInvalidPostalCode: 'Neplatné PSČ (formát: 123 45)',
274          errorInvalidIban: 'Neplatný IBAN',
275          errorGdprRequired: 'Musíte súhlasiť so spracovaním osobných údajov',
276          errorMinLength2: 'musí mať aspoň 2 znaky',
277          errorWeekdayOnly: 'Vyberte pracovný deň (Pondelok - Piatok)',
278          errorDatabaseUnavailable: 'Databáza nie je dostupná. Skúste obnoviť stránku.',
279          errorSavingContract: 'Nepodarilo sa uložiť zmluvu do databázy',
280          errorGeneratingContract: 'Nepodarilo sa vytvoriť zmluvu. Skúste to prosím znova.',
281          errorDownloadingContract: 'Chyba pri sťahovaní zmluvy. Skúste to prosím znova.',
282          errorSendingEmail: 'Nepodarilo sa odoslať email. Skúste to prosím znova.',
283          loadingCreatingContract: 'Vytváram zmluvu...',
284          loadingSavingContract: 'Ukladám do databázy...',
285          nextStepsTitle: 'Ďalšie kroky:',
286          nextStep1: 'Vytlačte zmluvu',
287          nextStep2: 'Podpíšte zmluvu na vyznačenom mieste',
288          nextStep3: 'Zabalte kávovar, kuriér ho vyzdvihne vo vami zvolený pracovný deň'
289        },
290        cs: {
291          widgetTitle: 'Výkup Kávovarů',
292          widgetSubtitle: 'Zjistěte cenu vašeho kávovaru',
293          selectBrand: 'Vyberte značku',
294          selectType: 'Vyberte typ',
295          selectDefect: 'Vyberte závadu',
296          selectCondition: 'Vyberte stav',
297          continue: 'Pokračovat',
298          back: 'Zpět',
299          estimatedPrice: 'Odhadovaná cena',
300          finalPrice: 'Výkupní cena',
301          yourMachine: 'Váš kávovar',
302          brand: 'Značka',
303          type: 'Typ',
304          defect: 'Závada',
305          condition: 'Stav',
306          continueToForm: 'Prodat kávovar za tuto cenu',
307          newCalculation: 'Nový výpočet',
308          sellerInfo: 'Údaje prodávajícího',
309          sellerInfoSubtitle: 'Vyplňte prosím vaše kontaktní údaje',
310          firstName: 'Jméno',
311          lastName: 'Příjmení',
312          email: 'Email',
313          phone: 'Telefon',
314          street: 'Ulice a číslo',
315          city: 'Město',
316          postalCode: 'PSČ',
317          country: 'Země',
318          iban: 'IBAN',
319          pickupDate: 'Datum vyzvednutí',
320          gdprConsent: 'Souhlasím se zpracováním osobních údajů',
321          generateContract: 'Vygenerovat smlouvu',
322          contractPreview: 'Náhled smlouvy',
323          contractPreviewSubtitle: 'Zkontrolujte si údaje před stažením',
324          downloadContract: 'Stáhnout smlouvu (PDF)',
325          sendEmail: 'Odeslat na email',
326          contractSuccess: 'Smlouva byla úspěšně stažena!',
327          emailSuccess: 'Smlouva byla úspěšně odeslána na email!',
328          loading: 'Načítám...',
329          loadingConnecting: 'Připojuji se k databázi...',
330          loadingData: 'Načítám data...',
331          generating: 'Generuji smlouvu...',
332          downloading: 'Stahuji...',
333          sending: 'Odesílám...',
334          errorNoData: 'Chyba: Žádné údaje o smlouvě',
335          errorRequired: 'je povinné',
336          errorInvalidEmail: 'Neplatný formát emailu',
337          errorInvalidPhone: 'Neplatný formát telefonu',
338          errorInvalidPostalCode: 'Neplatné PSČ (formát: 123 45)',
339          errorInvalidIban: 'Neplatný IBAN',
340          errorGdprRequired: 'Musíte souhlasit se zpracováním osobních údajů',
341          errorMinLength2: 'musí mít alespoň 2 znaky',
342          errorWeekdayOnly: 'Vyberte pracovní den (Pondělí - Pátek)',
343          errorDatabaseUnavailable: 'Databáze není dostupná. Zkuste obnovit stránku.',
344          errorSavingContract: 'Nepodařilo se uložit smlouvu do databáze',
345          errorGeneratingContract: 'Nepodařilo se vytvořit smlouvu. Zkuste to prosím znovu.',
346          errorDownloadingContract: 'Chyba při stahování smlouvy. Zkuste to prosím znovu.',
347          errorSendingEmail: 'Nepodařilo se odeslat email. Zkuste to prosím znovu.',
348          loadingCreatingContract: 'Vytvářím smlouvu...',
349          loadingSavingContract: 'Ukládám do databáze...',
350          nextStepsTitle: 'Další kroky:',
351          nextStep1: 'Vytiskněte smlouvu',
352          nextStep2: 'Podepište smlouvu na vyznačeném místě',
353          nextStep3: 'Zabalte kávovar, kurýr ho vyzvedne ve vámi zvolený pracovní den'
354        },
355        en: {
356          widgetTitle: 'Coffee Machine Buyback',
357          widgetSubtitle: 'Find out the price of your coffee machine',
358          selectBrand: 'Select brand',
359          selectType: 'Select type',
360          selectDefect: 'Select defect',
361          selectCondition: 'Select condition',
362          continue: 'Continue',
363          back: 'Back',
364          estimatedPrice: 'Estimated price',
365          finalPrice: 'Buyback price',
366          yourMachine: 'Your coffee machine',
367          brand: 'Brand',
368          type: 'Type',
369          defect: 'Defect',
370          condition: 'Condition',
371          continueToForm: 'Sell coffee machine for this price',
372          newCalculation: 'New calculation',
373          sellerInfo: 'Seller information',
374          sellerInfoSubtitle: 'Please fill in your contact details',
375          firstName: 'First name',
376          lastName: 'Last name',
377          email: 'Email',
378          phone: 'Phone',
379          street: 'Street and number',
380          city: 'City',
381          postalCode: 'Postal code',
382          country: 'Country',
383          iban: 'IBAN',
384          pickupDate: 'Pickup date',
385          gdprConsent: 'I agree to the processing of personal data',
386          generateContract: 'Generate contract',
387          contractPreview: 'Contract preview',
388          contractPreviewSubtitle: 'Check the details before downloading',
389          downloadContract: 'Download contract (PDF)',
390          sendEmail: 'Send by email',
391          contractSuccess: 'Contract downloaded successfully!',
392          emailSuccess: 'Contract sent by email successfully!',
393          loading: 'Loading...',
394          loadingConnecting: 'Connecting to database...',
395          loadingData: 'Loading data...',
396          generating: 'Generating contract...',
397          downloading: 'Downloading...',
398          sending: 'Sending...',
399          errorNoData: 'Error: No contract data',
400          errorRequired: 'is required',
401          errorInvalidEmail: 'Invalid email format',
402          errorInvalidPhone: 'Invalid phone format',
403          errorInvalidPostalCode: 'Invalid postal code (format: 123 45)',
404          errorInvalidIban: 'Invalid IBAN',
405          errorGdprRequired: 'You must agree to the processing of personal data',
406          errorMinLength2: 'must be at least 2 characters',
407          errorWeekdayOnly: 'Select a weekday (Monday - Friday)',
408          errorDatabaseUnavailable: 'Database is not available. Please refresh the page.',
409          errorSavingContract: 'Failed to save contract to database',
410          errorGeneratingContract: 'Failed to create contract. Please try again.',
411          errorDownloadingContract: 'Error downloading contract. Please try again.',
412          errorSendingEmail: 'Failed to send email. Please try again.',
413          loadingCreatingContract: 'Creating contract...',
414          loadingSavingContract: 'Saving to database...',
415          nextStepsTitle: 'Next steps:',
416          nextStep1: 'Print the contract',
417          nextStep2: 'Sign the contract in the designated place',
418          nextStep3: 'Pack the coffee machine, courier will pick it up on your chosen working day'
419        },
420        de: {
421          widgetTitle: 'Kaffeemaschinen Ankauf',
422          widgetSubtitle: 'Finden Sie den Preis Ihrer Kaffeemaschine heraus',
423          selectBrand: 'Marke wählen',
424          selectType: 'Typ wählen',
425          selectDefect: 'Defekt wählen',
426          selectCondition: 'Zustand wählen',
427          continue: 'Weiter',
428          back: 'Zurück',
429          estimatedPrice: 'Geschätzter Preis',
430          finalPrice: 'Ankaufspreis',
431          yourMachine: 'Ihre Kaffeemaschine',
432          brand: 'Marke',
433          type: 'Typ',
434          defect: 'Defekt',
435          condition: 'Zustand',
436          continueToForm: 'Kaffeemaschine für diesen Preis verkaufen',
437          newCalculation: 'Neue Berechnung',
438          sellerInfo: 'Verkäuferinformationen',
439          sellerInfoSubtitle: 'Bitte füllen Sie Ihre Kontaktdaten aus',
440          firstName: 'Vorname',
441          lastName: 'Nachname',
442          email: 'E-Mail',
443          phone: 'Telefon',
444          street: 'Straße und Nummer',
445          city: 'Stadt',
446          postalCode: 'Postleitzahl',
447          country: 'Land',
448          iban: 'IBAN',
449          pickupDate: 'Abholtermin',
450          gdprConsent: 'Ich stimme der Verarbeitung personenbezogener Daten zu',
451          generateContract: 'Vertrag erstellen',
452          contractPreview: 'Vertragsvorschau',
453          contractPreviewSubtitle: 'Überprüfen Sie die Details vor dem Download',
454          downloadContract: 'Vertrag herunterladen (PDF)',
455          sendEmail: 'Per E-Mail senden',
456          contractSuccess: 'Vertrag erfolgreich heruntergeladen!',
457          emailSuccess: 'Vertrag erfolgreich per E-Mail gesendet!',
458          loading: 'Laden...',
459          loadingConnecting: 'Verbindung zur Datenbank...',
460          loadingData: 'Daten werden geladen...',
461          generating: 'Vertrag wird erstellt...',
462          downloading: 'Herunterladen...',
463          sending: 'Senden...',
464          errorNoData: 'Fehler: Keine Vertragsdaten',
465          errorRequired: 'ist erforderlich',
466          errorInvalidEmail: 'Ungültiges E-Mail-Format',
467          errorInvalidPhone: 'Ungültiges Telefonformat',
468          errorInvalidPostalCode: 'Ungültige Postleitzahl (Format: 123 45)',
469          errorInvalidIban: 'Ungültige IBAN',
470          errorGdprRequired: 'Sie müssen der Verarbeitung personenbezogener Daten zustimmen',
471          errorMinLength2: 'muss mindestens 2 Zeichen lang sein',
472          errorWeekdayOnly: 'Wählen Sie einen Wochentag (Montag - Freitag)',
473          errorDatabaseUnavailable: 'Datenbank ist nicht verfügbar. Bitte aktualisieren Sie die Seite.',
474          errorSavingContract: 'Fehler beim Speichern des Vertrags in der Datenbank',
475          errorGeneratingContract: 'Vertrag konnte nicht erstellt werden. Bitte versuchen Sie es erneut.',
476          errorDownloadingContract: 'Fehler beim Herunterladen des Vertrags. Bitte versuchen Sie es erneut.',
477          errorSendingEmail: 'E-Mail konnte nicht gesendet werden. Bitte versuchen Sie es erneut.',
478          loadingCreatingContract: 'Vertrag wird erstellt...',
479          loadingSavingContract: 'Wird in Datenbank gespeichert...',
480          nextStepsTitle: 'Nächste Schritte:',
481          nextStep1: 'Drucken Sie den Vertrag aus',
482          nextStep2: 'Unterschreiben Sie den Vertrag an der markierten Stelle',
483          nextStep3: 'Verpacken Sie die Kaffeemaschine, Kurier holt sie an Ihrem gewählten Werktag ab'
484        }
485      };
486
487      return texts[this.language]?.[key] || texts['sk'][key] || key;
488    }
489
490    injectStyles() {
491      if (document.getElementById('coffee-widget-styles')) return;
492
493      const styles = `
494        .coffee-widget {
495          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
496          background: white;
497          border-radius: 8px;
498          box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
499          border: 1px solid #e5e7eb;
500          max-width: 448px;
501          margin: 0 auto;
502        }
503        .coffee-widget-header {
504          background: #0D2518;
505          color: white;
506          padding: 16px;
507          border-radius: 8px 8px 0 0;
508        }
509        .coffee-widget-title {
510          font-size: 18px;
511          font-weight: 600;
512          margin: 0;
513        }
514        .coffee-widget-subtitle {
515          font-size: 14px;
516          color: rgba(255, 255, 255, 0.7);
517          margin: 4px 0 0 0;
518        }
519        .coffee-widget-body {
520          padding: 24px;
521        }
522        .coffee-widget-progress {
523          display: flex;
524          align-items: center;
525          justify-content: space-between;
526          margin-bottom: 24px;
527        }
528        .coffee-widget-step {
529          width: 32px;
530          height: 32px;
531          border-radius: 50%;
532          display: flex;
533          align-items: center;
534          justify-content: center;
535          font-size: 14px;
536          font-weight: 500;
537        }
538        .coffee-widget-step.active {
539          background: #88C540;
540          color: white;
541        }
542        .coffee-widget-step.inactive {
543          background: #e5e7eb;
544          color: #6b7280;
545        }
546        .coffee-widget-arrow {
547          color: #d1d5db;
548          font-size: 16px;
549        }
550        .coffee-widget-arrow.passed {
551          color: #88C540;
552        }
553        .coffee-widget-section-title {
554          font-size: 18px;
555          font-weight: 500;
556          margin-bottom: 16px;
557          color: #0D2518;
558        }
559        .coffee-widget-option {
560          width: 100%;
561          padding: 12px;
562          margin-bottom: 8px;
563          text-align: left;
564          border: 1px solid #d1d5db;
565          border-radius: 8px;
566          background: white;
567          cursor: pointer;
568          transition: all 0.2s;
569          font-size: 14px;
570        }
571        .coffee-widget-option:hover {
572          background: rgba(136, 197, 64, 0.05);
573          border-color: #88C540;
574        }
575        .coffee-widget-option-title {
576          font-weight: 500;
577          margin-bottom: 4px;
578        }
579        .coffee-widget-option-desc {
580          font-size: 12px;
581          color: #6b7280;
582        }
583        .coffee-widget-back {
584          margin-top: 16px;
585          color: #88C540;
586          background: none;
587          border: none;
588          cursor: pointer;
589          font-size: 14px;
590          padding: 0;
591        }
592        .coffee-widget-back:hover {
593          color: #0D2518;
594        }
595        .coffee-widget-price-box {
596          background: rgba(136, 197, 64, 0.1);
597          border: 1px solid rgba(136, 197, 64, 0.3);
598          border-radius: 8px;
599          padding: 24px;
600          text-align: center;
601          margin-bottom: 16px;
602        }
603        .coffee-widget-price-title {
604          font-size: 20px;
605          font-weight: bold;
606          color: #0D2518;
607          margin-bottom: 8px;
608        }
609        .coffee-widget-price {
610          font-size: 36px;
611          font-weight: bold;
612          color: #88C540;
613          margin: 8px 0;
614        }
615        .coffee-widget-price-details {
616          color: rgba(13, 37, 24, 0.8);
617          font-size: 14px;
618          line-height: 1.5;
619        }
620        .coffee-widget-btn-primary {
621          width: 100%;
622          background: #88C540;
623          color: white;
624          border: none;
625          padding: 16px;
626          border-radius: 8px;
627          font-weight: 600;
628          font-size: 16px;
629          cursor: pointer;
630          margin-bottom: 12px;
631          transition: all 0.2s;
632          display: flex;
633          align-items: center;
634          justify-content: center;
635          gap: 8px;
636        }
637        .coffee-widget-btn-primary:hover {
638          background: #76b032;
639          box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
640        }
641        .coffee-widget-btn-primary:disabled {
642          background: #d1d5db;
643          cursor: not-allowed;
644          opacity: 0.5;
645        }
646        .coffee-widget-btn-secondary {
647          width: 100%;
648          background: white;
649          color: #0D2518;
650          border: 2px solid #88C540;
651          padding: 16px;
652          border-radius: 8px;
653          font-weight: 600;
654          font-size: 16px;
655          cursor: pointer;
656          transition: all 0.2s;
657          display: flex;
658          align-items: center;
659          justify-content: center;
660          gap: 8px;
661          margin-bottom: 12px;
662        }
663        .coffee-widget-btn-secondary:hover {
664          background: #f9fafb;
665        }
666        .coffee-widget-btn-secondary:disabled {
667          opacity: 0.5;
668          cursor: not-allowed;
669        }
670        .coffee-widget-btn-tertiary {
671          width: 100%;
672          background: #f3f4f6;
673          color: #374151;
674          border: none;
675          padding: 16px;
676          border-radius: 8px;
677          font-weight: 600;
678          font-size: 16px;
679          cursor: pointer;
680          transition: all 0.2s;
681          display: flex;
682          align-items: center;
683          justify-content: center;
684          gap: 8px;
685        }
686        .coffee-widget-btn-tertiary:hover {
687          background: #e5e7eb;
688        }
689        .coffee-widget-form-row {
690          display: grid;
691          grid-template-columns: 1fr 1fr;
692          gap: 16px;
693        }
694        .coffee-widget-form-group {
695          margin-bottom: 20px;
696        }
697        .coffee-widget-label {
698          display: block;
699          font-size: 14px;
700          font-weight: 500;
701          margin-bottom: 8px;
702          color: #374151;
703        }
704        .coffee-widget-input {
705          width: 100%;
706          padding: 12px 16px;
707          border: 1px solid #d1d5db;
708          border-radius: 8px;
709          font-size: 14px;
710          box-sizing: border-box;
711          transition: all 0.2s;
712        }
713        .coffee-widget-input:focus {
714          outline: none;
715          border-color: #88C540;
716          box-shadow: 0 0 0 3px rgba(136, 197, 64, 0.1);
717        }
718        .coffee-widget-input.error {
719          border-color: #ef4444;
720        }
721        .coffee-widget-input-hint {
722          font-size: 12px;
723          color: #6b7280;
724          margin-top: 4px;
725        }
726        .coffee-widget-error {
727          color: #ef4444;
728          font-size: 12px;
729          margin-top: 4px;
730        }
731        .coffee-widget-checkbox-group {
732          background: #f9fafb;
733          padding: 16px;
734          border-radius: 8px;
735          margin-bottom: 20px;
736        }
737        .coffee-widget-checkbox-label {
738          display: flex;
739          align-items: flex-start;
740          gap: 12px;
741          cursor: pointer;
742        }
743        .coffee-widget-checkbox {
744          width: 20px;
745          height: 20px;
746          margin-top: 2px;
747          cursor: pointer;
748          accent-color: #88C540;
749        }
750        .coffee-widget-checkbox-text {
751          font-size: 14px;
752          color: #374151;
753          line-height: 1.5;
754        }
755        .coffee-widget-loading {
756          text-align: center;
757          padding: 40px 20px;
758        }
759        .coffee-widget-spinner {
760          border: 3px solid #f3f3f3;
761          border-top: 3px solid #88C540;
762          border-radius: 50%;
763          width: 32px;
764          height: 32px;
765          animation: coffee-widget-spin 1s linear infinite;
766          margin: 0 auto 16px;
767        }
768        .coffee-widget-contract-scroll {
769          background: white;
770          border: 1px solid #e5e7eb;
771          border-radius: 8px;
772          box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
773          max-height: 500px;
774          overflow-y: auto;
775          margin-bottom: 16px;
776        }
777        .coffee-widget-contract-inner {
778          padding: 32px;
779        }
780        .coffee-widget-contract-logo {
781          width: 64px;
782          height: 64px;
783          border-radius: 8px;
784          display: block;
785          margin: 0 auto 16px;
786        }
787        .coffee-widget-contract-header {
788          text-align: center;
789          border-bottom: 1px solid #e5e7eb;
790          padding-bottom: 24px;
791          margin-bottom: 24px;
792        }
793        .coffee-widget-contract-title {
794          font-size: 24px;
795          font-weight: bold;
796          color: #0D2518;
797          margin: 0 0 8px 0;
798        }
799        .coffee-widget-contract-subtitle {
800          color: #6b7280;
801          font-size: 14px;
802          margin: 0;
803        }
804        .coffee-widget-contract-section {
805          margin-bottom: 24px;
806        }
807        .coffee-widget-contract-section-title {
808          font-size: 18px;
809          font-weight: bold;
810          color: #0D2518;
811          text-align: center;
812          margin: 0 0 4px 0;
813        }
814        .coffee-widget-contract-section-subtitle {
815          font-size: 18px;
816          font-weight: bold;
817          color: #0D2518;
818          text-align: center;
819          margin: 0 0 12px 0;
820        }
821        .coffee-widget-contract-party-title {
822          font-weight: 600;
823          color: #111827;
824          margin-bottom: 8px;
825        }
826        .coffee-widget-contract-text {
827          font-size: 14px;
828          color: #374151;
829          line-height: 1.6;
830          margin: 0 0 8px 0;
831        }
832        .coffee-widget-contract-text-center {
833          text-align: center;
834          font-weight: 600;
835        }
836        .coffee-widget-contract-text-bold {
837          font-weight: bold;
838        }
839        .coffee-widget-contract-text-indent {
840          margin-left: 16px;
841        }
842        .coffee-widget-contract-text-price {
843          font-weight: bold;
844          color: #88C540;
845        }
846        .coffee-widget-contract-signatures {
847          display: grid;
848          grid-template-columns: 1fr 1fr;
849          gap: 32px;
850          padding-top: 32px;
851          border-top: 1px solid #e5e7eb;
852          margin-top: 32px;
853        }
854        .coffee-widget-signature-block {
855
856        }
857        .coffee-widget-signature-date {
858          font-size: 14px;
859          color: #374151;
860          margin-bottom: 48px;
861        }
862        .coffee-widget-signature-line {
863          border-top: 1px solid #d1d5db;
864          padding-top: 8px;
865        }
866        .coffee-widget-signature-label {
867          font-size: 14px;
868          font-weight: 600;
869          color: #374151;
870        }
871        .coffee-widget-signature-company {
872          font-size: 14px;
873          color: #6b7280;
874        }
875        .coffee-widget-alert-error {
876          background: #fef2f2;
877          border: 1px solid #fecaca;
878          color: #991b1b;
879          padding: 12px 16px;
880          border-radius: 8px;
881          margin-bottom: 16px;
882          font-size: 14px;
883        }
884        .coffee-widget-alert-success {
885          background: #f0fdf4;
886          border: 1px solid #bbf7d0;
887          color: #166534;
888          padding: 12px 16px;
889          border-radius: 8px;
890          margin-bottom: 16px;
891          font-size: 14px;
892        }
893        .coffee-widget-info-box {
894          background: linear-gradient(135deg, #88C540 0%, #76b032 100%);
895          border: 2px solid #76b032;
896          border-radius: 12px;
897          padding: 20px;
898          margin-top: 16px;
899          box-shadow: 0 4px 12px rgba(136, 197, 64, 0.3);
900        }
901        .coffee-widget-info-title {
902          font-size: 16px;
903          font-weight: 700;
904          color: white;
905          margin: 0 0 12px 0;
906          display: flex;
907          align-items: center;
908          gap: 8px;
909        }
910        .coffee-widget-info-title:before {
911          content: "⚠️";
912          font-size: 20px;
913        }
914        .coffee-widget-info-list {
915          font-size: 14px;
916          color: white;
917          margin: 0;
918          padding-left: 24px;
919          line-height: 1.8;
920        }
921        .coffee-widget-info-list li {
922          margin-bottom: 8px;
923          font-weight: 500;
924        }
925        .coffee-widget-info-list li:last-child {
926          margin-bottom: 0;
927        }
928        .coffee-widget-form-note {
929          font-size: 12px;
930          color: #6b7280;
931          text-align: center;
932          margin-top: 16px;
933        }
934        .coffee-widget-header-row {
935          display: flex;
936          align-items: center;
937          gap: 12px;
938          margin-bottom: 24px;
939        }
940        .coffee-widget-header-back {
941          padding: 8px;
942          background: none;
943          border: none;
944          cursor: pointer;
945          border-radius: 8px;
946          display: flex;
947          align-items: center;
948          justify-content: center;
949        }
950        .coffee-widget-header-back:hover {
951          background: #f3f4f6;
952        }
953        .coffee-widget-header-content h2 {
954          font-size: 24px;
955          font-weight: bold;
956          color: #0D2518;
957          margin: 0;
958        }
959        .coffee-widget-header-content p {
960          font-size: 14px;
961          color: #6b7280;
962          margin: 4px 0 0 0;
963        }
964        @keyframes coffee-widget-spin {
965          0% { transform: rotate(0deg); }
966          100% { transform: rotate(360deg); }
967        }
968        @media (max-width: 480px) {
969          .coffee-widget-form-row {
970            grid-template-columns: 1fr;
971          }
972          .coffee-widget-contract-signatures {
973            grid-template-columns: 1fr;
974          }
975        }
976      `;
977
978      const styleSheet = document.createElement('style');
979      styleSheet.id = 'coffee-widget-styles';
980      styleSheet.textContent = styles;
981      document.head.appendChild(styleSheet);
982    }
983
984    render() {
985      const container = document.getElementById(this.containerId);
986      if (!container) return;
987
988      container.innerHTML = `
989        <div class="coffee-widget">
990          <div class="coffee-widget-header">
991            <div style="display: flex; align-items: center; gap: 8px;">
992              <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 8h1a4 4 0 1 1 0 8h-1"/><path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z"/><line x1="6" x2="6" y1="2" y2="4"/><line x1="10" x2="10" y1="2" y2="4"/><line x1="14" x2="14" y1="2" y2="4"/></svg>
993              <h2 class="coffee-widget-title">${this.getUIText('widgetTitle')}</h2>
994            </div>
995            <p class="coffee-widget-subtitle">${this.getUIText('widgetSubtitle')}</p>
996          </div>
997          <div class="coffee-widget-body">
998            ${this.renderProgress()}
999            ${this.renderCurrentStep()}
1000          </div>
1001        </div>
1002      `;
1003
1004      this.attachEventListeners();
1005    }
1006
1007    renderProgress() {
1008      if (this.step >= 5) return '';
1009
1010      return `
1011        <div class="coffee-widget-progress">
1012          <div class="coffee-widget-step ${this.step >= 1 ? 'active' : 'inactive'}">1</div>
1013          <span class="coffee-widget-arrow ${this.step > 1 ? 'passed' : ''}">
1014            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
1015          </span>
1016          <div class="coffee-widget-step ${this.step >= 2 ? 'active' : 'inactive'}">2</div>
1017          <span class="coffee-widget-arrow ${this.step > 2 ? 'passed' : ''}">
1018            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
1019          </span>
1020          <div class="coffee-widget-step ${this.step >= 3 ? 'active' : 'inactive'}">3</div>
1021          <span class="coffee-widget-arrow ${this.step > 3 ? 'passed' : ''}">
1022            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
1023          </span>
1024          <div class="coffee-widget-step ${this.step >= 4 ? 'active' : 'inactive'}">
10244</div>
1025        </div>
1026      `;
1027    }
1028
1029    renderCurrentStep() {
1030      if (this.isLoading) {
1031        return this.renderLoading();
1032      }
1033
1034      switch (this.step) {
1035        case 1:
1036          return this.renderBrandSelection();
1037        case 2:
1038          return this.renderTypeSelection();
1039        case 3:
1040          return this.renderDefectSelection();
1041        case 4:
1042          return this.renderConditionSelection();
1043        case 5:
1044          return this.renderFinalPrice();
1045        case 6:
1046          return this.renderSellerForm();
1047        case 7:
1048          return this.renderContractPreview();
1049        default:
1050          return '';
1051      }
1052    }
1053
1054    renderLoading() {
1055      return `
1056        <div class="coffee-widget-loading">
1057          <div class="coffee-widget-spinner"></div>
1058          <p style="color: #6b7280;">${this.loadingStatus || this.getUIText('loading')}</p>
1059        </div>
1060      `;
1061    }
1062
1063    renderBrandSelection() {
1064      return `
1065        <div>
1066          <h3 class="coffee-widget-section-title">${this.getUIText('selectBrand')}</h3>
1067          <div>
1068            ${this.data.brands.map(brand => {
1069              const translated = this.getTranslation('brand', brand.id, brand.name);
1070              return `<button class="coffee-widget-option" data-brand-id="${brand.id}">${translated.name}</button>`;
1071            }).join('')}
1072          </div>
1073        </div>
1074      `;
1075    }
1076
1077    renderTypeSelection() {
1078      const availableTypes = this.data.types.filter(type => type.brand_id === this.selectedBrand.id);
1079      const translatedBrand = this.getTranslation('brand', this.selectedBrand.id, this.selectedBrand.name);
1080      return `
1081        <div>
1082          <h3 class="coffee-widget-section-title">${this.getUIText('selectType')}</h3>
1083          <p style="color: #6b7280; margin-bottom: 16px;">${this.getUIText('brand')}: <strong>${translatedBrand.name}</strong></p>
1084          <div>
1085            ${availableTypes.map(type => {
1086              const translated = this.getTranslation('machine_type', type.id, type.name);
1087              return `<button class="coffee-widget-option" data-type-id="${type.id}">${translated.name}</button>`;
1088            }).join('')}
1089          </div>
1090          <button class="coffee-widget-back" onclick="coffeeWidget.goBack()">
1091            <span style="margin-right: 4px;">&#8592;</span> ${this.getUIText('back')}
1092          </button>
1093        </div>
1094      `;
1095    }
1096
1097    renderDefectSelection() {
1098      const translatedBrand = this.getTranslation('brand', this.selectedBrand.id, this.selectedBrand.name);
1099      const translatedType = this.getTranslation('machine_type', this.selectedType.id, this.selectedType.name);
1100      return `
1101        <div>
1102          <h3 class="coffee-widget-section-title">${this.getUIText('selectDefect')}</h3>
1103          <p style="color: #6b7280; margin-bottom: 16px;">
1104            ${translatedBrand.name} - ${translatedType.name}
1105          </p>
1106          <div>
1107            ${this.data.defects.map(defect => {
1108              const translated = this.getTranslation('defect', defect.id, defect.name);
1109              return `<button class="coffee-widget-option" data-defect-id="${defect.id}">
1110                <span>${translated.name}</span>
1111              </button>`;
1112            }).join('')}
1113          </div>
1114          <button class="coffee-widget-back" onclick="coffeeWidget.goBack()">
1115            <span style="margin-right: 4px;">&#8592;</span> ${this.getUIText('back')}
1116          </button>
1117        </div>
1118      `;
1119    }
1120
1121    renderConditionSelection() {
1122      const translatedBrand = this.getTranslation('brand', this.selectedBrand.id, this.selectedBrand.name);
1123      const translatedType = this.getTranslation('machine_type', this.selectedType.id, this.selectedType.name);
1124      return `
1125        <div>
1126          <h3 class="coffee-widget-section-title">${this.getUIText('selectCondition')}</h3>
1127          <p style="color: #6b7280; margin-bottom: 16px;">
1128            ${translatedBrand.name} - ${translatedType.name}
1129          </p>
1130          <div>
1131            ${this.data.conditions.map(condition => {
1132              const translated = this.getTranslation('condition', condition.id, condition.name, condition.description);
1133              return `<button class="coffee-widget-option" data-condition-id="${c
1133ondition.id}">
1134                <div class="coffee-widget-option-title">${translated.name}</div>
1135                ${translated.description ? `<div class="coffee-widget-option-desc">${translated.description}</div>` : ''}
1136              </button>`;
1137            }).join('')}
1138          </div>
1139          <button class="coffee-widget-back" onclick="coffeeWidget.goBack()">
1140            <span style="margin-right: 4px;">&#8592;</span> ${this.getUIText('back')}
1141          </button>
1142        </div>
1143      `;
1144    }
1145
1146    renderFinalPrice() {
1147      const translatedBrand = this.getTranslation('brand', this.selectedBrand.id, this.selectedBrand.name);
1148      const translatedType = this.getTranslation('machine_type', this.selectedType.id, this.selectedType.name);
1149      const translatedDefect = this.getTranslation('defect', this.selectedDefect.id, this.selectedDefect.name);
1150      const translatedCondition = this.getTranslation('condition', this.selectedCondition.id, this.selectedCondition.name);
1151
1152      return `
1153        <div style="text-align: center;">
1154          <div class="coffee-widget-price-box">
1155            <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#88C540" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin: 0 auto 8px;"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>
1156            <div class="coffee-widget-price-title">${this.getUIText('finalPrice')}</div>
1157            <div class="coffee-widget-price">${this.formatPrice(this.finalPrice)}</div>
1158            <div class="coffee-widget-price-details">
1159              ${translatedBrand.name} - ${translatedType.name}<br>
1160              ${this.getUIText('defect')}: ${translatedDefect.name}<br>
1161              ${this.getUIText('condition')}: ${translatedCondition.name}
1162            </div>
1163          </div>
1164          <button class="coffee-widget-btn-primary" onclick="coffeeWidget.goToSellerForm()">
1165            ${this.getUIText('continueToForm')}
1166          </button>
1167          <button class="coffee-widget-btn-secondary" onclick="coffeeWidget.reset()" style="border-color: #d1d5db; color: #374151;">
1168            ${this.getUIText('newCalculation')}
1169          </button>
1170        </div>
1171      `;
1172    }
1173
1174    getMinSelectableDate() {
1175      const tomorrow = new Date();
1176      tomorrow.setDate(tomorrow.getDate() + 1);
1177      while (tomorrow.getDay() === 0 || tomorrow.getDay() === 6) {
1178        tomorrow.setDate(tomorrow.getDate() + 1);
1179      }
1180      const year = tomorrow.getFullYear();
1181      const month = String(tomorrow.getMonth() + 1).padStart(2, '0');
1182      const day = String(tomorrow.getDate()).padStart(2, '0');
1183      return `${year}-${month}-${day}`;
1184    }
1185
1186    renderSellerForm() {
1187      const minDate = this.getMinSelectableDate();
1188      const pickupDateValue = this.sellerData.pickupDate || minDate;
1189
1190      const renderError = (field) => {
1191        return this.formErrors[field] ? `<div class="coffee-widget-error">${this.formErrors[field]}</div>` : '';
1192      };
1193
1194      const inputClass = (field) => {
1195        return `coffee-widget-input${this.formErrors[field] ? ' error' : ''}`;
1196      };
1197
1198      return `
1199        <div>
1200          <div class="coffee-widget-header-row">
1201            <button class="coffee-widget-header-back" onclick="coffeeWidget.goBack()">
1202              <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
1203            </button>
1204            <div class="coffee-widget-header-content">
1205              <h2>${this.getUIText('sellerInfo')}</h2>
1206              <p>${this.getUIText('sellerInfoSubtitle')}</p>
1207            </div>
1208          </div>
1209
1210          ${this.errorMessage ? `<div class="coffee-widget-alert-error">${this.errorMessage}</div>` : ''}
1211
1212          <form id="seller-form">
1213            <div class="coffee-widget-form-row">
1214              <div class="coffee-widget-form-group">
1215                <label class="coffee-widget-label">${this.getUIText('firstName')} *</label>
1216                <input type="text" class="${inputClass('firstName')}" id="firstName" value="${this.sellerData.firstName}" placeholder="${this.language === 'cs' ? 'Jan' : 'Ján'}">
1217                ${renderError('firstName')}
1218              </div>
1219              <div class="coffee-widget-form-group">
1220                <label class="coffee-widget-label">${this.getUIText('lastName')} *</label>
1221                <input type="text" class="${inputClass('lastName')}" id="lastName" value="${this.sellerData.lastName}" placeholder="${this.language === 'cs' ? 'Novák' : 'Novák'}">
1222                ${renderError('lastName')}
1223              </div>
1224            </div>
1225
1226            <div class="coffee-widget-form-group">
1227              <label class="coffee-widget-label">${this.getUIText('street')} *</label>
1228              <input type="text" class="${inputClass('street')}" id="street" value="${this.sellerData.street}" placeholder="${this.language === 'cs' ? 'Hlavní 123' : 'Hlavná 123'}">
1229              ${renderError('street')}
1230            </div>
1231
1232            <div class="coffee-widget-form-row">
1233              <div class="coffee-widget-form-group">
1234                <label class="coffee-widget-label">${this.getUIText('city')} *</label>
1235                <input type="text" class="${inputClass('city')}" id="city" value="${this.sellerData.city}" placeholder="${this.language === 'cs' ? 'Praha' : 'Bratislava'}">
1236                ${renderError('city')}
1237              </div>
1238              <div class="coffee-widget-form-group">
1239                <label class="coffee-widget-label">${this.getUIText('postalCode')} *</label>
1240                <input type="text" class="${inputClass('postalCode')}" id="postalCode" value="${this.sellerData.postalCode}" placeholder="${this.language === 'cs' ? '110 00' : '811 01'}">
1241                ${renderError('postalCode')}
1242              </div>
1243            </div>
1244
1245            <div class="coffee-widget-form-group">
1246              <label class="coffee-widget-label">${this.getUIText('country')} *</label>
1247              <input type="text" class="${inputClass('country')}" id="country" value="${this.sellerData.country}" placeholder="${this.language === 'cs' ? 'Česko' : 'Slovensko'}">
1248              ${renderError('country')}
1249            </div>
1250
1251            <div class="coffee-widget-form-group">
1252              <label class="coffee-widget-label">${this.getUIText('phone')} *</label>
1253              <input type="tel" class="${inputClass('phone')}" id="phone" value="${this.sellerData.phone}" placeholder="${this.language === 'cs' ? '+420 601 123 456' : '+421 910 123 456'}">
1254              ${renderError('phone')}
1255            </div>
1256
1257            <div class="coffee-widget-form-group">
1258              <label class="coffee-widget-label">${this.getUIText('email')} *</label>
1259              <input type="email" class="${inputClass('email')}" id="email" value="${this.sellerData.email}" placeholder="[email protected]">
1260              ${renderError('email')}
1261            </div>
1262
1263            <div class="coffee-widget-form-group">
1264              <label class="coffee-widget-label">${this.getUIText('iban')} *</label>
1265              <input type="text" class="${inputClass('iban')}" id="iban" value="${this.sellerData.iban}" placeholder="${this.language === 'cs' ? 'CZ65 0800 0000 1920 0014 5399' : 'SK31 1200 0000 1987 4263 7541'}">
1266              ${renderError('iban')}
1267            </div>
1268
1269            <div class="coffee-widget-form-group">
1270              <label class="coffee-widget-label">${this.getUIText('pickupDate')} *</label>
1271              <input type="date" class="${inputClass('pickupDate')}
1271" id="pickupDate" value="${pickupDateValue}" min="${minDate}">
1272              ${renderError('pickupDate')}
1273            </div>
1274
1275            <div class="coffee-widget-checkbox-group">
1276              <label class="coffee-widget-checkbox-label">
1277                <input type="checkbox" class="coffee-widget-checkbox" id="gdprConsent" ${this.sellerData.gdprConsent ? 'checked' : ''}>
1278                <span class="coffee-widget-checkbox-text">
1279                  ${this.getUIText('gdprConsent')} *
1280                </span>
1281              </label>
1282              ${renderError('gdprConsent')}
1283            </div>
1284
1285            <button type="submit" class="coffee-widget-btn-primary">
1286              ${this.getUIText('generateContract')}
1287            </button>
1288          </form>
1289        </div>
1290      `;
1291    }
1292
1293    formatPrice(price) {
1294      const locale = this.language === 'cs' ? 'cs-CZ' : 'sk-SK';
1295      const currency = this.currency;
1296
1297      return new Intl.NumberFormat(locale, {
1298        style: 'currency',
1299        currency: currency,
1300        minimumFractionDigits: 0,
1301        maximumFractionDigits: 0,
1302      }).format(price);
1303    }
1304
1305    formatDate(date = new Date()) {
1306      const day = String(date.getDate()).padStart(2, '0');
1307      const month = String(date.getMonth() + 1).padStart(2, '0');
1308      const year = date.getFullYear();
1309      return `${day}.${month}.${year}`;
1310    }
1311
1312    renderContractPreview() {
1313      if (!this.contractData) {
1314        return `<div class="coffee-widget-alert-error">${this.getUIText('errorNoData')}</div>`;
1315      }
1316
1317      const { seller, machine, contractNumber, contractDate } = this.contractData;
1318
1319      return `
1320        <div>
1321          <div class="coffee-widget-header-row">
1322            <button class="coffee-widget-header-back" onclick="coffeeWidget.goBack()">
1323              <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
1324            </button>
1325            <div class="coffee-widget-header-content">
1326              <h2>${this.getUIText('contractPreview')}</h2>
1327              <p>${this.getUIText('contractPreviewSubtitle')}</p>
1328            </div>
1329          </div>
1330
1331          ${this.errorMessage ? `<div class="coffee-widget-alert-error">${this.errorMessage}</div>` : ''}
1332          ${this.downloadSuccess ? `<div class="coffee-widget-alert-success">${this.getUIText('contractSuccess')}</div>` : ''}
1333          ${this.emailSuccess ? `<div class="coffee-widget-alert-success">${this.getUIText('emailSuccess')}</div>` : ''}
1334
1335          <div class="coffee-widget-contract-scroll">
1336            <div class="coffee-widget-contract-inner">
1337              <img src="/brewive_square.png" alt="Brewive" class="coffee-widget-contract-logo" onerror="this.style.display='none'">
1338
1339              <div class="coffee-widget-contract-header">
1340                <h1 class="coffee-widget-contract-title">KUPNÍ SMLOUVA</h1>
1341                <p class="coffee-widget-contract-subtitle">na prodej movité věci</p>
1342              </div>
1343
1344              <p class="coffee-widget-contract-text coffee-widget-contract-text-center" style="margin-bottom: 16px;">
1345                podle ustanovení § 2079 a následujících občanského zákoníku
1346              </p>
1347
1348              <p class="coffee-widget-contract-text coffee-widget-contract-text-center" style="margin-bottom: 24px;">
1349                mezi
1350              </p>
1351
1352              <div class="coffee-widget-contract-section">
1353                <div class="coffee-widget-contract-party-title">1. Prodávající:</div>
1354                <p class="coffee-widget-contract-text">Jméno a příjmení: ${seller.firstName} ${seller.lastName}</p>
1355                <p class="coffee-widget-contract-text">Bydliště: ${seller.street}, ${seller.postalCode} ${seller.city}, ${seller.country}</p>
1356                <p class="coffee-widget-contract-text">Telefon: ${seller.phone}</p>
1357                <p class="coffee-widget-contract-text">E-mail: ${seller.email}</p>
1358                <p class="coffee-widget-contract-text">IBAN: ${seller.iban}</p>
1359                <p class="coffee-widget-contract-text" style="font-style: italic;">(dále také jen "prodávající")</p>
1360              </div>
1361
1362              <p class="coffee-widget-contract-text coffee-widget-contract-text-center" style="margin: 16px 0;">a</p>
1363
1364              <div class="coffee-widget-contract-section">
1365                <div class="coffee-widget-contract-party-title">2. Kupující:</div>
1366                <p class="coffee-widget-contract-text">Brewive s.r.o.</p>
1367                <p class="coffee-widget-contract-text">se sídlem: Jičínská 226/17, 130 00 Praha – Žižkov</p>
1368                <p class="coffee-widget-contract-text">IČO: 23032189</p>
1369                <p class="coffee-widget-contract-text">DIČ: CZ23032189</p>
1370                <p class="coffee-widget-contract-text">zastoupená: Lukášem Bojkovským, jednatelem</p>
1371                <p class="coffee-widget-contract-text">kontakt: [email protected]</p>
1372                <p class="coffee-widget-contract-text" style="font-style: italic;">(dále také jen "kupující")</p>
1373              </div>
1374
1375              <p class="coffee-widget-contract-text coffee-widget-contract-text-center" style="margin: 16px 0;">
1376                společně také "smluvní strany"
1377              </p>
1378
1379              <div class="coffee-widget-contract-section">
1380                <h3 class="coffee-widget-contract-section-title">I.</h3>
1381                <h3 class="coffee-widget-contract-section-subtitle">Úvodní ustanovení</h3>
1382                <p class="coffee-widget-contract-text">1. Prodávající prohlašuje, že je vlastníkem:</p>
1383                <p class="coffee-widget-contract-text coffee-widget-contract-text-indent coffee-widget-contract-text-bold">${machine.brand} ${machine.type}</p>
1384                <p class="coffee-widget-contract-text">(dále jen "předmět koupě").</p>
1385                <p class="coffee-widget-contract-text">2. Kupující prohlašuje, že si předmět koupě řádně prohlédl a vyzkoušel, a že byl seznámen s jeho stavem.</p>
1386              </div>
1387
1388              <div class="coffee-widget-contract-section">
1389                <h3 class="coffee-widget-contract-section-title">II.</h3>
1390                <h3 class="coffee-widget-contract-section-subtitle">Předmět smlouvy</h3>
1391                <p class="coffee-widget-contract-text">1. Prodávající se touto smlouvou zavazuje předmět koupě kupujícímu odevzdat.</p>
1392                <p class="coffee-widget-contract-text">2. Kupující se touto smlouvou zavazuje předmět koupě převzít a zaplatit za něj kupní cenu sjednanou v článku IV. této smlouvy.</p>
1393              </div>
1394
1395              <div class="coffee-widget-contract-section">
1396                <h3 class="coffee-widget-contract-section-title">III.</h3>
1397                <h3 class="coffee-widget-contract-section-subtitle">Termín a místo odevzdání a převzetí předmětu koupě, přechod vlastnického práva</h3>
1398                <p class="coffee-widget-contract-text">1. Prodávající odevzdá kupujícímu předmět koupě bezprostředně po podpisu této smlouvy, případně doručením na adresu sídla kupujícího.</p>
1399                <p class="coffee-widget-contract-text">2. Vlastnické právo k předmětu koupě kupující nabývá již samotnou účinností smlouvy.</p>
1400              </div>
1401
1402              <div class="coffee-widget-contract-section">
1403                <h3 class="coffee-widget-contract-section-title">IV.</h3>
1404                <h3 class="coffee-widget-contract-section-subtitle">Cena a způsob úhrady</h3>
1405                <p class="coffee-widget-contract-text">1. Kupní cena byla dle dohody prodávajícího a kupujícího stanovena ve výši:</p>
1406                <p class="coffee-widget-contract-text coffee-widget-contract-text-indent coffee-widget-contract-text-price">${this.formatPrice(machine.price)}</p>
1407                <p class="coffee-widget-contract-text">2. Kupující se zavazuje uhradit kupní cenu bezhotovostním převodem na bankovní účet prodávajícího (IBAN: ${seller.iban}), a to do 7 pracovních dnů od doručení předmětu koupě na adresu sídla kupujícího.</p>
1408              </div>
1409
1410              <div class="coffee-widget-contract-section">
1411                <h3 class="coffee-widget-contract-section-title">V.</h3>
1412                <h3 class="coffee-widget-contract-section-subtitle">Odpovědnost za vady</h3>
1413                <p class="coffee-widget-contract-text">1. Prodávající výslovně upozorňuje na následující vady předmětu koupě:</p>
1414                <p class="coffee-widget-contract-text coffee-widget-contract-text-indent">Stav: ${machine.condition}</p>
1415                <p class="coffee-widget-contract-text coffee-widget-contract-text-indent">Závada: ${machine.defect}</p>
1416                <p class="coffee-widget-contract-text">2. Prodávající prohlašuje, že předmět koupě nemá žádné právní vady a není zatížen právy třetích osob.</p>
1417                <p class="coffee-widget-contract-text">3. V případě nepodstatného porušení smlouvy má kupující právo na přiměřenou slevu ze sjednané ceny odpovídající rozsahu a povaze vady. Jde-li o podstatné porušení smlouvy, má kupující právo od smlouvy odstoupit.</p>
1418                <p class="coffee-widget-contract-text">4. Kupující prohlašuje, že se řádně seznámil se stavem předmětu koupě, jakož i vadami uvedenými v odstavci 1 tohoto článku smlouvy.</p>
1419              </div>
1420
1421              <div class="coffee-widget-contract-section">
1422                <h3 class="coffee-widget-contract-section-title">VI.</h3>
1423                <h3 class="coffee-widget-contract-section-subtitle">Závěrečná ustanovení</h3>
1424                <p class="coffee-widget-contract-text">1. Práva a povinnosti neupravené touto smlouvou se řídí ustanoveními občanského zákoníku a právními předpisy souvisejícími.</p>
1425                <p class="coffee-widget-contract-text">
14252. Změny a doplňky této smlouvy je možné činit pouze po dohodě smluvních stran formou písemných vzestupně číslovaných dodatků.</p>
1426                <p class="coffee-widget-contract-text">3. Smlouva byla vypracována ve dvou vyhotoveních, z nichž každý z účastníků obdrží jedno.</p>
1427                <p class="coffee-widget-contract-text">4. Smluvní strany prohlašují, že si tuto smlouvu před jejím podpisem přečetly, že byla uzavřena po vzájemném projednání podle jejich pravé a svobodné vůle, určitě, vážně a srozumitelně, nikoli v tísni za nápadně nevýhodných podmínek.</p>
1428              </div>
1429
1430              <div class="coffee-widget-contract-signatures">
1431                <div class="coffee-widget-signature-block">
1432                  <p class="coffee-widget-signature-date">V ${seller.city} dne ${contractDate}</p>
1433                  <div class="coffee-widget-signature-line">
1434                    <p class="coffee-widget-signature-label">Prodávající</p>
1435                    <p class="coffee-widget-signature-company">${seller.firstName} ${seller.lastName}</p>
1436                  </div>
1437                </div>
1438                <div class="coffee-widget-signature-block">
1439                  <p class="coffee-widget-signature-date">V Praze dne ${contractDate}</p>
1440                  <div class="coffee-widget-signature-line">
1441                    <p class="coffee-widget-signature-label">Kupující</p>
1442                    <p class="coffee-widget-signature-company">Brewive s.r.o.</p>
1443                    <p class="coffee-widget-signature-company">Lukáš Bojkovský, jednatel</p>
1444                  </div>
1445                </div>
1446              </div>
1447            </div>
1448          </div>
1449
1450          <div style="display: flex; flex-direction: column; gap: 12px;">
1451            <button class="coffee-widget-btn-primary" onclick="coffeeWidget.downloadContract()" ${this.isDownloading ? 'disabled' : ''}>
1452              ${this.isDownloading ? `
1453                <svg class="coffee-widget-spinner" style="width: 20px; height: 20px; margin: 0;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>
1454                ${this.getUIText('downloading')}
1455              ` : `
1456                <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>
1457                ${this.getUIText('downloadContract')}
1458              `}
1459            </button>
1460
1461            <button class="coffee-widget-btn-tertiary" onclick="coffeeWidget.reset()">
1462              <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 16h5v5"/></svg>
1463              ${this.getUIText('newCalculation')}
1464            </button>
1465          </div>
1466
1467          <div class="coffee-widget-info-box">
1468            <p class="coffee-widget-info-title">${this.getUIText('nextStepsTitle')}</p>
1469            <ol class="coffee-widget-info-list">
1470              <li>${this.getUIText('nextStep1')}</li>
1471              <li>${this.getUIText('nextStep2')}</li>
1472              <li>${this.getUIText('nextStep3')}</li>
1473            </ol>
1474          </div>
1475        </div>
1476      `;
1477    }
1478
1479    attachEventListeners() {
1480      document.querySelectorAll('[data-brand-id]').forEach(btn => {
1481        btn.addEventListener('click', (e) => {
1482          const brandId = e.currentTarget.dataset.brandId;
1483          this.selectedBrand = this.data.brands.find(b => b.id === brandId);
1484          this.step = 2;
1485          this.render();
1486        });
1487      });
1488
1489      document.querySelectorAll('[data-type-id]').forEach(btn => {
1490        btn.addEventListener('click', (e) => {
1491          const typeId = e.currentTarget.dataset.typeId;
1492          this.selectedType = this.data.types.find(t => t.id === typeId);
1493          this.step = 3;
1494          this.render();
1495        });
1496      });
1497
1498      document.querySelectorAll('[data-defect-id]').forEach(btn => {
1499        btn.addEventListener('click', (e) => {
1500          const defectId = e.currentTarget.dataset.defectId;
1501          this.selectedDefect = this.data.defects.find(d => d.id === defectId);
1502          this.step = 4;
1503          this.render();
1504        });
1505      });
1506
1507      document.querySelectorAll('[data-condition-id]').forEach(btn => {
1508        btn.addEventListener('click', (e) => {
1509          const conditionId = e.currentTarget.dataset.conditionId;
1510          this.selectedCondition = this.data.conditions.find(c => c.id === conditionId);
1511          this.calculatePrice();
1512        });
1513      });
1514
1515      const sellerForm = document.getElementById('seller-form');
1516      if (sellerForm) {
1517        sellerForm.addEventListener('submit', (e) => {
1518          e.preventDefault();
1519          this.submitSellerForm();
1520        });
1521      }
1522    }
1523
1524    calculatePrice() {
1525      const priceRule = this.data.priceRules.find(rule =>
1526        rule.brand_id === this.selectedBrand.id && rule.machine_type_id === this.selectedType.id
1527      );
1528
1529      if (priceRule) {
1530        const baseEurPrice = Math.round(priceRule.base_price * this.selectedDefect.price_multiplier * this.selectedCondition.price_multiplier);
1531        this.basePrice = baseEurPrice;
1532
1533        // Convert price based on language/currency
1534        switch (this.language) {
1535          case 'cs':
1536            // Czech Republic uses CZK (1 EUR ≈ 25 CZK)
1537            this.finalPrice = baseEurPrice * 25;
1538            this.currency = 'CZK';
1539            break;
1540          case 'sk':
1541          case 'de':
1542          case 'en':
1543          default:
1544            // Slovakia, Germany, and English markets use EUR
1545            this.finalPrice = baseEurPrice;
1546            this.currency = 'EUR';
1547            break;
1548        }
1549
1550        this.step = 5;
1551        this.render();
1552      }
1553    }
1554
1555    goToSellerForm() {
1556      this.step = 6;
1557      this.errorMessage = null;
1558      this.formErrors = {};
1559      this.render();
1560    }
1561
1562    async submitSellerForm() {
1563      this.sellerData = {
1564        firstName: document.getElementById('firstName').value.trim(),
1565        lastName: document.getElementById('lastName').value.trim(),
1566        email: document.getElementById('email').value.trim(),
1567        phone: document.getElementById('phone').value.trim(),
1568        street: document.getElementById('street').value.trim(),
1569        city: document.getElementById('city').value.trim(),
1570        postalCode: document.getElementById('postalCode').value.trim(),
1571        country: document.getElementById('country').value.trim(),
1572        iban: document.getElementById('iban').value.trim().toUpperCase(),
1573        pickupDate: document.getElementById('pickupDate').value,
1574        gdprConsent: document.getElementById('gdprConsent').checked
1575      };
1576
1577      if (!this.validateSellerData()) {
1578        return;
1579      }
1580
1581      await this.generateContract();
1582    }
1583
1584    validateSellerData() {
1585      this.formErrors = {};
1586      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1587      const phoneRegex = /^[+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{3,6}$/im;
1588      const pscRegex = /^\d{3}\s?\d{2}$/;
1589      const ibanRegex = /^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$/;
1590
1591      if (!this.sellerData.firstName) {
1592        this.formErrors.firstName = `${this.getUIText('firstName')} ${this.getUIText('errorRequired')}`;
1593      } else if (this.sellerData.firstName.length < 2) {
1594        this.formErrors.firstName = `${this.getUIText('firstName')} ${this.getUIText('errorMinLength2')}`;
1595      }
1596
1597      if (!this.sellerData.lastName) {
1598        this.formErrors.lastName = `${this.getUIText('lastName')} ${this.getUIText('errorRequired')}`;
1599      } else if (this.sellerData.lastName.length < 2) {
1600        this.formErrors.lastName = `${this.getUIText('lastName')} ${this.getUIText('errorMinLength2')}`;
1601      }
1602
1603      if (!this.sellerData.street) {
1604        this.formErrors.street = `${this.getUIText('street')} ${this.getUIText('errorRequired')}`;
1605      }
1606
1607      if (!this.sellerData.city) {
1608        this.formErrors.city = `${this.getUIText('city')} ${this.getUIText('errorRequired')}`;
1609      }
1610
1611      if (!this.sellerData.postalCode) {
1612        this.formErrors.postalCode = `${this.getUIText('postalCode')} ${this.getUIText('errorRequired')}`;
1613      } else if (!pscRegex.test(this.sellerData.postalCode)) {
1614        this.formErrors.postalCode = this.getUIText('errorInvalidPostalCode');
1615      }
1616
1617      if (!this.sellerData.country) {
1618        this.formErrors.country = `${this.getUIText('country')} ${this.getUIText('errorRequired')}`;
1619      }
1620
1621      if (!this.sellerData.phone) {
1622        this.formErrors.phone = `${this.getUIText('phone')} ${this.getUIText('errorRequired')}`;
1623      } else if (!phoneRegex.test(this.sellerData.phone)) {
1624        this.formErrors.phone = this.getUIText('errorInvalidPhone');
1625      }
1626
1627      if (!this.sellerData.email) {
1628        this.formErrors.email = `${this.getUIText('email')} ${this.getUIText('errorRequired')}`;
1629      } else if (!emailRegex.test(this.sellerData.email)) {
1630        this.formErrors.email = this.getUIText('errorInvalidEmail');
1631      }
1632
1633      const ibanClean = this.sellerData.iban.replace(/\s/g, '');
1634      if (!this.sellerData.iban) {
1635        this.formErrors.iban = `${this.getUIText('iban')} ${this.getUIText('errorRequired')}`;
1636      } else if (!ibanRegex.test(ibanClean)) {
1637        this.formErrors.iban = this.getUIText('errorInvalidIban');
1638      }
1639
1640      if (!this.sellerData.pickupDate) {
1641        this.formErrors.pickupDate = `${this.getUIText('pickupDate')} ${this.getUIText('errorRequired')}`;
1642      } else {
1643        const selectedDate = new Date(this.sellerData.pickupDate);
1644        const dayOfWeek = selectedDate.getDay();
1645        if (dayOfWeek === 0 || dayOfWeek === 6) {
1646          this.formErrors.pickupDate = this.getUIText('errorWeekdayOnly');
1647        }
1648      }
1649
1650      if (!this.sellerData.gdprConsent) {
1651        this.formErrors.gdprConsent = this.getUIText('errorGdprRequired');
1652      }
1653
1654      if (Object.keys(this.formErrors).length > 0) {
1655        this.render();
1656        return false;
1657      }
1658
1659      return true;
1660    }
1661
1662    generateContractNumber() {
1663      const date = new Date();
1664      const year = date.getFullYear();
1665      const month = String(date.getMonth() + 1).padStart(2, '0');
1666      const day = String(date.getDate()).padStart(2, '0');
1667      const random = String(Math.floor(Math.random() * 10000)).padStart(4, '0');
1668      return `KZ-${year}${month}${day}-${random}`;
1669    }
1670
1671    async generateContract() {
1672      this.isLoading = true;
1673      this.loadingStatus = this.getUIText('loadingCreatingContract');
1674      this.log('Starting contract generation...');
1675      this.render();
1676
1677      try {
1678        const contractNumber = this.generateContractNumber();
1679        const contractDate = this.formatDate();
1680        this.log('Contract number generated:', contractNumber);
1681
1682        if (!this.supabase) {
1683          throw new Error(this.getUIText('errorDatabaseUnavailable'));
1684        }
1685
1686        this.loadingStatus = this.getUIText('loadingSavingContract');
1687        this.log('Saving contract to database...');
1688
1689        const { data, error } = await this.supabase
1690          .from('contracts')
1691          .insert({
1692            contract_number: contractNumber,
1693            seller_first_name: this.sellerData.firstName,
1694            seller_last_name: this.sellerData.lastName,
1695            seller_street: this.sellerData.street,
1696            seller_city: this.sellerData.city,
1697            seller_postal_code: this.sellerData.postalCode,
1698            seller_country: this.sellerData.country,
1699            seller_phone: this.sellerData.phone,
1700            seller_email: this.sellerData.email,
1701            seller_iban: this.sellerData.iban,
1702            pickup_date: this.sellerData.pickupDate,
1703            brand_name: this.selectedBrand.name,
1704            machine_type_name: this.selectedType.name,
1705            defect_name: this.selectedDefect.name,
1706            condition_name: this.selectedCondition.name,
1707            final_price: this.basePrice || this.finalPrice,
1708            language: this.language,
1709            status: 'draft',
1710          })
1711          .select()
1712          .single();
1713
1714        if (error) {
1715          this.error('Error creating contract in DB:', error);
1716          throw new Error(`${this.getUIText('errorSavingContract')}: ${error.message}`);
1717        }
1718
1719        this.contractId = data.id;
1720        this.log('Contract saved to database successfully, ID:', data.id);
1721
1722        this.contractData = {
1723          seller: this.sellerData,
1724          machine: {
1725            brand: this.selectedBrand.name,
1726            type: this.selectedType.name,
1727            defect: this.selectedDefect.name,
1728            condition: this.selectedCondition.name,
1729            price: this.finalPrice,
1730          },
1731          contractNumber,
1732          contractDate,
1733        };
1734        this.log('Contract data prepared, moving to preview');
1735        this.step = 7;
1736      } catch (error) {
1737        this.error('Error generating contract:', error);
1738        this.errorMessage = error.message || this.getUIText('errorGeneratingContract');
1739        this.step = 6;
1740      } finally {
1741        this.isLoading = false;
1742        this.loadingStatus = '';
1743        this.render();
1744      }
1745    }
1746
1747    async downloadContract() {
1748      this.isDownloading = true;
1749      this.errorMessage = null;
1750      this.log('Starting contract download...');
1751      this.render();
1752
1753      try {
1754        if (!window.jspdf) {
1755          this.log('Loading jsPDF library...');
1756          await this.loadJsPDF();
1757          this.log('jsPDF library loaded');
1758        }
1759
1760        this.log('Generating PDF document...');
1761        const { jsPDF } = window.jspdf;
1762        const doc = new jsPDF();
1763        const { seller, machine, contractNumber, contractDate } = this.contractData;
1764
1765        doc.setFont('helvetica');
1766
1767        doc.setFontSize(18);
1768        doc.setFont('helvetica', 'bold');
1769        doc.text('KUPNI SMLOUVA', 105, 25, { align: 'center' });
1770
1771        doc.setFontSize(11);
1772        doc.setFont('helvetica', 'normal');
1773        doc.text('na prodej movite veci', 105, 32, { align: 'center' });
1774        doc.text('podle ustanoveni § 2079 a nasledujicich obcanskeho zakoniku', 105, 40, { align: 'center' });
1775        doc.text('mezi', 105, 50, { align: 'center' });
1776
1777        let y = 60;
1778        doc.setFont('helvetica', 'bold');
1779        doc.text('1. Prodavajici:', 20, y);
1780        doc.setFont('helvetica', 'normal');
1781        y += 7;
1782        doc.text(`Jmeno a prijmeni: ${seller.firstName} ${seller.lastName}`, 20, y);
1783        y += 6;
1784        doc.text(`Bydliste: ${seller.street}, ${seller.postalCode} ${seller.city}, ${seller.country}`, 20, y);
1785        y += 6;
1786        doc.text(`Telefon: ${seller.phone}`, 20, y);
1787        y += 6;
1788        doc.text(`E-mail: ${seller.email}`, 20, y);
1789        y += 6;
1790        doc.text(`IBAN: ${seller.iban}`, 20, y);
1791        y += 6;
1792        doc.text('(dale take jen "prodavajici")', 20, y);
1793
1794        y += 12;
1795        doc.text('a', 105, y, { align: 'center' });
1796
1797        y += 10;
1798        doc.setFont('helvetica', 'bold');
1799        doc.text('2. Kupujici:', 20, y);
1800        doc.setFont('helvetica', 'normal');
1801        y += 7;
1802        doc.text('Brewive s.r.o.', 20, y);
1803        y += 6;
1804        doc.text('se sidlem: Jicinska 226/17, 130 00 Praha - Zizkov', 20, y);
1805        y += 6;
1806        doc.text('ICO: 23032189, DIC: CZ23032189', 20, y);
1807        y += 6;
1808        doc.text('zastoupena: Lukasem Bojkovskym, jednatelem', 20, y);
1809        y += 6;
1810        doc.text('(dale take jen "kupujici")', 20, y);
1811
1812        y += 12;
1813        doc.text('spolecne take "smluvni strany"', 105, y, { align: 'center' });
1814
1815        y += 15;
1816        doc.setFont('helvetica', 'bold');
1817        doc.text('I. Uvodni ustanoveni', 105, y, { align: 'center' });
1818        doc.setFont('helvetica', 'normal');
1819        y += 8;
1820        doc.text('1. Prodavajici prohlasuje, ze je vlastnikem:', 20, y);
1821        y += 6;
1822        doc.setFont('helvetica', 'bold');
1823        doc.text(`   ${machine.brand} ${machine.type}`, 20, y);
1824        doc.setFont('helvetica', 'normal');
1825        y += 6;
1826        doc.text('(dale jen "predmet koupe").', 20, y);
1827
1828        y += 12;
1829        doc.setFont('helvetica', 'bold');
1830        doc.text('IV. Cena a zpusob uhrady', 105, y, { align: 'center' });
1831        doc.setFont('helvetica', 'normal');
1832        y += 8;
1833        doc.text('1. Kupni cena byla stanovena ve vysi:', 20, y);
1834        y += 6;
1835        doc.setFont('helvetica', 'bold');
1836        doc.text(`   ${this.formatPrice(machine.price)}`, 20, y);
1837        doc.setFont('helvetica', 'normal');
1838
1839        y += 12;
1840        doc.setFont('helvetica', 'bold');
1841        doc.text('V. Odpovednost za vady', 105, y, { align: 'center' });
1842        doc.setFont('helvetica', 'normal');
1843        y += 8;
1844        doc.text('1. Prodavajici upozornuje na nasledujici vady:', 20, y);
1845        y += 6;
1846        doc.text(`   Stav: ${machine.condition}`, 20, y);
1847        y += 6;
1848        doc.text(`   Zavada: ${machine.defect}`, 20, y);
1849
1850        y += 20;
1851        doc.line(20, y, 85, y);
1852        doc.line(115, y, 190, y);
1853        y += 5;
1854        doc.text('Prodavajici', 52, y, { align: 'center' });
1855        doc.text('Kupujici', 152, y, { align: 'center' });
1856        y += 5;
1857        doc.setFontSize(9);
1858        doc.text(`${seller.firstName} ${seller.lastName}`, 52, y, { align: 'center' });
1859        doc.text(`V ${seller.city} dne ${contractDate}`, 52, y + 4, { align: 'center' });
1860        doc.text('Brewive s.r.o.', 152, y, { align: 'center' });
1861        doc.text(`V Praze dne ${contractDate}
1861`, 152, y + 4, { align: 'center' });
1862
1863        const fileName = `Zmluva_${contractNumber}.pdf`;
1864        this.log('Saving PDF as:', fileName);
1865        doc.save(fileName);
1866
1867        if (this.supabase && this.contractId) {
1868          this.log('Updating contract status to downloaded...');
1869          const { error } = await this.supabase
1870            .from('contracts')
1871            .update({ status: 'downloaded' })
1872            .eq('id', this.contractId);
1873
1874          if (error) {
1875            this.error('Error updating contract status:', error);
1876          } else {
1877            this.log('Contract status updated successfully');
1878          }
1879        }
1880
1881        this.log('Contract downloaded successfully');
1882        this.downloadSuccess = true;
1883        setTimeout(() => {
1884          this.downloadSuccess = false;
1885          this.render();
1886        }, 3000);
1887      } catch (error) {
1888        this.error('Error downloading contract:', error);
1889        this.errorMessage = this.getUIText('errorDownloadingContract');
1890      } finally {
1891        this.isDownloading = false;
1892        this.render();
1893      }
1894    }
1895
1896    async loadJsPDF() {
1897      return new Promise((resolve, reject) => {
1898        const script = document.createElement('script');
1899        script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
1900        script.onload = resolve;
1901        script.onerror = reject;
1902        document.head.appendChild(script);
1903      });
1904    }
1905
1906    async sendContractEmail() {
1907      if (!this.contractData) {
1908        this.errorMessage = this.getUIText('errorNoData');
1909        this.render();
1910        return;
1911      }
1912
1913      this.isSendingEmail = true;
1914      this.errorMessage = null;
1915      this.render();
1916
1917      try {
1918        const response = await fetch(`${this.supabaseUrl}/functions/v1/send-contract-email`, {
1919          method: 'POST',
1920          headers: {
1921            'Content-Type': 'application/json',
1922            'Authorization': `Bearer ${this.supabaseKey}`,
1923          },
1924          body: JSON.stringify({
1925            contractId: this.contractId,
1926            contractData: this.contractData
1927          })
1928        });
1929
1930        if (!response.ok) {
1931          throw new Error(this.getUIText('errorSendingEmail'));
1932        }
1933
1934        if (this.supabase && this.contractId) {
1935          await this.supabase
1936            .from('contracts')
1937            .update({ status: 'emailed', email_sent: true, email_sent_at: new Date().toISOString() })
1938            .eq('id', this.contractId);
1939        }
1940
1941        this.emailSuccess = true;
1942        setTimeout(() => {
1943          this.emailSuccess = false;
1944          this.render();
1945        }, 3000);
1946      } catch (error) {
1947        console.error('Error sending email:', error);
1948        this.errorMessage = this.getUIText('errorSendingEmail');
1949      } finally {
1950        this.isSendingEmail = false;
1951        this.render();
1952      }
1953    }
1954
1955    goBack() {
1956      if (this.step > 1) {
1957        if (this.step === 7) {
1958          this.step = 6;
1959        } else if (this.step === 6) {
1960          this.step = 5;
1961        } else {
1962          this.step--;
1963        }
1964        this.errorMessage = null;
1965        this.formErrors = {};
1966        this.render();
1967      }
1968    }
1969
1970    reset() {
1971      this.step = 1;
1972      this.selectedBrand = null;
1973      this.selectedType = null;
1974      this.selectedDefect = null;
1975      this.selectedCondition = null;
1976      this.finalPrice = null;
1977      this.sellerData = {
1978        firstName: '',
1979        lastName: '',
1980        email: '',
1981        phone: '',
1982        street: '',
1983        city: '',
1984        postalCode: '',
1985        country: '',
1986        iban: '',
1987        pickupDate: '',
1988        gdprConsent: false
1989      };
1990      this.contractData = null;
1991      this.contractId = null;
1992      this.errorMessage = null;
1993      this.formErrors = {};
1994      this.downloadSuccess = false;
1995      this.emailSuccess = false;
1996      this.render();
1997    }
1998  }
1999
2000  window.CoffeeBuybackWidget = {
2001    init: function(options) {
2002      if (!options.supabaseUrl) {
2003        options.supabaseUrl = 'https://your-project.supabase.co';
2004      }
2005      if (!options.supabaseKey) {
2006        options.supabaseKey = 'your-anon-key';
2007      }
2008      window.coffeeWidget = new CoffeeBuybackWidget(options);
2009    }
2010  };
2011})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.