FoxyTeam

Custom Calculator for WebSites

Sep 24th, 2025 (edited)
382
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
CSS 29.07 KB | Source Code | 0 0
  1. <div id="cost-estimator" class="idi-estimator">
  2.   <style>
  3.     .idi-estimator{
  4.       --c:#0ea5e9; --bg:#0b1220; --panel:#121a2a; --muted:#8aa0bd; --ok:#16a34a; --warn:#f59e0b;
  5.       font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif; color:#e6eefb; border-radius:18px;}
  6.     .idi-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px}
  7.     @media(max-width:900px){.idi-grid{grid-template-columns:1fr}}
  8.     .idi-card{background:var(--panel);border:1px solid rgba(255,255,255,.06);border-radius:16px;padding:16px}
  9.     .idi-title{font-size:22px;font-weight:700;margin:0 0 12px}
  10.     .idi-sub{font-size:12px;color:var(--muted);margin-bottom:12px}
  11.     .idi-row{display:grid;grid-template-columns:1fr auto;gap:10px;align-items:center;margin:10px 0}
  12.     .idi-row label{font-size:14px}
  13.     .idi-row input[type="number"],.idi-row select{width:190px;background:#0e1626;color:#e6eefb;border:1px solid rgba(255,255,255,.08);border-radius:10px;padding:8px 10px}
  14.     .idi-check{display:flex;align-items:center;gap:10px;margin:6px 0}
  15.     .idi-check input{transform:scale(1.2)}
  16.     .idi-total{font-size:28px;font-weight:800}
  17.     .idi-badges{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}
  18.     .idi-badge{background:#0e1626;border:1px solid rgba(255,255,255,.08);padding:4px 8px;border-radius:999px;font-size:12px;color:var(--muted)}
  19.     .idi-breakdown{margin:10px 0 0;padding:0;list-style:none;color:#cfe0ff}
  20.     .idi-breakdown li{display:flex;justify-content:space-between;border-bottom:1px dashed rgba(255,255,255,.09);padding:7px 0}
  21.     .idi-actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:12px}
  22.     .idi-btn{background:var(--c);border:none;color:#001322;font-weight:700;padding:10px 14px;border-radius:12px;cursor:pointer}
  23.     .idi-btn.secondary{background:#0e1626;color:#e6eefb;border:1px solid rgba(255,255,255,.12)}
  24.     .idi-note{font-size:12px;color:var(--muted);margin-top:8px}
  25.     .idi-hint{font-size:12px;color:#a8ffc2}
  26.     .idi-divider{height:1px;background:linear-gradient(90deg,transparent,rgba(255,255,255,.12),transparent);margin:14px 0}
  27.     .idi-right .idi-title{display:flex;justify-content:space-between;align-items:center}
  28.     .idi-right .idi-title small{font-weight:600;color:var(--muted);font-size:12px}
  29.     /* ново: ред за бележки на цяла ширина */
  30.     .idi-row.full{grid-template-columns:1fr}
  31.     .idi-row.full textarea{width:100%;min-height:100px;background:#0e1626;color:#e6eefb;border:1px solid rgba(255,255,255,.08);border-radius:10px;padding:10px}
  32.   </style>
  33.  
  34.   <div class="idi-grid">
  35.     <!-- ЛЯВО: Входове -->
  36.     <div class="idi-card">
  37.       <h3 class="idi-title">Калкулатор за цена на уебсайт</h3>
  38.       <p class="idi-sub">Изберете опции. Сумата се изчислява след избор (BGN & EUR).</p>
  39.  
  40.       <div class="idi-row">
  41.         <label>Тип проект</label>
  42.         <select id="projectType">
  43.           <option value="" selected disabled hidden>— Изберете —</option>
  44.           <option value="landing">Ландинг страница</option>
  45.           <option value="corporate">Корпоративен сайт</option>
  46.           <option value="ecommerce">Онлайн магазин</option>
  47.           <option value="redesign">Редизайн</option>
  48.         </select>
  49.       </div>
  50.  
  51.       <div class="idi-row">
  52.         <label>Брой страници</label>
  53.         <input id="pages" type="number" min="1" max="100" placeholder="напр. 6" value="">
  54.       </div>
  55.  
  56.       <div class="idi-row">
  57.         <label>Езици</label>
  58.         <select id="languages">
  59.           <option value="" selected disabled hidden>— Изберете —</option>
  60.           <option value="1">1</option>
  61.           <option value="2">2</option>
  62.           <option value="3">3</option>
  63.         </select>
  64.       </div>
  65.  
  66.       <div class="idi-row">
  67.         <label>Ниво на дизайн</label>
  68.         <select id="designLevel">
  69.           <option value="" selected disabled hidden>— Изберете —</option>
  70.           <option value="basic">Базов</option>
  71.           <option value="custom">Къстъм</option>
  72.           <option value="premium">Премиум</option>
  73.         </select>
  74.       </div>
  75.  
  76.       <div class="idi-check">
  77.         <input id="ecom" type="checkbox">
  78.         <label for="ecom">Е-магазин (WooCommerce)</label>
  79.       </div>
  80.  
  81.       <div class="idi-row" id="productCountRow" style="display:none">
  82.         <label>Продукти (приблиз.)</label>
  83.         <input id="products" type="number" min="0" step="10" value="0">
  84.       </div>
  85.  
  86.       <div class="idi-check">
  87.         <input id="blog" type="checkbox">
  88.         <label for="blog">Блог</label>
  89.       </div>
  90.  
  91.       <div class="idi-check">
  92.         <input id="booking" type="checkbox">
  93.         <label for="booking">Резервации / Записвания</label>
  94.       </div>
  95.  
  96.       <div class="idi-check">
  97.         <input id="payments" type="checkbox">
  98.         <label for="payments">Онлайн плащания (Stripe/PayPal)</label>
  99.       </div>
  100.  
  101.       <div class="idi-check">
  102.         <input id="couriers" type="checkbox">
  103.         <label for="couriers">Интеграция с куриери (Еконт/Спиди)</label>
  104.       </div>
  105.  
  106.       <div class="idi-row">
  107.         <label>Качване на съдържание</label>
  108.         <select id="contentYesNo">
  109.           <option value="" selected disabled hidden>— Изберете —</option>
  110.           <option value="no">Не</option>
  111.           <option value="yes">Да</option>
  112.         </select>
  113.       </div>
  114.  
  115.       <div class="idi-row">
  116.         <label>Имате ли лого?</label>
  117.         <select id="hasLogo">
  118.           <option value="" selected disabled hidden>— Изберете —</option>
  119.           <option value="yes">Да</option>
  120.           <option value="no">Не (нужно е лого)</option>
  121.         </select>
  122.       </div>
  123.  
  124.       <div class="idi-row">
  125.         <label>SEO пакет</label>
  126.         <select id="seoYesNo">
  127.           <option value="" selected disabled hidden>— Изберете —</option>
  128.           <option value="no">Не</option>
  129.           <option value="yes">Да</option>
  130.         </select>
  131.       </div>
  132.  
  133.       <div class="idi-row">
  134.         <label>Оптимизация на скорост</label>
  135.         <select id="speedYesNo">
  136.           <option value="" selected disabled hidden>— Изберете —</option>
  137.           <option value="no">Не</option>
  138.           <option value="yes">Да</option>
  139.         </select>
  140.       </div>
  141.  
  142.       <div class="idi-row">
  143.         <label>Поддръжка (месеци)</label>
  144.         <select id="maintenance">
  145.           <option value="" selected disabled hidden>— Изберете —</option>
  146.           <option value="0">Без</option>
  147.           <option value="3">3 месеца</option>
  148.           <option value="6">6 месеца</option>
  149.           <option value="12">12 месеца</option>
  150.         </select>
  151.       </div>
  152.  
  153.       <div class="idi-row">
  154.         <label>Хостинг (1 година)</label>
  155.         <select id="hosting">
  156.           <option value="" selected disabled hidden>— Изберете —</option>
  157.           <option value="0">Не</option>
  158.           <option value="1">Да</option>
  159.         </select>
  160.       </div>
  161.  
  162.       <!-- НОВО: ДДС -->
  163.       <div class="idi-row">
  164.         <label>ДДС</label>
  165.         <select id="vatMode">
  166.           <option value="" selected disabled hidden>— Изберете —</option>
  167.           <option value="novat">Без ДДС</option>
  168.           <option value="withvat">С ДДС (20%)</option>
  169.         </select>
  170.       </div>
  171.  
  172.       <div class="idi-divider"></div>
  173.  
  174.       <div class="idi-row">
  175.         <label>Срок</label>
  176.         <select id="rush">
  177.           <option value="" selected disabled hidden>— Изберете —</option>
  178.           <option value="standard">Стандартен</option>
  179.           <option value="fast">Бърз (+15%)</option>
  180.           <option value="express">Експрес (+30%)</option>
  181.         </select>
  182.       </div>
  183.  
  184.       <!-- НОВО: Бележки -->
  185.       <div class="idi-row full">
  186.         <label>Бележки / допълнителна информация</label>
  187.         <textarea id="notes" placeholder="Пример: Прехвърляне на съдържание от стар сайт; специфични интеграции; срокове и др."></textarea>
  188.       </div>
  189.  
  190.       <p class="idi-hint">При избор „Онлайн магазин“ се показва поле за брой продукти.</p>
  191.     </div>
  192.  
  193.     <!-- ДЯСНО: Резултат -->
  194.     <div class="idi-card idi-right">
  195.       <h3 class="idi-title">
  196.         Обобщение
  197.         <small id="calcStatus">Live</small>
  198.       </h3>
  199.  
  200.       <div class="idi-badges" id="badges"></div>
  201.       <ul class="idi-breakdown" id="breakdown"></ul>
  202.  
  203.       <div class="idi-divider"></div>
  204.  
  205.       <div class="idi-row">
  206.         <div>
  207.           <div class="idi-sub">Общо (BGN)</div>
  208.           <div class="idi-total" id="totalBGN">—</div>
  209.         </div>
  210.         <div>
  211.           <div class="idi-sub">Общо (EUR)</div>
  212.           <div class="idi-total" id="totalEUR">—</div>
  213.         </div>
  214.       </div>
  215.  
  216.       <div class="idi-actions">
  217.         <button class="idi-btn" id="copyQuote">Копирай офертата</button>
  218.         <a id="emailQuote" class="idi-btn secondary" href="#" target="_blank" rel="noopener">Изпрати по имейл</a>
  219.         <button class="idi-btn secondary" id="pdfExport" type="button">Експорт PDF</button>
  220.         <button class="idi-btn secondary" id="resetForm" type="button">Нулирай</button>
  221.       </div>
  222.  
  223.       <p class="idi-note">* Цените са ориентировъчни. Финална оферта след кратък разговор за обхвата.</p>
  224.       <p class="idi-note">* EUR е изчислено при 1 EUR = 1.95583 BGN.</p>
  225.     </div>
  226.   </div>
  227.  
  228.   <script>
  229.     (function(){
  230.       // ===== ЦЕНООБРАЗУВАНЕ =====
  231.       const RATE_EUR = 1.95583;
  232.       const VAT_RATE = 0.20; // 20%
  233.       const PRICING = {
  234.         baseFee: { landing: 240, corporate: 320, ecommerce: 420, redesign: 280 },
  235.         pagePrice: { basic: 120, custom: 180, premium: 260 },
  236.         multiLangFactorPerExtraLang: 0.35,
  237.         ecommerce: {
  238.           enableAddon: 800,
  239.           productTiers: [
  240.             { max: 0, add: 0 },
  241.             { max: 50, add: 200 },
  242.             { max: 200, add: 500 },
  243.             { max: Infinity, add: 900 }
  244.           ]
  245.         },
  246.         blogAddon: 150,
  247.         bookingAddon: 250,
  248.         paymentsAddon: 200,
  249.         couriersAddon: 180,
  250.         contentUploadYes: 120,
  251.         logoDesign: 300,
  252.         seoYes: 350,
  253.         speedYes: 200,
  254.         maintenanceMonthly: 70,
  255.         hostingYearly: 120,
  256.         rushMultiplier: { standard: 1, fast: 1.15, express: 1.30 }
  257.       };
  258.  
  259.       // ===== ЕЛЕМЕНТИ =====
  260.       const el = id => document.getElementById(id);
  261.       const projectType = el('projectType');
  262.       const pages = el('pages');
  263.       const languages = el('languages');
  264.       const designLevel = el('designLevel');
  265.       const ecom = el('ecom');
  266.       const products = el('products');
  267.       const productCountRow = el('productCountRow');
  268.       const blog = el('blog');
  269.       const booking = el('booking');
  270.       const payments = el('payments');
  271.       const couriers = el('couriers');
  272.       const contentYesNo = el('contentYesNo');
  273.       const hasLogo = el('hasLogo');
  274.       const seoYesNo = el('seoYesNo');
  275.       const speedYesNo = el('speedYesNo');
  276.       const maintenance = el('maintenance');
  277.       const hosting = el('hosting');
  278.       const vatMode = el('vatMode');     // ново
  279.       const rush = el('rush');
  280.       const notes = el('notes');         // ново
  281.  
  282.       const totalBGN = el('totalBGN');
  283.       const totalEUR = el('totalEUR');
  284.       const breakdown = el('breakdown');
  285.       const badges = el('badges');
  286.       const copyBtn = el('copyQuote');
  287.       const emailBtn = el('emailQuote');
  288.       const resetBtn = el('resetForm');
  289.       const pdfBtn = el('pdfExport');
  290.  
  291.       // ===== ПОМОЩНИ =====
  292.       const fmtBGN = n => new Intl.NumberFormat('bg-BG', { style:'currency', currency:'BGN', maximumFractionDigits:0 }).format(n);
  293.       const fmtEUR = n => new Intl.NumberFormat('de-DE', { style:'currency', currency:'EUR', maximumFractionDigits:0 }).format(n);
  294.       const toEUR = bgn => bgn / RATE_EUR;
  295.       const clampInt = (v,min,max)=>{ v=parseInt(v||0,10); if(isNaN(v)) v=min; return Math.min(Math.max(v,min),max); };
  296.       const escapeHtml = s => String(s).replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'&#39;'}[m]));
  297.      const maps = {
  298.        project: {landing:'Ландинг страница', corporate:'Корпоративен сайт', ecommerce:'Онлайн магазин', redesign:'Редизайн'},
  299.        design: {basic:'Базов', custom:'Къстъм', premium:'Премиум'},
  300.        rush: {standard:'Стандартен', fast:'Бърз (+15%)', express:'Експрес (+30%)'},
  301.        yn: {yes:'Да', no:'Не'}
  302.      };
  303.  
  304.      function labelProject(key){ return maps.project[key]||key; }
  305.  
  306.      function updateBadges(){
  307.        const list = [];
  308.        const badge = t => `<span class="idi-badge">${t}</span>`;
  309.        if(projectType.value) list.push(badge(labelProject(projectType.value)));
  310.        if(designLevel.value) list.push(badge(maps.design[designLevel.value]));
  311.        if(parseInt(languages.value,10) > 1) list.push(badge(languages.value + ' езика'));
  312.        if(ecom.checked) list.push(badge('e-магазин'));
  313.        if(blog.checked) list.push(badge('блог'));
  314.        if(booking.checked) list.push(badge('резервации'));
  315.        if(payments.checked) list.push(badge('плащания'));
  316.        if(couriers.checked) list.push(badge('Еконт/Спиди'));
  317.        if(contentYesNo.value==='yes') list.push(badge('качване съдърж.'));
  318.        if(hasLogo.value) list.push(badge('лого: '+(hasLogo.value==='yes'?'да':'не')));
  319.        if(seoYesNo.value==='yes') list.push(badge('SEO'));
  320.        if(speedYesNo.value==='yes') list.push(badge('скорост'));
  321.        const m = parseInt(maintenance.value,10)||0;
  322.        if(m>0) list.push(badge('поддръжка '+m+'м.'));
  323.        if(hosting.value==='1') list.push(badge('хостинг'));
  324.        if(vatMode.value==='withvat') list.push(badge('с ДДС'));
  325.        if(vatMode.value==='novat') list.push(badge('без ДДС'));
  326.        if(rush.value) list.push(badge(rush.value));
  327.        badges.innerHTML = list.join('');
  328.      }
  329.  
  330.      function isReadyToCalculate(){
  331.        if(!projectType.value || !designLevel.value) return false;
  332.        const p = parseInt(pages.value||'0',10);
  333.        if(!p || p<1) return false;
  334.        return true;
  335.      }
  336.  
  337.      function gatherSelections(p, langs, rushKey){
  338.        const sel = {
  339.          'Тип проект': labelProject(projectType.value||''),
  340.          'Брой страници': p,
  341.          'Езици': langs || '1',
  342.          'Ниво на дизайн': maps.design[designLevel.value||''] || '',
  343.          'Е-магазин': ecom.checked ? 'Да' : 'Не'
  344.        };
  345.        if(ecom.checked) sel['Продукти (прибл.)'] = clampInt(products.value||'0',0,100000);
  346.  
  347.        sel['Блог'] = blog.checked ? 'Да' : 'Не';
  348.        sel['Резервации/Записвания'] = booking.checked ? 'Да' : 'Не';
  349.        sel['Онлайн плащания'] = payments.checked ? 'Да' : 'Не';
  350.        sel['Куриери (Еконт/Спиди)'] = couriers.checked ? 'Да' : 'Не';
  351.        sel['Качване на съдържание'] = maps.yn[contentYesNo.value||'no'];
  352.        sel['Имате ли лого?'] = (hasLogo.value==='no') ? 'Не' : (hasLogo.value==='yes' ? 'Да' : '');
  353.        sel['SEO пакет'] = maps.yn[seoYesNo.value||'no'];
  354.        sel['Оптимизация на скорост'] = maps.yn[speedYesNo.value||'no'];
  355.  
  356.        const m = parseInt(maintenance.value||'0',10);
  357.        sel['Поддръжка (месеци)'] = m>0 ? m : 'Без';
  358.        sel['Хостинг (1 година)'] = hosting.value==='1' ? 'Да' : 'Не';
  359.        sel['ДДС'] = vatMode.value==='withvat' ? 'С ДДС (20%)' : (vatMode.value==='novat' ? 'Без ДДС' : '');
  360.        sel['Срок'] = maps.rush[rushKey] || maps.rush.standard;
  361.  
  362.        const note = (notes.value||'').trim();
  363.        if(note) sel['Бележки'] = note;
  364.  
  365.        return sel;
  366.      }
  367.  
  368.      function calculate(){
  369.        // показване на продукти при е-магазин
  370.        productCountRow.style.display = ecom.checked ? 'grid' : 'none';
  371.  
  372.        if(!isReadyToCalculate()){
  373.          breakdown.innerHTML = '';
  374.          totalBGN.textContent = '';
  375.          totalEUR.textContent = '';
  376.          badges.innerHTML = '';
  377.          return;
  378.        }
  379.  
  380.        const pt = projectType.value;
  381.        const p = clampInt(pages.value,1,100);
  382.        const langs = clampInt(languages.value||'1',1,10);
  383.        const dl = designLevel.value;
  384.        const isEcom = ecom.checked;
  385.        const prodCount = clampInt(products.value||'0',0,100000);
  386.        const hasBlog = blog.checked;
  387.        const hasBooking = booking.checked;
  388.        const hasPayments = payments.checked;
  389.        const hasCouriers = couriers.checked;
  390.        const contentYN = contentYesNo.value;
  391.        const logo = hasLogo.value;
  392.        const seoYN = seoYesNo.value;
  393.        const speedYN = speedYesNo.value;
  394.        const maintMonths = maintenance.value === '' ? 0 : clampInt(maintenance.value,0,60);
  395.        const hasHosting = hosting.value === '1';
  396.        const rushKey = rush.value || 'standard';
  397.        const includeVAT = (vatMode.value === 'withvat');
  398.  
  399.        const items = [];
  400.  
  401.        const base = PRICING.baseFee[pt];
  402.        items.push(['База ('+labelProject(pt)+')', base]);
  403.  
  404.        const perPage = PRICING.pagePrice[dl];
  405.        const pagesCost = p * perPage;
  406.        items.push([`Страници (${p} × ${perPage} BGN)`, pagesCost]);
  407.  
  408.        if(langs > 1){
  409.          const extraLangs = langs - 1;
  410.          const langAdd = Math.round(pagesCost * PRICING.multiLangFactorPerExtraLang * extraLangs);
  411.          items.push([`Доп. езици (+${extraLangs})`, langAdd]);
  412.        }
  413.  
  414.        if(isEcom){
  415.          let eAdd = PRICING.ecommerce.enableAddon;
  416.          for(const t of PRICING.ecommerce.productTiers){ if(prodCount <= t.max){ eAdd += t.add; break; } }
  417.          items.push([`Е-магазин настройка`, eAdd]);
  418.        }
  419.  
  420.        if(hasBlog)    items.push(['Блог', PRICING.blogAddon]);
  421.        if(hasBooking) items.push(['Резервации/Записвания', PRICING.bookingAddon]);
  422.        if(hasPayments)items.push(['Интеграция на плащания', PRICING.paymentsAddon]);
  423.        if(hasCouriers)items.push(['Куриери (Еконт/Спиди)', PRICING.couriersAddon]);
  424.  
  425.        if(contentYN==='yes') items.push([`Качване на съдържание`, PRICING.contentUploadYes]);
  426.        if(logo==='no')       items.push([`Изработка на лого`, PRICING.logoDesign]);
  427.        if(seoYN==='yes')     items.push([`SEO пакет`, PRICING.seoYes]);
  428.        if(speedYN==='yes')   items.push([`Оптимизация на скорост`, PRICING.speedYes]);
  429.  
  430.        if(maintMonths>0) items.push([`Поддръжка (${maintMonths} месеца)`, maintMonths * PRICING.maintenanceMonthly]);
  431.        if(hasHosting)    items.push([`Хостинг (1 година)`, PRICING.hostingYearly]);
  432.  
  433.        // междинна сума и срок
  434.        const sub = items.reduce((a,[_t,v]) => a + v, 0);
  435.        const mult = PRICING.rushMultiplier[rushKey] || 1;
  436.        const baseTotal = Math.round(sub * mult);
  437.  
  438.        // ДДС
  439.        let vat = 0, grand = baseTotal;
  440.        if(includeVAT){
  441.          vat = Math.round(baseTotal * VAT_RATE);
  442.          grand = baseTotal + vat;
  443.        }
  444.  
  445.        // рендер
  446.        breakdown.innerHTML =
  447.          items.map(([t,v])=>`<li><span>${escapeHtml(t)}</span><strong>${fmtBGN(v)}</strong></li>`).join('') +
  448.          (mult>1 ? `<li><span>Коеф. срок</span><strong>× ${mult.toFixed(2)}</strong></li>` : '') +
  449.          (includeVAT ? `<li><span>ДДС (20%)</span><strong>${fmtBGN(vat)}</strong></li>` : '');
  450.  
  451.        totalBGN.textContent = fmtBGN(grand);
  452.        totalEUR.textContent = fmtEUR(toEUR(grand));
  453.  
  454.        updateBadges();
  455.  
  456.        // параметри за имейл/копиране
  457.        const selections = gatherSelections(p, langs, rushKey);
  458.        const subject = encodeURIComponent('Запитване: калкулатор за уебсайт');
  459.        const body = encodeURIComponent(makeEmailBody(items, mult, includeVAT, vat, grand, selections));
  460.        emailBtn.href = `mailto:[email protected]?subject=${subject}&body=${body}`;
  461.  
  462.        copyBtn.onclick = () => {
  463.          const txt = makePlainText(items, mult, includeVAT, vat, grand, selections);
  464.          navigator.clipboard.writeText(txt).then(()=>{
  465.            copyBtn.textContent='Копирано!';
  466.            setTimeout(()=>copyBtn.textContent='Копирай офертата', 1200);
  467.          });
  468.        };
  469.      }
  470.  
  471.      function makeEmailBody(items, mult, includeVAT, vat, total, sel){
  472.        const lines = [];
  473.        lines.push('Здравей, Красимир,','','Ориентировъчна калкулация за уебсайт.','', 'Параметри:');
  474.        for(const [k,v] of Object.entries(sel)){ lines.push(`• ${k}: ${v}`); }
  475.        lines.push('', 'Разбивка:');
  476.        for(const [t,v] of items){ lines.push(`• ${t}: ${v} BGN`); }
  477.        if(mult>1) lines.push(`• Коеф. срок: × ${mult.toFixed(2)}`);
  478.        if(includeVAT) lines.push(`• ДДС (20%): ${vat} BGN`);
  479.        lines.push('', `ОБЩО: ${Math.round(total)} BGN (≈ ${Math.round(toEUR(total))} EUR)`, '', 'Ще се радвам да уточним детайлите и да финализираме офертата.');
  480.        return lines.join('\n');
  481.      }
  482.  
  483.      function makePlainText(items, mult, includeVAT, vat, total, sel){
  484.        const lines = [];
  485.        lines.push('Ориентировъчна оферта – уебсайт','Параметри:');
  486.        for(const [k,v] of Object.entries(sel)){ lines.push(`${k}: ${v}`); }
  487.        lines.push('----------------------');
  488.        for(const [t,v] of items){ lines.push(`${t}: ${v} BGN`); }
  489.        if(mult>1) lines.push(`Коеф. срок: × ${mult.toFixed(2)}`);
  490.        if(includeVAT) lines.push(`ДДС (20%): ${vat} BGN`);
  491.        lines.push('----------------------', `ОБЩО: ${Math.round(total)} BGN (≈ ${Math.round(toEUR(total))} EUR)`);
  492.        return lines.join('\n');
  493.      }
  494.  
  495.      // ===== ЕКСПОРТ В PDF =====
  496.      function parseBGNtoNumber(txt){
  497.        const n = (txt||'').replace(/[^\d,.\s]/g,'').replace(/\s/g,'');
  498.        const i = n.lastIndexOf(',');
  499.        const norm = i>-1 ? n.slice(0,i).replace(/[,]/g,'') + '.' + n.slice(i+1) : n.replace(/[,]/g,'');
  500.        return Number(norm) || 0;
  501.      }
  502.      function exportPDF(){
  503.        if(!isReadyToCalculate()){
  504.          alert('Попълнете минимум: Тип проект, Брой страници и Ниво на дизайн.');
  505.          return;
  506.        }
  507.        const root = document.getElementById('cost-estimator');
  508.        const title = root.querySelector('.idi-title')?.textContent.trim() || 'Оферта – уебсайт';
  509.  
  510.        const p = clampInt(pages.value,1,100);
  511.        const langs = clampInt(languages.value||'1',1,10);
  512.        const rushKey = rush.value || 'standard';
  513.        const selections = gatherSelections(p, langs, rushKey);
  514.  
  515.        const rows = [];
  516.        root.querySelectorAll('.idi-breakdown li').forEach(li=>{
  517.          const k = li.querySelector('span')?.textContent.trim();
  518.          const v = li.querySelector('strong')?.textContent.trim();
  519.          if(k && v) rows.push([k, v]);
  520.        });
  521.        const rowsHTML = rows.map(([k,v])=>`<tr><td>${k}</td><td class="r">${v}</td></tr>`).join('');
  522.  
  523.        const totalBGNtxt = totalBGN.textContent.trim();
  524.        const totalBGNnum = parseBGNtoNumber(totalBGNtxt);
  525.        const totalEURnum = Math.round(totalBGNnum / RATE_EUR);
  526.  
  527.        const selHTML = Object.entries(selections)
  528.          .map(([k,v])=>`<div class="row"><span class="k">${k}</span><span class="v">${v}</span></div>`).join('');
  529.  
  530.        const now = new Date();
  531.        const dateStr = new Intl.DateTimeFormat('bg-BG', {year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'}).format(now);
  532.  
  533.        const html = `<!doctype html>
  534. <html lang="bg">
  535. <head>
  536. <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
  537. <title>${title}</title>
  538. <style>
  539.  *{box-sizing:border-box}
  540.  body{font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;margin:0;padding:24px;background:#fff;color:#111}
  541.  .wrap{max-width:820px;margin:0 auto}
  542.  .head{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;margin-bottom:12px}
  543.  .brand{font-weight:800;font-size:20px}
  544.  .meta{color:#666;font-size:12px}
  545.  h2{font-size:16px;margin:18px 0 8px;color:#444}
  546.  .grid{display:grid;grid-template-columns:1fr 1fr;gap:18px}
  547.  .card{border:1px solid #e6e6e6;border-radius:12px;padding:16px}
  548.  .row{display:flex;justify-content:space-between;border-bottom:1px dashed #e6e6e6;padding:6px 0}
  549.  .row .k{color:#444}
  550.  .row .v{font-weight:600}
  551.  table{width:100%;border-collapse:collapse;margin-top:6px}
  552.  td{padding:8px 6px;border-bottom:1px solid #eee}
  553.  td.r{text-align:right;white-space:nowrap}
  554.  .total{font-size:20px;font-weight:900;margin-top:10px}
  555.  .muted{color:#666;font-size:12px;margin-top:6px}
  556.  .footer{margin-top:24px;font-size:12px;color:#666}
  557.  @media print {.no-print{display:none}}
  558. </style>
  559. </head>
  560. <body onload="window.print()">
  561.  <div class="wrap">
  562.    <div class="head">
  563.      <div>
  564.        <div class="brand">${title}</div>
  565.        <div class="meta">Генерирана: ${dateStr} • ${location.host}</div>
  566.      </div>
  567.    </div>
  568.  
  569.    <div class="grid">
  570.      <div class="card">
  571.        <h2>Параметри</h2>
  572.        ${selHTML || '<div class="muted">Няма избрани параметри.</div>'}
  573.      </div>
  574.      <div class="card">
  575.        <h2>Разбивка</h2>
  576.        <table>${rowsHTML || '<tr><td colspan="2" class="r">—</td></tr>'}</table>
  577.        <div class="total">ОБЩО: ${totalBGNtxt || ''} (≈ ${isFinite(totalEURnum)? totalEURnum : ''} EUR)</div>
  578.        <div class="muted">Курс: 1 EUR = ${RATE_EUR} BGN</div>
  579.      </div>
  580.    </div>
  581.  
  582.    <div class="footer">
  583.      * Офертата е ориентировъчна и подлежи на уточнение според обхват, съдържание, интеграции и срокове.
  584.    </div>
  585.  
  586.    <div class="no-print" style="margin-top:16px">
  587.      <button onclick="window.print()">Печат / Запази като PDF</button>
  588.    </div>
  589.  </div>
  590. </body>
  591. </html>`;
  592.  
  593.        const w = window.open('', '_blank', 'width=900,height=900');
  594.        if(!w){ alert('Разрешете pop-up за сайта, за да се отвори PDF прозорец.'); return; }
  595.        w.document.write(html);
  596.        w.document.close();
  597.        w.focus();
  598.      }
  599.  
  600.      // ===== СЪБИТИЯ =====
  601.      ['change','input'].forEach(evt=>{
  602.        document.querySelectorAll('#cost-estimator select, #cost-estimator input, #cost-estimator textarea')
  603.          .forEach(el=>el.addEventListener(evt, calculate));
  604.      });
  605.      projectType.addEventListener('change', ()=>{
  606.        if(projectType.value==='ecommerce'){ ecom.checked = true; }
  607.        calculate();
  608.      });
  609.      ecom.addEventListener('change', calculate);
  610.      if (pdfBtn) pdfBtn.addEventListener('click', exportPDF);
  611.  
  612.      if(resetBtn){
  613.        resetBtn.addEventListener('click', ()=>{
  614.          [projectType,languages,designLevel,hasLogo,seoYesNo,speedYesNo,maintenance,hosting,vatMode,rush].forEach(sel=>{
  615.            if(sel){ sel.selectedIndex = 0; }
  616.          });
  617.          pages.value = '';
  618.          products.value = '0';
  619.          [ecom, blog, booking, payments, couriers].forEach(cb => cb.checked = false);
  620.          document.getElementById('productCountRow').style.display = 'none';
  621.          breakdown.innerHTML = '';
  622.          totalBGN.textContent = '';
  623.          totalEUR.textContent = '';
  624.          badges.innerHTML = '';
  625.          if(notes) notes.value = '';
  626.        });
  627.      }
  628.  
  629.      // init
  630.      document.querySelectorAll('#cost-estimator select, #cost-estimator input, #cost-estimator textarea')
  631.        .forEach(el=>el.setAttribute('autocomplete','off'));
  632.      breakdown.innerHTML=''; totalBGN.textContent=''; totalEUR.textContent='';
  633.    })();
  634.  </script>
  635. </div>
  636.  
Tags: html CSS js
Advertisement
Add Comment
Please, Sign In to add comment