1/* ââââââââââââââââââââââââââââââââââââââ 2 Umowy.AI â Main JS 3 ââââââââââââââââââââââââââââââââââââââ */ 4 5(function () { 6 'use strict'; 7 8 // ââ i18n ââ 9 // Wersje inne niż polska sÄ tymczasowo wyÅÄ czone (tÅumaczenia niekompletne). 10 // Przywrócenie: true tutaj + odkomentowanie przeÅÄ czników w index.html 11 // + przywrócenie tagów hreflang w <head> i wpisów w sitemap.xml. 12 const MULTILANG_ENABLED = false; 13 14 const ALL_LANGS = ['pl', 'en', 'ua', 'ru']; 15 const SUPPORTED_LANGS = MULTILANG_ENABLED ? ALL_LANGS : ['pl']; 16 // Kod pliku 'ua' to kod kraju; atrybut lang/hreflang wymaga ISO 639-1 ('uk'). 17 const HTML_LANG = { pl: 'pl', en: 'en', ua: 'uk', ru: 'ru' }; 18 19 // Przy wyÅÄ czonych tÅumaczeniach wymuszamy 'pl' zamiast czytaÄ localStorage â 20 // inaczej wracajÄ cy użytkownik z zapisanym 'en' dalej dostawaÅby tÄ wersjÄ. 21 // Gdy wÅÄ czone, ?lang= musi wygrywaÄ z localStorage, żeby udostÄpniony link 22 // otwieraÅ siÄ w jÄzyku nadawcy, a nie odbiorcy. 23 let currentLang = MULTILANG_ENABLED 24 ? (langFromUrl() || localStorage.getItem('umowy-ai-lang') || detectLang()) 25 : 'pl'; 26 let translations = {}; 27 // Czy strona zostaÅa już nadpisana tÅumaczeniem z JSON (jÄzyk inny niż polski) 28 let translationsApplied = false; 29 30 function langFromUrl() { 31 const value = new URLSearchParams(location.search).get('lang'); 32 return SUPPORTED_LANGS.includes(value) ? value : null; 33 } 34 35 function detectLang() { 36 const nav = navigator.language?.slice(0, 2) || 'pl'; 37 return SUPPORTED_LANGS.includes(nav) ? nav : 'pl'; 38 } 39 40 // Utrzymuje ?lang=, <html lang>, canonical i og:url w zgodzie z aktualnym 41 // jÄzykiem. Bez aktualizacji canonical każda wersja ?lang= wskazywaÅaby na 42 // polski adres i hreflang zostaÅby przez wyszukiwarki zignorowany. 43 const OG_LOCALE = { pl: 'pl_PL', en: 'en_US', ua: 'uk_UA', ru: 'ru_RU' }; 44 45 function syncLangUrl(lang) { 46 document.documentElement.lang = HTML_LANG[lang] || 'pl'; 47 48 const url = new URL(location.href); 49 if (lang === 'pl') url.searchParams.delete('lang'); 50 else url.searchParams.set('lang', lang); 51 url.hash = ''; 52 history.replaceState(null, '', url.pathname + url.search + location.hash); 53 54 const absolute = 'https://umowy.ai/' + (lang === 'pl' ? '' : '?lang=' + lang); 55 const canonical = document.querySelector('link[rel="canonical"]'); 56 if (canonical) canonical.href = absolute; 57 const ogUrl = document.querySelector('meta[property="og:url"]'); 58 if (ogUrl) ogUrl.content = absolute; 59 const ogLocale = document.querySelector('meta[property="og:locale"]'); 60 if (ogLocale) ogLocale.content = OG_LOCALE[lang] || 'pl_PL'; 61 } 62 63 async function loadTranslations(lang) { 64 // Zabezpieczenie na wypadek wywoÅania z pominiÄciem UI (stary localStorage, 65 // link z ?lang=, konsola) â przy wyÅÄ czonych tÅumaczeniach zawsze polski. 66 if (!MULTILANG_ENABLED) lang = 'pl'; 67 68 // Polski jest jÄzykiem źródÅowym strony â treÅÄ siedzi w HTML i jest 69 // zawsze aktualna. Nie nadpisujemy jej danymi z pl.json (plik bywa 70 // starszy niż HTML). TÅumaczenia z JSON aplikujemy tylko dla innych jÄzyków. 71 if (lang === 'pl') { 72 localStorage.setItem('umowy-ai-lang', 'pl'); 73 if (translationsApplied) { 74 // Strona byÅa przeÅÄ czona na inny jÄzyk â przeÅadowanie przywraca 75 // oryginalny polski HTML w caÅoÅci. Åadujemy adres bez ?lang, żeby 76 // po odÅwieżeniu parametr nie przywróciÅ poprzedniego jÄzyka. 77 const url = new URL(location.href); 78 url.searchParams.delete('lang'); 79 location.replace(url); 80 return; 81 } 82 currentLang = 'pl'; 83 syncLangUrl('pl'); 84 updateLangButtons(); 85 return; 86 } 87 try { 88 const res = await fetch(`i18n/${lang}.json?v=4`); 89 if (!res.ok) throw new Error(); 90 translations = await res.json(); 91 currentLang = lang; 92 localStorage.setItem('umowy-ai-lang', lang); 93 applyTranslations(); 94 translationsApplied = true; 95 syncLangUrl(lang); 96 updateLangButtons(); 97 } catch { 98 if (lang !== 'pl') loadTranslations('pl'); 99 } 100 } 101 102 function applyTranslations() { 103 // Simple text nodes via data-i18n 104 document.querySelectorAll('[data-i18n]').forEach(function(el) {
105 const key = el.getAttribute('data-i18n'); 106 const val = getNestedValue(translations, key); 107 if (val) el.textContent = val; 108 }); 109 110 // Hero title â built from safe text values 111 buildHeroTitle(); 112 113 // Features 114 applyFeatureTranslations(); 115 116 // Showcase tabs 117 applyShowcaseTranslations(); 118 119 // Pricing 120 applyPricingTranslations(); 121 122 // Implementation terminal 123 buildTerminal(); 124 125 // FAQ 126 applyFaqTranslations(); 127 128 // Footer 129 applyFooterTranslations(); 130 } 131 132 // ââ Hero title typewriter rotation ââ 133 var _heroTypewriterTimer = null; 134 135 function buildHeroTitle() { 136 var heroTitle = document.getElementById('hero-title'); 137 if (!heroTitle || !translations.hero) return; 138 139 // Cancel any running typewriter before rebuilding (e.g. on lang switch) 140 if (_heroTypewriterTimer !== null) { 141 clearTimeout(_heroTypewriterTimer); 142 _heroTypewriterTimer = null; 143 } 144 145 var headlines = translations.hero.rotating_headlines || []; 146 // Prepend the primary headline; accent is optional (empty string = no accent span) 147 var accentText = translations.hero.title_accent || ''; 148 var primary = { 149 accent: accentText.length > 0 ? accentText : null, 150 main: translations.hero.title_main || 'Wybierz, co potrzebujesz.' 151 }; 152 153 var allHeadlines = [primary].concat( 154 headlines.map(function(h) { return { accent: null, main: h }; }) 155 ); 156 157 // Start with empty title â typewriter writes first headline from scratch on load 158 _renderHeroTitle(heroTitle, primary, 0); 159 _startHeroTypewriter(heroTitle, allHeadlines); 160 } 161 162 function _getHeadlineFullLength(headline) { 163 return (headline.accent ? headline.accent.length : 0) + headline.main.length; 164 } 165 166 function _renderHeroTitle(heroTitle, headline, charCount) { 167 heroTitle.textContent = ''; 168 169 if (headline.accent) { 170 var accentLen = headline.accent.length; 171 var accentSpan = document.createElement('span'); 172 accentSpan.className = 'accent'; 173 if (charCount <= accentLen) { 174 accentSpan.textContent = headline.accent.substring(0, charCount); 175 heroTitle.appendChild(accentSpan); 176 } else { 177 accentSpan.textContent = headline.accent; 178 heroTitle.appendChild(accentSpan); 179 var mainSlice = headline.main.substring(0, charCount - accentLen); 180 mainSlice.split('\n').forEach(function(part, i, arr) { 181 heroTitle.appendChild(document.createTextNode(part)); 182 if (i < arr.length - 1) heroTitle.appendChild(document.createElement('br')); 183 }); 184 } 185 } else { 186 var textSlice = headline.main.substring(0, charCount); 187 textSlice.split('\n').forEach(function(part, i, arr) { 188 heroTitle.appendChild(document.createTextNode(part)); 189 if (i < arr.length - 1) heroTitle.appendChild(document.createElement('br')); 190 }); 191 } 192 193 var cursor = document.createElement('span'); 194 cursor.className = 'hero__cursor'; 195 heroTitle.appendChild(cursor); 196 } 197 198 function _startHeroTypewriter(heroTitle, headlines) { 199 var TYPE_SPEED = 55; // ms per character when typing 200 var DELETE_SPEED = 28; // ms per character when deleting (faster = more natural) 201 var PAUSE_AFTER = 3000; // ms pause after fully typed 202 var PAUSE_BEFORE = 320; // ms pause before typing next headline 203 204 var idx = 0; 205 var charCount = 0; // start from empty â types in on load 206 var deleting = false; 207 208 function tick() { 209 var headline = headlines[idx]; 210 var fullLen = _getHeadlineFullLength(headline); 211 212 if (!deleting) { 213 charCount++; 214 _renderHeroTitle(heroTitle, headline, charCount); 215 if (charCount >= fullLen) { 216 if (headlines.length === 1) return; // single headline â stop after typing 217 deleting = true; 218 _heroTypewriterTimer = setTimeout(tick, PAUSE_AFTER); 219 } else { 220 _heroTypewriterTimer = setTimeout(tick, TYPE_SPEED); 221 } 222 } else { 223 charCount--; 224 _renderHeroTitle(heroTitle, headline, charCount); 225 if (charCount <= 0) { 226 deleting = false;
227 idx = (idx + 1) % headlines.length; 228 charCount = 0; 229 _heroTypewriterTimer = setTimeout(tick, PAUSE_BEFORE); 230 } else { 231 _heroTypewriterTimer = setTimeout(tick, DELETE_SPEED); 232 } 233 } 234 } 235 236 // Short initial delay so the cursor blinks once before typing starts 237 _heroTypewriterTimer = setTimeout(tick, 600); 238 } 239 240 function applyFeatureTranslations() { 241 if (!translations.features?.items) return; 242 translations.features.items.forEach(function(item, i) { 243 var card = document.querySelector('.feature-card[data-index="' + i + '"]'); 244 if (!card) return; 245 var num = card.querySelector('.feature-card__num'); 246 var title = card.querySelector('.feature-card__title'); 247 var desc = card.querySelector('.feature-card__desc'); 248 var bullets = card.querySelectorAll('.feature-card__bullets li'); 249 if (num) num.textContent = item.num; 250 if (title) title.textContent = item.title; 251 if (desc) desc.textContent = item.desc; 252 bullets.forEach(function(li, j) { 253 if (item.bullets[j]) li.textContent = item.bullets[j]; 254 }); 255 }); 256 } 257 258 function applyShowcaseTranslations() { 259 if (!translations.showcase?.tabs) return; 260 document.querySelectorAll('.showcase__tab').forEach(function(tab, i) { 261 if (translations.showcase.tabs[i]) tab.textContent = translations.showcase.tabs[i]; 262 }); 263 } 264 265 function applyPricingTranslations() { 266 if (!translations.pricing?.plans) return; 267 translations.pricing.plans.forEach(function(plan, i) { 268 var card = document.querySelector('.pricing-card[data-index="' + i + '"]'); 269 if (!card) return; 270 card.querySelector('.pricing-card__name').textContent = plan.name; 271 card.querySelector('.pricing-card__price-value').textContent = plan.price; 272 card.querySelector('.pricing-card__tokens-value').textContent = plan.tokens; 273 var cta = card.querySelector('.pricing-card__cta'); 274 if (cta) cta.textContent = plan.cta; 275 plan.features.forEach(function(f, j) { 276 var fEl = card.querySelectorAll('.pricing-card__feature')[j]; 277 if (fEl) { 278 var textEl = fEl.querySelector('.text'); 279 if (textEl) textEl.textContent = f.text; 280 } 281 }); 282 }); 283 284 // Token table 285 if (translations.pricing.token_rows) { 286 document.querySelectorAll('.pricing__token-row').forEach(function(row, i) { 287 var data = translations.pricing.token_rows[i]; 288 if (!data) return; 289 var label = row.querySelector('.label'); 290 var value = row.querySelector('.value'); 291 if (label) label.textContent = data[0]; 292 if (value) value.textContent = data[1]; 293 }); 294 } 295 } 296 297 function buildTerminal() { 298 if (!translations.implementation?.terminal_lines) return; 299 var body = document.querySelector('.terminal__body'); 300 if (!body) return; 301 302 // Clear existing content 303 body.textContent = ''; 304 305 translations.implementation.terminal_lines.forEach(function(line) { 306 if (!line) { 307 body.appendChild(document.createElement('br')); 308 return; 309 } 310 var span = document.createElement('span'); 311 if (line.startsWith('$')) { 312 span.className = 'terminal__line--prompt'; 313 } else if (line.includes('\u2713')) { // â 314 span.className = 'terminal__line--success'; 315 } else if (line.includes('\u2593')) { // â 316 span.className = 'terminal__line--progress'; 317 } else if (line.includes('oczekuje') || line.includes('pending')) { 318 span.className = 'terminal__line--pending'; 319 } else if (line.startsWith('>')) { 320 span.className = 'terminal__line--info'; 321 } 322 span.textContent = line; 323 body.appendChild(span); 324 body.appendChild(document.createTextNode('\n')); 325 }); 326 327 var cursor = document.createElement('span'); 328 cursor.className = 'terminal__cursor'; 329 body.appendChild(cursor); 330 } 331 332 function applyFaqTranslations() { 333 if (!translations.faq?.items) return; 334 document.querySelectorAll('.faq__item').forEach(function(item, i) { 335 var data = translations.faq.items[i]; 336 if (!data) return; 337 var q = item.querySelector('.faq__question-text'); 338 var a = item.querySelector('.faq__answer-inner'); 339 if (q) q.textContent = data.q; 340 if (a) a.textContent = data.a; 341 }); 342 } 343 344 function applyFooterTranslations() { 345 if (!translations.footer) return; 346 var f = translations.footer;
347 document.querySelectorAll('.footer__col').forEach(function(col, i) { 348 var titleKey = ['col1_title', 'col2_title', 'col3_title'][i]; 349 var linksKey = ['col1_links', 'col2_links', 'col3_links'][i]; 350 var titleEl = col.querySelector('.footer__col-title'); 351 if (titleEl && f[titleKey]) titleEl.textContent = f[titleKey]; 352 var links = col.querySelectorAll('.footer__col-links a'); 353 if (f[linksKey]) links.forEach(function(a, j) { 354 if (f[linksKey][j]) a.textContent = f[linksKey][j]; 355 }); 356 }); 357 } 358 359 function getNestedValue(obj, path) { 360 return path.split('.').reduce(function(o, k) { return o?.[k]; }, obj); 361 } 362 363 function updateLangButtons() { 364 document.querySelectorAll('[data-lang]').forEach(function(btn) { 365 btn.classList.toggle('active', btn.dataset.lang === currentLang); 366 }); 367 } 368 369 // ââ Scroll reveal ââ 370 function initScrollReveal() { 371 var observer = new IntersectionObserver(function(entries) { 372 entries.forEach(function(entry) { 373 if (entry.isIntersecting) { 374 entry.target.classList.add('visible'); 375 observer.unobserve(entry.target); 376 } 377 }); 378 }, { threshold: 0.15 }); 379 380 document.querySelectorAll('.reveal').forEach(function(el) { 381 observer.observe(el); 382 }); 383 } 384 385 // ââ Scroll spy ââ 386 function initScrollSpy() { 387 var sections = document.querySelectorAll('section[id]'); 388 var links = document.querySelectorAll('.nav__link[href^="#"]'); 389 390 var observer = new IntersectionObserver(function(entries) { 391 entries.forEach(function(entry) { 392 if (entry.isIntersecting) { 393 var id = entry.target.id; 394 links.forEach(function(link) { 395 link.classList.toggle('active', link.getAttribute('href') === '#' + id); 396 }); 397 } 398 }); 399 }, { rootMargin: '-20% 0px -80% 0px' }); 400 401 sections.forEach(function(section) { 402 observer.observe(section); 403 }); 404 } 405 406 // ââ Mobile menu ââ 407 function initMobileMenu() { 408 var hamburger = document.querySelector('.nav__hamburger'); 409 var menu = document.querySelector('.nav__mobile-menu'); 410 if (!hamburger || !menu) return; 411 412 hamburger.addEventListener('click', function() { 413 hamburger.classList.toggle('open'); 414 menu.classList.toggle('open'); 415 document.body.style.overflow = menu.classList.contains('open') ? 'hidden' : ''; 416 }); 417 418 menu.querySelectorAll('a').forEach(function(link) { 419 link.addEventListener('click', function() { 420 hamburger.classList.remove('open'); 421 menu.classList.remove('open'); 422 document.body.style.overflow = ''; 423 }); 424 }); 425 } 426 427 // ââ FAQ accordion ââ 428 function initFaqAccordion() { 429 document.querySelectorAll('.faq__question').forEach(function(btn) { 430 btn.addEventListener('click', function() { 431 var item = btn.closest('.faq__item'); 432 var answer = item.querySelector('.faq__answer'); 433 var isOpen = item.classList.contains('open'); 434 435 // Close all 436 document.querySelectorAll('.faq__item.open').forEach(function(openItem) { 437 openItem.classList.remove('open'); 438 openItem.querySelector('.faq__question').setAttribute('aria-expanded', 'false'); 439 openItem.querySelector('.faq__answer').style.maxHeight = '0'; 440 }); 441 442 // Open clicked (if was closed) 443 if (!isOpen) { 444 item.classList.add('open'); 445 btn.setAttribute('aria-expanded', 'true'); 446 answer.style.maxHeight = answer.scrollHeight + 'px'; 447 } 448 }); 449 450 btn.addEventListener('keydown', function(e) { 451 if (e.key === 'Enter' || e.key === ' ') { 452 e.preventDefault(); 453 btn.click(); 454 } 455 }); 456 }); 457 } 458 459 // ââ Showcase tabs ââ 460 function initShowcaseTabs() { 461 var tabs = document.querySelectorAll('.showcase__tab'); 462 var preview = document.querySelector('.showcase__preview-inner'); 463 if (!tabs.length || !preview) return; 464 var placeholders = ['// analysis.output', '// document.output', '// verification.output']; 465 466 function activateTab(index) {
467 tabs.forEach(function(t) { 468 t.classList.remove('active'); 469 t.setAttribute('aria-selected', 'false'); 470 t.setAttribute('tabindex', '-1'); 471 }); 472 tabs[index].classList.add('active'); 473 tabs[index].setAttribute('aria-selected', 'true'); 474 tabs[index].setAttribute('tabindex', '0'); 475 tabs[index].focus(); 476 preview.textContent = placeholders[index] || '// preview'; 477 } 478 479 tabs.forEach(function(tab, i) { 480 tab.addEventListener('click', function() { activateTab(i); }); 481 tab.addEventListener('keydown', function(e) { 482 if (e.key === 'ArrowRight') { e.preventDefault(); activateTab((i + 1) % tabs.length); } 483 if (e.key === 'ArrowLeft') { e.preventDefault(); activateTab((i - 1 + tabs.length) % tabs.length); } 484 }); 485 }); 486 } 487 488 // ââ Language switch ââ 489 function initLangSwitch() { 490 document.addEventListener('click', function(e) { 491 var btn = e.target.closest('[data-lang]'); 492 if (btn) { 493 loadTranslations(btn.dataset.lang); 494 } 495 }); 496 497 // Nav dropdown 498 var langBtn = document.querySelector('.nav__lang'); 499 var langDropdown = document.querySelector('.nav__lang-dropdown'); 500 if (langBtn && langDropdown) { 501 langBtn.addEventListener('click', function(e) { 502 e.stopPropagation(); 503 langDropdown.classList.toggle('open'); 504 }); 505 document.addEventListener('click', function(e) { 506 if (!e.target.closest('.nav__lang-wrap')) { 507 langDropdown.classList.remove('open'); 508 } 509 }); 510 } 511 } 512 513 // ââ Theme toggle ââ 514 // Click handler is inline onclick on the button (no duplicate addEventListener) 515 516 // ââ Features Carousel ââ 517 function initFeaturesPersonas() { 518 var el = document.getElementById('featuresPersona'); 519 if (!el) return; 520 var words = ['firm', 'kancelarii', 'prawników', 'dziaÅów prawnych', 'przedsiÄbiorców']; 521 var prefersReduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; 522 if (prefersReduced) { el.textContent = words[0]; return; } 523 524 var typeSpeed = 55; 525 var deleteSpeed = 30; 526 var holdAfterType = 1100; 527 var holdAfterDelete = 180; 528 var wordIndex = 0; 529 var charIndex = 0; 530 var isDeleting = false; 531 532 function tick() { 533 var word = words[wordIndex]; 534 if (!isDeleting) { 535 charIndex++; 536 el.textContent = word.slice(0, charIndex); 537 if (charIndex === word.length) { 538 isDeleting = true; 539 setTimeout(tick, holdAfterType); 540 return; 541 } 542 setTimeout(tick, typeSpeed); 543 } else { 544 charIndex--; 545 el.textContent = word.slice(0, charIndex); 546 if (charIndex === 0) { 547 isDeleting = false; 548 wordIndex = (wordIndex + 1) % words.length; 549 setTimeout(tick, holdAfterDelete); 550 return; 551 } 552 setTimeout(tick, deleteSpeed); 553 } 554 } 555 tick(); 556 } 557 558 function initFoundersCarousel() { 559 var track = document.getElementById('foundersTrack'); 560 if (!track) return; 561 var section = document.getElementById('founders'); 562 var dots = document.querySelectorAll('.founders-carousel__dot'); 563 var slides = track.querySelectorAll('.founders-carousel__slide'); 564 var current = 0; 565 var autoplayTimer = null; 566 var autoplayDelay = 6000; 567 var startTime = 0; 568 var remainingTime = autoplayDelay; 569 var isVisible = false; 570 var isHovering = false; 571 572 function setDotStates(restart) { 573 dots.forEach(function(d, i) { 574 d.classList.remove('founders-carousel__dot--active', 'founders-carousel__dot--completed'); 575 if (i < current) d.classList.add('founders-carousel__dot--completed'); 576 }); 577 var activeDot = dots[current]; 578 if (activeDot) { 579 if (restart) { void activeDot.offsetWidth; } 580 activeDot.classList.add('founders-carousel__dot--active'); 581 } 582 } 583 584 function goTo(index) { 585 current = (index + slides.length) % slides.length; 586 track.style.transform = 'translateX(-' + (current * 100) + '%)'; 587 remainingTime = autoplayDelay; 588 setDotStates(true); 589 } 590 591 function clearTimer() { 592 if (autoplayTimer) { clearTimeout(autoplayTimer); autoplayTimer = null; } 593 } 594 595 function startAutoplay() { 596 clearTimer(); 597 if (!isVisible || isHovering || slides.length < 2) return; 598 if (section) section.classList.remove('is-paused'); 599 startTime = Date.now(); 600 autoplayTimer = setTimeout(function() { 601 goTo(current + 1); 602 startAutoplay(); 603 }, remainingTime); 604 } 605 606 function pauseAutoplay() { 607 if (autoplayTimer) { 608 clearTimeout(autoplayTimer); 609 autoplayTimer = null;
610 remainingTime = Math.max(0, remainingTime - (Date.now() - startTime)); 611 } 612 if (section) section.classList.add('is-paused'); 613 } 614 615 dots.forEach(function(dot, i) { 616 dot.addEventListener('click', function() { 617 goTo(i); 618 startAutoplay(); 619 }); 620 }); 621 622 if (section) { 623 section.addEventListener('mouseenter', function() { 624 isHovering = true; 625 pauseAutoplay(); 626 }); 627 section.addEventListener('mouseleave', function() { 628 isHovering = false; 629 startAutoplay(); 630 }); 631 } 632 633 setDotStates(false); 634 635 if ('IntersectionObserver' in window && section) { 636 var observer = new IntersectionObserver(function(entries) { 637 entries.forEach(function(entry) { 638 isVisible = entry.isIntersecting; 639 if (isVisible) { 640 if (!autoplayTimer) { remainingTime = autoplayDelay; setDotStates(true); } 641 startAutoplay(); 642 } else { 643 pauseAutoplay(); 644 } 645 }); 646 }, { threshold: 0.35 }); 647 observer.observe(section); 648 } else { 649 isVisible = true; 650 setDotStates(true); 651 startAutoplay(); 652 } 653 } 654 655 // ââ MCP terminal (§05) â interaktywne demo klienta AI + serwera MCP ââ 656 // Dane zmockowane, ale prawdziwe i aktualne (zweryfikowane: art. 476 KC, 657 // art. 113 ust. 1 VAT â 240 000 zÅ od 01.01.2026, art. 27 ust. 1 UPK). 658 var MCP_SCENARIOS = [ 659 { 660 preset: 'Podaj treÅÄ art. 476 KC', 661 prompt: 'Podaj treÅÄ art. 476 KC', 662 call: 'get_article(article_number: "476", legal_act_code: "KC")', 663 action: 'Pobieram peÅnÄ , aktualnÄ treÅÄ artykuÅu z bazy', 664 meta: 'Art. 476 Kodeksu cywilnego · obowiÄ zuje · Dz.U. 2025/1071', 665 quote: 'DÅużnik dopuszcza siÄ zwÅoki, gdy nie speÅnia Åwiadczenia w terminie, a jeżeli termin nie jest oznaczony â gdy nie speÅnia Åwiadczenia niezwÅocznie po wezwaniu przez wierzyciela. Nie dotyczy to wypadku, gdy opóźnienie jest nastÄpstwem okolicznoÅci, za które dÅużnik odpowiedzialnoÅci nie ponosi.', 666 answer: 'Art. 476 KC definiuje zwÅokÄ dÅużnika. Kluczowe rozróżnienie: zwÅoka to opóźnienie zawinione â gdy przyczyna leży poza dÅużnikiem, mamy zwykÅe opóźnienie, a nie zwÅokÄ.', 667 citation: 'art. 476 KC' 668 }, 669 { 670 preset: 'Od jakiej kwoty muszÄ zarejestrowaÄ siÄ do VAT?', 671 prompt: 'Od jakiej kwoty muszÄ zarejestrowaÄ siÄ do VAT?', 672 call: 'search_legal_provisions(query: "limit zwolnienia podmiotowego z VAT")', 673 action: 'WyszukujÄ wÅaÅciwy przepis â dopasowanie po sensie pytania', 674 optimized: 'art. 113 ust. 1 ustawy o VAT', 675 meta: 'Art. 113 ust. 1 ustawy o VAT · obowiÄ zuje · Dz.U. 2025/896', 676 quote: 'Zwalnia siÄ od podatku sprzedaż dokonywanÄ przez podatników, u których wartoÅÄ sprzedaży nie przekroczyÅa ÅÄ cznie w poprzednim roku podatkowym kwoty 240 000 zÅ.', 677 answer: 'Limit zwolnienia podmiotowego z VAT to 240 000 zÅ wartoÅci sprzedaży w poprzednim roku. Uwaga na aktualnoÅÄ: od 1 stycznia 2026 r. próg wzrósÅ z 200 000 zÅ do 240 000 zÅ (ustawa z 24.06.2025). Po jego przekroczeniu rejestracja jako podatnik VAT czynny jest obowiÄ zkowa.', 678 citation: 'art. 113 ust. 1 VAT' 679 }, 680 { 681 preset: 'Ile mam czasu na zwrot rzeczy kupionej online?', 682 prompt: 'Ile mam czasu na zwrot rzeczy kupionej w sklepie internetowym?', 683 call: 'search_legal_provisions(query: "odstÄ pienie od umowy zawartej na odlegÅoÅÄ")', 684 action: 'WyszukujÄ wÅaÅciwy przepis â dopasowanie po sensie pytania', 685 optimized: 'art. 27 ustawy o prawach konsumenta', 686 meta: 'Art. 27 ust. 1 ustawy o prawach konsumenta · obowiÄ zuje · Dz.U. 2024/1796', 687 quote: 'Konsument, który zawarÅ umowÄ na odlegÅoÅÄ lub poza lokalem przedsiÄbiorstwa, może w terminie 14 dni odstÄ piÄ od niej bez podawania przyczyny i bez ponoszenia kosztów.', 688 answer: 'Masz 14 dni na odstÄ pienie bez podawania przyczyny, liczÄ c od objÄcia rzeczy w posiadanie. WyjÄ tek: przy nieumówionej wizycie przedsiÄbiorcy w domu termin wynosi 30 dni. JeÅli sprzedawca nie poinformowaÅ CiÄ o tym prawie â wydÅuża siÄ aż do 12 miesiÄcy.', 689 citation: 'art. 27 ust. 1 UPK' 690 } 691 ]; 692 693 function initMcpTerminal() { 694 var root = document.querySelector('[data-mcp-terminal]'); 695 if (!root) return; 696 var output = root.querySelector('[data-mcp-output]'); 697 var presetsWrap = root.querySelector('[data-mcp-presets]'); 698 var presetsGrid = presetsWrap && presetsWrap.querySelector('.mcp__presets-grid');
699 if (!output || !presetsWrap || !presetsGrid) return; 700 701 var prefersReduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; 702 var SPINNER = ['â ', 'â ', 'â ¹', 'â ¸', 'â ¼', 'â ´', 'â ¦', 'â §', 'â ', 'â ']; 703 var runId = 0; 704 var timers = []; 705 var userPicked = false; 706 707 function clearTimers() { timers.forEach(clearTimeout); timers = []; } 708 function wait(ms) { return new Promise(function(res) { timers.push(setTimeout(res, ms)); }); } 709 710 function el(cls, text) { 711 var d = document.createElement('div'); 712 d.className = cls; 713 if (text != null) d.textContent = text; 714 return d; 715 } 716 function glyphLine(cls, glyph, text) { 717 var d = el('mcp__ln ' + cls); 718 var g = document.createElement('span'); 719 g.className = 'mcp__glyph'; 720 g.textContent = glyph; 721 d.appendChild(g); 722 d.appendChild(document.createTextNode(' ' + text)); 723 return d; 724 } 725 function promptLine(text, withCursor) { 726 var d = el('mcp__ln mcp__ln--prompt'); 727 var caret = document.createElement('span'); 728 caret.className = 'mcp__caret'; 729 caret.textContent = '>'; 730 d.appendChild(caret); 731 d.appendChild(document.createTextNode(' ')); 732 var t = document.createElement('span'); 733 if (text != null) t.textContent = text; 734 d.appendChild(t); 735 var cursor = null; 736 if (withCursor) { 737 cursor = document.createElement('span'); 738 cursor.className = 'mcp__cursor'; 739 d.appendChild(cursor); 740 } 741 return { line: d, text: t, cursor: cursor }; 742 } 743 function citeChip(text) { 744 var d = el('mcp__cite'); 745 var mark = document.createElement('span'); 746 mark.className = 'mcp__cite-mark'; 747 mark.textContent = '§'; 748 d.appendChild(mark); 749 d.appendChild(document.createTextNode(' ' + text)); 750 return d; 751 }
752 function appendResults(sc) { 753 if (sc.optimized) { 754 output.appendChild(glyphLine('mcp__ln--opt', 'â¿', 'znaleziono dopasowanie w bazie:')); 755 output.appendChild(el('mcp__ln mcp__ln--sub', sc.meta)); 756 } else { 757 output.appendChild(glyphLine('mcp__ln--meta', 'â¿', sc.meta)); 758 } 759 } 760 761 function renderFinal(sc) { 762 output.textContent = ''; 763 output.appendChild(promptLine(sc.prompt, false).line); 764 output.appendChild(glyphLine('mcp__ln--tool', 'âº', sc.call)); 765 if (sc.action) output.appendChild(glyphLine('mcp__ln--note', 'â³', sc.action)); 766 appendResults(sc); 767 var q = el('mcp__quote', sc.quote); q.classList.add('is-in'); 768 output.appendChild(q); 769 output.appendChild(el('mcp__answer', sc.answer)); 770 var c = citeChip(sc.citation); c.classList.add('is-in'); 771 output.appendChild(c); 772 } 773 774 function setActive(i) { 775 buttons.forEach(function(b, j) { b.classList.toggle('is-active', j === i); }); 776 } 777 778 async function play(i) { 779 var sc = MCP_SCENARIOS[i]; 780 if (!sc) return; 781 clearTimers(); 782 var my = ++runId; 783 setActive(i); 784 output.textContent = ''; 785 786 if (prefersReduced) { renderFinal(sc); return; } 787 788 // 1 â użytkownik âwpisuje" pytanie 789 var p = promptLine('', true); 790 output.appendChild(p.line); 791 for (var c = 0; c < sc.prompt.length; c++) { 792 if (my !== runId) return; 793 p.text.textContent += sc.prompt.charAt(c); 794 await wait(24); 795 } 796 if (p.cursor) p.line.removeChild(p.cursor); 797 await wait(360); 798 if (my !== runId) return; 799 800 // 2 â wywoÅanie narzÄdzia MCP 801 output.appendChild(glyphLine('mcp__ln--tool', 'âº', sc.call)); 802 await wait(240); 803 if (my !== runId) return; 804 805 // 2b â czytelny dla czÅowieka opis tego, co robi narzÄdzie 806 if (sc.action) { 807 output.appendChild(glyphLine('mcp__ln--note', 'â³', sc.action)); 808 await wait(260); 809 if (my !== runId) return; 810 } 811 812 // 3 â retrieval: sam spinner przy â¿, lekko wydÅużony 813 var spin = el('mcp__ln mcp__ln--running'); 814 var corner = document.createElement('span'); 815 corner.className = 'mcp__glyph'; 816 corner.textContent = 'â¿'; 817 spin.appendChild(corner); 818 spin.appendChild(document.createTextNode(' ')); 819 var sg = document.createElement('span'); 820 sg.className = 'mcp__spinner'; 821 spin.appendChild(sg); 822 output.appendChild(spin); 823 for (var t = 0; t < 20; t++) { 824 if (my !== runId) return; 825 sg.textContent = SPINNER[t % SPINNER.length]; 826 await wait(78); 827 } 828 await wait(220); 829 if (my !== runId) return; 830 output.removeChild(spin); 831 832 // 4 â wynik z metadanymi (aktualnoÅÄ, sygnatura Dz.U.) 833 appendResults(sc); 834 await wait(180); 835 if (my !== runId) return; 836 837 // 5 â cytat przepisu (dane) 838 var quote = el('mcp__quote', sc.quote); 839 output.appendChild(quote); 840 timers.push(setTimeout(function() { quote.classList.add('is-in'); }, 20)); 841 await wait(440); 842 if (my !== runId) return; 843 844 // 6 â synteza AI strumieniowo, sÅowo po sÅowie 845 var ans = el('mcp__answer'); 846 output.appendChild(ans); 847 var words = sc.answer.split(' '); 848 for (var w = 0; w < words.length; w++) { 849 if (my !== runId) return; 850 ans.textContent += (w ? ' ' : '') + words[w]; 851 await wait(40); 852 } 853 await wait(200); 854 if (my !== runId) return; 855 856 // 7 â chip z podstawÄ prawnÄ 857 var chip = citeChip(sc.citation); 858 output.appendChild(chip); 859 timers.push(setTimeout(function() { chip.classList.add('is-in'); }, 20)); 860 } 861 862 var buttons = []; 863 MCP_SCENARIOS.forEach(function(sc, i) { 864 var btn = document.createElement('button'); 865 btn.type = 'button'; 866 btn.className = 'mcp__terminal-preset'; 867 btn.textContent = sc.preset; 868 btn.setAttribute('data-mcp-scenario', i); 869 presetsGrid.appendChild(btn); 870 buttons.push(btn); 871 }); 872 873 // Delegacja na kontenerze â odporna na przebudowÄ DOM i pewna w dziaÅaniu 874 presetsWrap.addEventListener('click', function(ev) { 875 var b = ev.target.closest('.mcp__terminal-preset');
876 if (!b || !presetsWrap.contains(b)) return; 877 var idx = parseInt(b.getAttribute('data-mcp-scenario'), 10); 878 if (isNaN(idx)) return; 879 userPicked = true; 880 play(idx); 881 }); 882 883 // stan poczÄ tkowy: gotowy transkrypt #1 (bez pustego okna) 884 renderFinal(MCP_SCENARIOS[0]); 885 setActive(0); 886 887 // pierwsze wejÅcie w viewport â odegraj #1 z animacjÄ (raz) 888 if ('IntersectionObserver' in window && !prefersReduced) { 889 var seen = false; 890 var io = new IntersectionObserver(function(entries) { 891 entries.forEach(function(e) { 892 if (e.isIntersecting && !seen && !userPicked) { seen = true; play(0); } 893 }); 894 }, { threshold: 0.4 }); 895 io.observe(root); 896 } 897 } 898 899 // ââ Versus lead â "czytaj wiÄcej" ââ 900 function initVersusLeadToggles() { 901 document.querySelectorAll('.versus__lead-col-toggle').forEach(function(btn) { 902 var more = btn.previousElementSibling; 903 if (!more || !more.classList.contains('versus__lead-col-more')) return; 904 btn.addEventListener('click', function() { 905 var expanded = btn.getAttribute('aria-expanded') === 'true'; 906 if (expanded) { 907 more.style.maxHeight = '0'; 908 btn.setAttribute('aria-expanded', 'false'); 909 btn.textContent = 'czytaj wiÄcej +'; 910 } else { 911 more.style.maxHeight = more.scrollHeight + 'px'; 912 btn.setAttribute('aria-expanded', 'true'); 913 btn.textContent = 'zwiÅ â'; 914 } 915 }); 916 }); 917 } 918 919 // ââ Init ââ 920 document.addEventListener('DOMContentLoaded', function() { 921 loadTranslations(currentLang); 922 initScrollReveal(); 923 initScrollSpy(); 924 initMobileMenu(); 925 initFaqAccordion(); 926 initShowcaseTabs(); 927 initLangSwitch(); 928 initFoundersCarousel(); 929 initFeaturesPersonas(); 930 initMcpTerminal(); 931 initVersusLeadToggles(); 932 }); 933})();
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.