1// app.js 2 3// ÐаÑиÑа Ð¾Ñ Ð¸ÑполÑÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¼Ð¾Ð´ÑÐ»Ñ Ð½Ð° ÑÑоÑÐ¾Ð½Ð½Ð¸Ñ Ð´Ð¾Ð¼ÐµÐ½Ð°Ñ (Domain Lock) 4(function() { 5 const allowedDomains = ['alleyadoma.ru', 'localhost', '127.0.0.1']; 6 const currentHost = window.location.hostname; 7 const isLocalFile = window.location.protocol === 'file:'; 8 9 const isDomainAllowed = isLocalFile || allowedDomains.some(domain => { 10 return currentHost === domain || currentHost.endsWith('.' + domain); 11 }); 12 13 if (!isDomainAllowed) { 14 console.warn('[Security] ÐÑполÑзование данного модÑÐ»Ñ Ð½Ð° ÑÑом домене запÑеÑено.'); 15 document.addEventListener('DOMContentLoaded', (e) => { 16 e.stopImmediatePropagation(); 17 const slider = document.querySelector('.designer-advice-slider'); 18 if (slider) { 19 slider.innerHTML = '<div style="padding: 25px; text-align: center; color: #CF003A; font-weight: 600; background: #FFF; border-radius: 16px; border: 1px solid #DFE0E5;">ÐÑибка безопаÑноÑÑи: ÐÑполÑзование модÑÐ»Ñ Ð½Ð° ÑÑоÑоннем домене запÑеÑено.</div>'; 20 } 21 }, true); 22 } 23})(); 24 25// Ð¥ÐµÐ»Ð¿ÐµÑ ÑкÑаниÑÐ¾Ð²Ð°Ð½Ð¸Ñ HTML Ð´Ð»Ñ Ð¿ÑедоÑвÑаÑÐµÐ½Ð¸Ñ XSS 26function escapeHTML(str) { 27 if (!str) return ''; 28 return str.toString() 29 .replace(/&/g, '&') 30 .replace(/</g, '<') 31 .replace(/>/g, '>') 32 .replace(/"/g, '"') 33 .replace(/'/g, '''); 34} 35 36// Ð¥ÐµÐ»Ð¿ÐµÑ Ð±ÐµÐ·Ð¾Ð¿Ð°Ñного Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÑлÑÑаÑÐµÐ»Ñ ÑобÑÑий (Ð´Ð»Ñ ÑлеменÑов конÑÑÑÑкÑоÑа на боевом ÑайÑе) 37function safeAddListener(element, event, callback) { 38 if (element) { 39 element.addEventListener(event, callback); 40 } 41} 42 43// Ð¥ÐµÐ»Ð¿ÐµÑ Ð¾ÑпÑавки ÑобÑÑий аналиÑики (ЯндекÑ.ÐеÑÑика и Google Analytics) 44function sendAnalyticsEvent(eventName, params = {}) { 45 console.log(`[Analytics] ÐÑпÑавка ÑобÑÑиÑ: ${eventName}`, params); 46 47 // 1. Google Analytics (GA4 / gtag.js) 48 if (typeof gtag === 'function') { 49 gtag('event', eventName, params); 50 } 51 52 // 2. ЯндекÑ.ÐеÑÑика 53 if (typeof ym === 'function') { 54 try { 55 const counters = window.Ya && window.Ya.Metrika && window.Ya.Metrika.counters; 56 if (counters && counters.length > 0) { 57 counters.forEach(counter => { 58 ym(counter.id, 'reachGoal', eventName, params); 59 }); 60 } else { 61 const metrikaCounters = window.Ya && window.Ya._metrika && window.Ya._metrika.counters; 62 if (metrikaCounters) { 63 Object.keys(metrikaCounters).forEach(id => { 64 ym(id, 'reachGoal', eventName, params); 65 }); 66 } else if (window.yandexMetrikaId) { 67 ym(window.yandexMetrikaId, 'reachGoal', eventName, params); 68 } 69 } 70 } catch (e) { 71 console.warn('[Analytics] ÐÑибка пÑи оÑпÑавке в ЯндекÑ.ÐеÑÑикÑ:', e); 72 } 73 } 74} 75 76// 1. ÐниÑиализаÑÐ¸Ñ ÑоÑÑоÑÐ½Ð¸Ñ Ð¿ÑÐ¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ 77const STORAGE_KEY = 'designer_carousel_slides'; 78let slides = []; 79function initSlidesData() { 80 if (slides && slides.length > 0) return; 81 82 // ÐÑÑаемÑÑ Ð²Ð·ÑÑÑ Ð¸Ð· localStorage 83 try { 84 const savedSlides = localStorage.getItem(STORAGE_KEY); 85 if (savedSlides) { 86 slides = JSON.parse(savedSlides); 87 return; 88 } 89 } catch (e) { 90 console.error("ÐÑибка загÑÑзки даннÑÑ Ð¸Ð· LocalStorage:", e); 91 } 92 93 // ÐÑли Ð½ÐµÑ Ð² localStorage, беÑем из data.js 94 if (typeof initialSlidesData !== 'undefined' && Array.isArray(initialSlidesData)) { 95 slides = [...initialSlidesData]; 96 } else { 97 console.warn("[Designer tips] ÐеÑÐµÐ¼ÐµÐ½Ð½Ð°Ñ initialSlidesData не найдена или не ÑвÐ
97»ÑеÑÑÑ Ð¼Ð°ÑÑивом. ÐÑÐ´ÐµÑ Ð¸ÑполÑзован пÑÑÑой ÑлайдеÑ."); 98 slides = []; 99 } 100} 101 102let currentSlideIndex = 0; 103let selectedMarkerId = null; 104let activeExportType = 'json'; // 'json' или 'html' 105let isDragging = false; 106let dragMarkerId = null; 107 108// ÐÑÑоÑÐ¸Ñ Ð´ÐµÐ¹ÑÑвий (Undo / Redo) 109let undoStack = []; 110let redoStack = []; 111const MAX_HISTORY_LIMIT = 30; 112let stateBeforeInput = null; 113let stateBeforeDrag = null; 114 115// ÐвÑомаÑиÑеÑкое пеÑелиÑÑÑвание (Autoplay) 116let autoplayInterval = null; 117let isAutoplayPaused = false; 118let isMouseInsideSlider = false; 119let autoplayProgress = 0; 120const AUTOPLAY_DURATION = 7000; 121const PROGRESS_INTERVAL = 30; // инÑеÑвал Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð² Ð¼Ñ 122 123// ФÑнкÑÐ¸Ñ Ð´Ð»Ñ ÑÐ¾Ñ ÑÐ°Ð½ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½ÑÑ 124function saveToLocalStorage() { 125 try { 126 localStorage.setItem(STORAGE_KEY, JSON.stringify(slides)); 127 } catch (e) { 128 console.error("ÐÑибка ÑÐ¾Ñ ÑÐ°Ð½ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½ÑÑ Ð² LocalStorage:", e); 129 } 130} 131 132// УпÑавление иÑÑоÑией изменений (Undo / Redo) 133function pushHistory() { 134 const stateStr = JSON.stringify(slides); 135 if (undoStack.length > 0 && undoStack[undoStack.length - 1] === stateStr) { 136 return; 137 } 138 undoStack.push(stateStr); 139 if (undoStack.length > MAX_HISTORY_LIMIT) { 140 undoStack.shift(); 141 } 142 redoStack = []; 143 updateHistoryButtons(); 144} 145 146function undo() { 147 if (undoStack.length === 0) return; 148 redoStack.push(JSON.stringify(slides)); 149 const prevState = undoStack.pop(); 150 slides = JSON.parse(prevState); 151 initBuilder(); 152 renderSlider(); 153 updateHistoryButtons(); 154} 155 156function redo() { 157 if (redoStack.length === 0) return; 158 undoStack.push(JSON.stringify(slides)); 159 const nextState = redoStack.pop(); 160 slides = JSON.parse(nextState); 161 initBuilder(); 162 renderSlider(); 163 updateHistoryButtons(); 164} 165 166function updateHistoryButtons() { 167 const undoBtn = document.getElementById('undo-btn'); 168 const redoBtn = document.getElementById('redo-btn'); 169 if (undoBtn) undoBtn.disabled = undoStack.length === 0; 170 if (redoBtn) redoBtn.disabled = redoStack.length === 0; 171} 172 173// УпÑавление авÑопÑокÑÑÑкой (7 ÑекÑнд) 174function startAutoplay(resetProgress = true) { 175 stopAutoplay(); 176 177 // Ðе запÑÑкаем авÑопÑокÑÑÑÐºÑ Ð½Ð° вкладке конÑÑÑÑкÑоÑа 178 const previewTab = document.getElementById('preview-tab'); 179 if (!previewTab || !previewTab.classList.contains('active')) { 180 return; 181 } 182 183 isAutoplayPaused = isMouseInsideSlider; 184 if (isAutoplayPaused) { 185 if (resetProgress) { 186 autoplayProgress = 0; 187 updateProgressBar(0); 188 } 189 return; 190 } 191 192 if (resetProgress) { 193 autoplayProgress = 0; 194 } 195 196 updateProgressBar(autoplayProgress / AUTOPLAY_DURATION); 197 198 autoplayInterval = setInterval(() => { 199 const hasActiveTooltip = document.querySelector('.complect-marker.active') !== null; 200 if (hasActiveTooltip) { 201 // ÐамоÑаживаем пÑогÑеÑÑ, еÑли оÑкÑÑÑо вÑплÑваÑÑее окно 202 return; 203 } 204 205 autoplayProgress += PROGRESS_INTERVAL; 206 updateProgressBar(autoplayProgress / AUTOPLAY_DURATION); 207 208 if (autoplayProgress >= AUTOPLAY_DURATION) { 209 autoplayProgress = 0; 210 goToSlide(currentSlideIndex + 1); 211 } 212 }, PROGRESS_INTERVAL); 213} 214 215function stopAutoplay() { 216 if (autoplayInterval) { 217 clearInterval(autoplayInterval); 218 autoplayInterval = null; 219 } 220} 221 222function pauseAutoplay() { 223 isAutoplayPaused = true; 224 stopAutoplay(); 225} 226 227function resumeAutoplay() { 228 isAutoplayPaused = false; 229 startAutoplay(false); // ÐÑодолжаем Ñ Ñого же меÑÑа 230} 231 232function updateProgressBar(ratio) { 233 const progressBar = document.getElementById('autoplay-progress-bar'); 234 if (progressBar) { 235 const pct = Math.max(0, Math.min(100, ratio * 100)); 236 progressBar.style.width = `${pct}%`; 237 238 // ÐлавнÑй пеÑÐµÑ Ð¾Ð´ ÑвеÑа: Ð¾Ñ ÑвеÑло-ÑалаÑового (75) к наÑÑÑÐµÐ½Ð½Ð¾Ð¼Ñ ÑиÑÐ¼ÐµÐ½Ð½Ð¾Ð¼Ñ Ð·ÐµÐ»ÐµÐ½Ð¾Ð¼Ñ (95)
239 const hue = 75 + (95 - 75) * ratio; 240 const lightness = 55 - (55 - 37) * ratio; 241 const saturation = 50 + (65 - 50) * ratio; 242 243 progressBar.style.backgroundColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`; 244 245 // ÐаÑаÑÑаÑÑее неоновое ÑвеÑение 246 progressBar.style.boxShadow = `0 1px 8px rgba(85, 151, 35, ${0.1 + ratio * 0.45})`; 247 } 248} 249 250// ÐлеменÑÑ DOM Ð´Ð»Ñ ÐºÐ°ÑÑÑели 251let sliderWrapper, sliderPrev, sliderNext, sliderPagination; 252let designerImg, designerName, slideCollectionName, slideCollectionDesc, slideCollectionLink, slideBadge, designerTitleLabel; 253 254// ÐлеменÑÑ DOM Ð´Ð»Ñ Ð²ÐºÐ»Ð°Ð´Ð¾Ðº 255let tabButtons, tabContents; 256 257// ÐлеменÑÑ DOM Ð´Ð»Ñ ÐºÐ¾Ð½ÑÑÑÑкÑоÑа 258let builderCanvas, builderImagePreview, builderMarkersContainer, slideSelect, addSlideBtn, deleteSlideBtn; 259 260// ÐÐ¾Ð»Ñ Ð²Ð²Ð¾Ð´Ð° Ñлайда 261let inputSlideName, inputSlideDesc, inputSlideImage, inputSlideLink, inputSlideBadge, inputSlideDesignerTitle, inputDesignerName, inputDesignerAvatar; 262 263// РедакÑÐ¾Ñ Ð¼Ð°ÑкеÑа 264let markerEditorSection, deleteMarkerBtn, markerCoordX, markerCoordY; 265let inputMarkerTitle, inputMarkerPrice, inputMarkerDiscount, calculatedPriceBadge, inputMarkerLink, fetchProductBtn, importStatusMsg; 266 267// ÐкÑпоÑÑ ÐºÐ¾Ð´Ð° 268let exportTabButtons, exportTextarea, copyCodeBtn; 269 270// ФÑнкÑÐ¸Ñ Ð´Ð»Ñ Ð±ÐµÐ·Ð¾Ð¿Ð°Ñного поиÑка ÑлеменÑов в DOM (вÑзÑваеÑÑÑ Ð¿Ñи DOMContentLoaded) 271function initDOMElements() { 272 sliderWrapper = document.getElementById('slider-wrapper'); 273 sliderPrev = document.getElementById('slider-prev'); 274 sliderNext = document.getElementById('slider-next'); 275 sliderPagination = document.getElementById('slider-pagination'); 276 designerImg = document.getElementById('designer-img'); 277 designerName = document.getElementById('designer-name'); 278 slideCollectionName = document.getElementById('slide-collection-name'); 279 slideCollectionDesc = document.getElementById('slide-collection-desc'); 280 slideCollectionLink = document.getElementById('slide-collection-link'); 281 slideBadge = document.getElementById('slide-badge'); 282 designerTitleLabel = document.getElementById('designer-title'); 283 284 tabButtons = document.querySelectorAll('.tab-btn'); 285 tabContents = document.querySelectorAll('.tab-content'); 286 287 builderCanvas = document.getElementById('builder-canvas'); 288 builderImagePreview = document.getElementById('builder-image-preview'); 289 builderMarkersContainer = document.getElementById('builder-markers-container'); 290 slideSelect = document.getElementById('slide-select'); 291 addSlideBtn = document.getElementById('add-new-slide-btn'); 292 deleteSlideBtn = document.getElementById('delete-slide-btn'); 293 294 inputSlideName = document.getElementById('input-slide-name'); 295 inputSlideDesc = document.getElementById('input-slide-desc'); 296 inputSlideImage = document.getElementById('input-slide-image'); 297 inputSlideLink = document.getElementById('input-slide-link'); 298 inputSlideBadge = document.getElementById('input-slide-badge'); 299 inputSlideDesignerTitle = document.getElementById('input-slide-designer-title'); 300 inputDesignerName = document.getElementById('input-designer-name'); 301 inputDesignerAvatar = document.getElementById('input-designer-avatar'); 302 303 markerEditorSection = document.getElementById('marker-editor-section'); 304 deleteMarkerBtn = document.getElementById('delete-marker-btn'); 305 markerCoordX = document.getElementById('marker-coord-x'); 306 markerCoordY = document.getElementById('marker-coord-y'); 307 inputMarkerTitle = document.getElementById('input-marker-title'); 308 inputMarkerPrice = document.getElementById('input-marker-price'); 309 inputMarkerDiscount = document.getElementById('input-marker-discount'); 310 calculatedPriceBadge = document.getElementById('calculated-price-badge'); 311 inputMarkerLink = document.getElementById('input-marker-link'); 312 fetchProductBtn = document.getElementById('fetch-product-btn'); 313 importStatusMsg = document.getElementById('import-status-msg'); 314 315 exportTabButtons = document.querySelectorAll('.export-tab-btn'); 316 exportTextarea = document.getElementById('export-textarea'); 317 copyCodeBtn = document.getElementById('copy-code-btn'); 318} 319 320// ========================================================================== 321// 2. Ðогика инÑеÑакÑивного ÑлайдеÑа (Live Preview) 322// ========================================================================== 323 324// ФоÑмаÑиÑование ÑиÑел (добавление пÑобелов как ÑазделиÑелей ÑÑÑÑÑ) 325function formatPrice(value) { 326 if (!value) return '0'; 327 // ÐÑиÑÑиÑÑ Ð¾Ñ Ð¿Ñобелов и неÑиÑÑовÑÑ Ñимволов 328 const cleanValue = value.toString().replace(/\s/g, '').replace(/\D/g, ''); 329 if (!cleanValue) return '0';
330 return Number(cleanValue).toLocaleString('ru-RU'); 331} 332 333// ÐпÑеделение опÑималÑного напÑÐ°Ð²Ð»ÐµÐ½Ð¸Ñ ÑÑлÑипа оÑноÑиÑелÑно кооÑÐ´Ð¸Ð½Ð°Ñ Ð¼Ð°ÑкеÑа 334function getTooltipPositionClass(x, y) { 335 // СнаÑала пÑовеÑÑем гоÑизонÑалÑнÑе гÑаниÑÑ Ð´Ð»Ñ Ð±Ð¾ÐºÐ¾Ð²ÑÑ ÑоÑек 336 if (x < 25) return 'pos-right'; // СлиÑком близко к Ð»ÐµÐ²Ð¾Ð¼Ñ ÐºÑÐ°Ñ -> оÑкÑÑваем впÑаво 337 if (x > 75) return 'pos-left'; // СлиÑком близко к пÑÐ°Ð²Ð¾Ð¼Ñ ÐºÑÐ°Ñ -> оÑкÑÑваем влево 338 // ÐоÑог веÑÑикали: еÑли ÑоÑка в веÑÑ Ð½ÐµÐ¹ половине (y < 45%) -> ÑаÑкÑÑваем вниз 339 if (y < 45) return 'pos-bottom'; 340 // Рнижней половине -> ÑаÑкÑÑваем ввеÑÑ 341 return 'pos-top'; 342} 343 344// РендеÑинг Ñлайдов и маÑкеÑов в пÑедпÑоÑмоÑÑе 345function renderSlider() { 346 sliderWrapper.innerHTML = '<div class="autoplay-progress-bar" id="autoplay-progress-bar"></div>'; 347 sliderPagination.innerHTML = ''; 348 349 if (slides.length === 0) { 350 sliderWrapper.innerHTML = '<div class="slider-empty-state">ÐÐµÑ Ð´Ð¾ÑÑÑпнÑÑ Ñлайдов. СоздайÑе Ñлайд в конÑÑÑÑкÑоÑе!</div>'; 351 return; 352 } 353 354 // РендеÑим ÑÐ»Ð°Ð¹Ð´Ñ 355 slides.forEach((slide, sIdx) => { 356 const slideItem = document.createElement('div'); 357 slideItem.className = `slider-item ${sIdx === currentSlideIndex ? 'active' : ''}`; 358 359 const img = document.createElement('img'); 360 const isCurrent = sIdx === currentSlideIndex; 361 const realSrc = slide.image || 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?auto=format&fit=crop&w=1200&q=80'; 362 363 if (isCurrent) { 364 img.src = realSrc; 365 img.setAttribute('fetchpriority', 'high'); 366 } else { 367 img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; 368 img.dataset.src = realSrc; 369 img.loading = 'lazy'; 370 } 371 372 img.alt = slide.name; 373 slideItem.appendChild(img); 374 375 // Создаем конÑÐµÐ¹Ð½ÐµÑ ÑоÑек 376 const markersContainer = document.createElement('div'); 377 markersContainer.className = 'complect-markers'; 378 379 slide.markers.forEach((marker) => { 380 const markerEl = document.createElement('div'); 381 markerEl.className = 'complect-marker'; 382 markerEl.style.setProperty('--x', marker.x); 383 markerEl.style.setProperty('--y', marker.y); 384 385 const btn = document.createElement('span'); 386 btn.className = 'complect-marker__btn'; 387 btn.setAttribute('data-href', marker.link || '#'); 388 markerEl.appendChild(btn); 389 390 // ТÑлÑип 391 const tooltip = document.createElement('div'); 392 const posClass = getTooltipPositionClass(marker.x, marker.y); 393 tooltip.className = `complect-marker__tooltip ${posClass}`; 394 395 // РаÑÑÐµÑ Ñен 396 const discountPercent = parseInt(marker.discount) || 0; 397 const originalPriceFormatted = formatPrice(marker.price); 398 const currentPriceFormatted = formatPrice(marker.priceWithDiscount || marker.price); 399 400 let priceRowHTML = ''; 401 if (discountPercent > 0) { 402 priceRowHTML = ` 403 <span class="tooltip-price-current">${currentPriceFormatted} â½</span> 404 <span class="tooltip-price-old">${originalPriceFormatted} â½</span> 405 <span class="tooltip-discount-badge">-${discountPercent}%</span> 406 `; 407 } else { 408 priceRowHTML = `<span class="tooltip-price-current">${currentPriceFormatted} â½</span>`; 409 } 410 411 tooltip.innerHTML = ` 412 <div class="tooltip-header"> 413 <div class="tooltip-title">${escapeHTML(marker.title)}</div> 414 <button class="tooltip-close" aria-label="ÐакÑÑÑÑ"><i class="fa-solid fa-xmark"></i></button> 415 </div> 416 <div class="tooltip-price-row"> 417 ${priceRowHTML} 418 </div> 419 <div class="tooltip-footer"> 420 <a href="${escapeHTML(marker.link || '#')}" target="_blank" class="tooltip-action-btn" title="ÐеÑейÑи Ð
420º ÑоваÑÑ"> 421 <i class="fa-solid fa-chevron-right"></i> 422 </a> 423 </div> 424 `; 425 426 // ÐÑÑлеживание пеÑÐµÑ Ð¾Ð´Ð° в каÑÑоÑÐºÑ ÑоваÑа 427 tooltip.querySelector('.tooltip-action-btn').addEventListener('click', () => { 428 sendAnalyticsEvent('Advice_Product_Go', { 429 slide_index: sIdx + 1, 430 slide_name: slide.name, 431 product_title: marker.title, 432 product_link: marker.link || '#' 433 }); 434 }); 435 436 // ÐбÑабоÑÑик Ð´Ð»Ñ ÐºÐ»Ð¸ÐºÐ¾Ð² по кÑеÑÑÐ¸ÐºÑ Ð½Ð° мобилÑнÑÑ ÑÑÑÑойÑÑÐ²Ð°Ñ 437 tooltip.querySelector('.tooltip-close').addEventListener('click', (e) => { 438 e.stopPropagation(); 439 markerEl.classList.remove('active'); 440 }); 441 442 // ÐнÑеÑакÑив Ð´Ð»Ñ Ð¼Ð¾Ð±Ð¸Ð»ÑнÑÑ (клик по ÑоÑке пеÑеклÑÑÐ°ÐµÑ ÑÑлÑип) 443 markerEl.addEventListener('click', (e) => { 444 e.stopPropagation(); 445 446 if (e.target.closest('.tooltip-action-btn') || e.target.closest('.tooltip-close')) { 447 return; 448 } 449 450 // ÐакÑÑваем дÑÑгие акÑивнÑе ÑÑлÑÐ¸Ð¿Ñ Ð½Ð° Ñлайде 451 const activeMarkers = slideItem.querySelectorAll('.complect-marker.active'); 452 activeMarkers.forEach(am => { 453 if (am !== markerEl) am.classList.remove('active'); 454 }); 455 456 const isOpening = !markerEl.classList.contains('active'); 457 markerEl.classList.toggle('active'); 458 459 if (isOpening) { 460 sendAnalyticsEvent('Advice_Hotspot_Click', { 461 slide_index: sIdx + 1, 462 slide_name: slide.name, 463 product_title: marker.title, 464 product_price: marker.priceWithDiscount || marker.price 465 }); 466 } 467 }); 468 469 markerEl.appendChild(tooltip); 470 markersContainer.appendChild(markerEl); 471 }); 472 473 slideItem.appendChild(markersContainer); 474 sliderWrapper.appendChild(slideItem); 475 476 // Создаем ÑоÑÐºÑ Ð¿Ð°Ð³Ð¸Ð½Ð°Ñии 477 const bullet = document.createElement('span'); 478 bullet.className = `pagination-bullet ${sIdx === currentSlideIndex ? 'active' : ''}`; 479 bullet.addEventListener('click', () => { 480 goToSlide(sIdx, true); 481 }); 482 sliderPagination.appendChild(bullet); 483 }); 484 485 // ÐбновлÑем инÑоÑмаÑÐ¸Ñ Ð´Ð¸Ð·Ð°Ð¹Ð½ÐµÑа ÑпÑава 486 const activeSlide = slides[currentSlideIndex]; 487 if (activeSlide) { 488 designerImg.src = activeSlide.designer?.avatar || 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=150&q=80'; 489 designerImg.alt = activeSlide.designer?.name || 'ÐизайнеÑ'; 490 designerName.textContent = activeSlide.designer?.name || 'ÐикÑоÑ'; 491 slideCollectionName.textContent = activeSlide.name; 492 slideCollectionDesc.textContent = activeSlide.description; 493 if (slideCollectionLink) slideCollectionLink.href = activeSlide.link || '#'; 494 slideBadge.textContent = activeSlide.badge || 'СÐÐÐТЫ ÐÐÐÐÐÐÐÐ Ð'; 495 designerTitleLabel.textContent = activeSlide.designerTitle || 'ÐÑкÑÑÑÑво инÑеÑÑеÑа'; 496 } 497} 498 499// Ðлик вне маÑкеÑов закÑÑÐ²Ð°ÐµÑ Ð²Ñе акÑивнÑе ÑÑлÑÐ¸Ð¿Ñ 500document.addEventListener('click', () => { 501 const activeMarkers = document.querySelectorAll('.complect-marker.active'); 502 activeMarkers.forEach(m => m.classList.remove('active')); 503}); 504 505// ÐавигаÑÐ¸Ñ 506function goToSlide(index, isManual = false) { 507 // СбÑаÑÑваем и пеÑезапÑÑкаем ÑÐ°Ð¹Ð¼ÐµÑ Ð°Ð²ÑопÑокÑÑÑки 508 const previewTab = document.getElementById('preview-tab'); 509 if (previewTab && previewTab.classList.contains('active')) { 510 startAutoplay(); 511 } 512 513 if (index < 0) { 514 currentSlideIndex = slides.length - 1; 515 } else if (index >= slides.length) { 516 currentSlideIndex = 0; 517 } else { 518 currentSlideIndex = index; 519 } 520 521 // ÐбновлÑем клаÑÑÑ Ð°ÐºÑивноÑÑи 522 const items = sliderWrapper.querySelectorAll('.slider-item');
523 items.forEach((item, idx) => { 524 item.classList.toggle('active', idx === currentSlideIndex); 525 }); 526 527 // Lazy Loading: ÐагÑÑжаем каÑÑÐ¸Ð½ÐºÑ ÑекÑÑего акÑивного Ñлайда 528 const activeItem = items[currentSlideIndex]; 529 if (activeItem) { 530 const activeImg = activeItem.querySelector('img'); 531 if (activeImg && activeImg.dataset.src && activeImg.src !== activeImg.dataset.src) { 532 activeImg.src = activeImg.dataset.src; 533 } 534 } 535 536 // Pre-fetching: УпÑеждаÑÑе загÑÑжаем каÑÑÐ¸Ð½ÐºÑ ÑледÑÑÑего Ñлайда Ð´Ð»Ñ Ð±ÐµÑÑовного пеÑÐµÑ Ð¾Ð´Ð° 537 if (items.length > 1) { 538 const nextIndex = (currentSlideIndex + 1) % items.length; 539 const nextItem = items[nextIndex]; 540 if (nextItem) { 541 const nextImg = nextItem.querySelector('img'); 542 if (nextImg && nextImg.dataset.src && nextImg.src !== nextImg.dataset.src) { 543 nextImg.src = nextImg.dataset.src; 544 } 545 } 546 } 547 548 const bullets = sliderPagination.querySelectorAll('.pagination-bullet'); 549 bullets.forEach((bullet, idx) => { 550 bullet.classList.toggle('active', idx === currentSlideIndex); 551 }); 552 553 // ÐбновлÑем даннÑе дизайнеÑа 554 const activeSlide = slides[currentSlideIndex]; 555 if (activeSlide) { 556 designerImg.src = activeSlide.designer?.avatar; 557 designerName.textContent = activeSlide.designer?.name; 558 slideCollectionName.textContent = activeSlide.name; 559 slideCollectionDesc.textContent = activeSlide.description; 560 if (slideCollectionLink) slideCollectionLink.href = activeSlide.link || '#'; 561 slideBadge.textContent = activeSlide.badge || 'СÐÐÐТЫ ÐÐÐÐÐÐÐÐ Ð'; 562 designerTitleLabel.textContent = activeSlide.designerTitle || 'ÐÑкÑÑÑÑво инÑеÑÑеÑа'; 563 564 // ÐÑпÑавлÑем ÑобÑÑие аналиÑики пÑи ÑÑÑном пеÑелиÑÑÑвании 565 if (isManual) { 566 sendAnalyticsEvent('Advice_Slide_Change', { 567 slide_index: currentSlideIndex + 1, 568 slide_name: activeSlide.name 569 }); 570 } 571 } 572 573 // ÐÑли Ð¼Ñ Ð² конÑÑÑÑкÑоÑе, нÑжно пеÑеиниÑиализиÑоваÑÑ ÐµÐ³Ð¾ 574 const builderTab = document.getElementById('builder-tab'); 575 if (builderTab && builderTab.classList.contains('active')) { 576 initBuilder(); 577 } 578} 579 580// ÐÑивÑзка оÑновнÑÑ ÑобÑÑий ÑлайдеÑа и Ñабов (вÑзÑваеÑÑÑ Ð¿Ð¾Ñле DOMContentLoaded) 581function initMainEvents() { 582 if (sliderPrev) sliderPrev.addEventListener('click', () => goToSlide(currentSlideIndex - 1, true)); 583 if (sliderNext) sliderNext.addEventListener('click', () => goToSlide(currentSlideIndex + 1, true)); 584 585 if (tabButtons && tabButtons.length > 0) { 586 tabButtons.forEach(btn => { 587 btn.addEventListener('click', () => { 588 tabButtons.forEach(b => b.classList.remove('active')); 589 tabContents.forEach(c => c.classList.remove('active')); 590 591 btn.classList.add('active'); 592 const targetTab = btn.getAttribute('data-tab'); 593 const targetTabEl = document.getElementById(`${targetTab}-tab`); 594 if (targetTabEl) targetTabEl.classList.add('active'); 595 596 if (targetTab === 'builder') { 597 stopAutoplay(); 598 initBuilder(); 599 } else { 600 renderSlider(); 601 startAutoplay(); 602 } 603 }); 604 }); 605 } 606} 607 608// ========================================================================== 609// 4. Ðогика ÐонÑÑÑÑкÑоÑа (Visual Builder) 610// ========================================================================== 611 612function initBuilder() { 613 // 4.1 ÐаполнÑем вÑпадаÑÑий ÑпиÑок Ñлайдов 614 slideSelect.innerHTML = '';
615 slides.forEach((slide, idx) => { 616 const option = document.createElement('option'); 617 option.value = idx; 618 option.textContent = `${idx + 1}. ${slide.name || 'Слайд без названиÑ'}`; 619 option.selected = idx === currentSlideIndex; 620 slideSelect.appendChild(option); 621 }); 622 623 const currentSlide = slides[currentSlideIndex]; 624 if (!currentSlide) { 625 builderImagePreview.src = ''; 626 builderMarkersContainer.innerHTML = ''; 627 markerEditorSection.style.display = 'none'; 628 return; 629 } 630 631 // 4.2 Ð¡Ð¸Ð½Ñ ÑонизиÑÑем ÑоÑÐ¼Ñ ÑедакÑиÑÐ¾Ð²Ð°Ð½Ð¸Ñ Ñлайда 632 inputSlideName.value = currentSlide.name || ''; 633 inputSlideDesc.value = currentSlide.description || ''; 634 inputSlideImage.value = currentSlide.image || ''; 635 if (inputSlideLink) inputSlideLink.value = currentSlide.link || ''; 636 inputSlideBadge.value = currentSlide.badge || 'СÐÐÐТЫ ÐÐÐÐÐÐÐÐ Ð'; 637 inputSlideDesignerTitle.value = currentSlide.designerTitle || 'ÐÑкÑÑÑÑво инÑеÑÑеÑа'; 638 inputDesignerName.value = currentSlide.designer?.name || 'ÐикÑоÑ'; 639 inputDesignerAvatar.value = currentSlide.designer?.avatar || ''; 640 641 // 4.3 УÑÑанавливаем Ñон Ñ Ð¾Ð»ÑÑа 642 builderImagePreview.src = currentSlide.image || 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?auto=format&fit=crop&w=1200&q=80'; 643 644 // ÐбновлÑем пÑевÑÑ Ð² зоне загÑÑзки аваÑаÑа 645 const avatarPreviewImg = document.getElementById('avatar-drop-zone-preview'); 646 const avatarIcon = document.getElementById('avatar-drop-zone-icon'); 647 if (avatarPreviewImg && avatarIcon) { 648 if (currentSlide.designer?.avatar) { 649 avatarPreviewImg.src = currentSlide.designer.avatar; 650 avatarPreviewImg.style.display = 'block'; 651 avatarIcon.style.display = 'none'; 652 } else { 653 avatarPreviewImg.src = ''; 654 avatarPreviewImg.style.display = 'none'; 655 avatarIcon.style.display = 'block'; 656 } 657 } 658 659 // 4.4 ÐÑÑиÑовÑваем маÑкеÑÑ Ð½Ð° Ñ Ð¾Ð»ÑÑе конÑÑÑÑкÑоÑа 660 renderBuilderMarkers(); 661 662 // СкÑÑваем ÑедакÑÐ¾Ñ ÑоваÑа пока ниÑего не вÑбÑано 663 if (!selectedMarkerId || !currentSlide.markers.find(m => m.id === selectedMarkerId)) { 664 selectedMarkerId = null; 665 markerEditorSection.style.display = 'none'; 666 } else { 667 selectMarker(selectedMarkerId); 668 } 669 670 updateExportCode(); 671} 672 673// ÐÑÑиÑовка маÑкеÑов на Ñ Ð¾Ð»ÑÑе ÑедакÑоÑа 674function renderBuilderMarkers() { 675 builderMarkersContainer.innerHTML = ''; 676 const currentSlide = slides[currentSlideIndex]; 677 if (!currentSlide) return; 678 679 currentSlide.markers.forEach((marker, index) => { 680 const markerEl = document.createElement('div'); 681 markerEl.className = `builder-edit-marker ${marker.id === selectedMarkerId ? 'active' : ''}`; 682 markerEl.style.setProperty('--x', marker.x); 683 markerEl.style.setProperty('--y', marker.y); 684 markerEl.setAttribute('data-id', marker.id); 685 markerEl.setAttribute('data-number', index + 1); 686 687 // ÐбÑабоÑÑик клика Ð´Ð»Ñ Ð²ÑбоÑа ÑоÑки 688 markerEl.addEventListener('click', (e) => { 689 e.stopPropagation(); 690 if (isDragging) return; 691 selectMarker(marker.id); 692 }); 693 694 // Drag-and-drop мÑÑи 695 markerEl.addEventListener('mousedown', (e) => { 696 e.stopPropagation(); 697 startDragging(marker.id, e); 698 }); 699 700 // Drag-and-drop Touch 701 markerEl.addEventListener('touchstart', (e) => { 702 e.stopPropagation(); 703 startDragging(marker.id, e.touches[0]); 704 }, { passive: true }); 705 706 builderMarkersContainer.appendChild(markerEl); 707 }); 708} 709 710// ÐÑÐ±Ð¾Ñ Ð¼Ð°ÑкеÑа в боковой панели 711function selectMarker(id) { 712 if (typeof clearImportStatus === 'function') clearImportStatus(); 713 selectedMarkerId = id; 714 const currentSlide = slides[currentSlideIndex]; 715 const marker = currentSlide.markers.find(m => m.id === id); 716 717 if (!marker) { 718 markerEditorSection.style.display = 'none'; 719 return; 720 } 721 722 // ÐодÑвеÑиваем акÑивнÑй маÑÐºÐµÑ Ð½Ð° Ñ Ð¾Ð»ÑÑе 723 const markerEls = builderMarkersContainer.querySelectorAll('.builder-edit-marker');
724 markerEls.forEach(el => { 725 el.classList.toggle('active', el.getAttribute('data-id') === id); 726 }); 727 728 // ÐаполнÑем ÑоÑÐ¼Ñ Ð¼Ð°ÑкеÑа 729 markerEditorSection.style.display = 'block'; 730 markerCoordX.textContent = marker.x; 731 markerCoordY.textContent = marker.y; 732 inputMarkerTitle.value = marker.title || ''; 733 inputMarkerPrice.value = marker.price ? marker.price.replace(/\s/g, '') : '0'; 734 inputMarkerDiscount.value = marker.discount || 0; 735 inputMarkerLink.value = marker.link || ''; 736 737 updateCalculatedPrice(marker); 738} 739 740// РаÑÑÐµÑ Ð¸ вÑвод ÑÐµÐ½Ñ Ñо Ñкидкой 741function updateCalculatedPrice(marker) { 742 const rawPrice = inputMarkerPrice.value.replace(/\s/g, ''); 743 const priceNum = parseFloat(rawPrice) || 0; 744 const discountNum = parseInt(inputMarkerDiscount.value) || 0; 745 746 let finalPrice = priceNum; 747 if (discountNum > 0) { 748 finalPrice = Math.round(priceNum * (1 - discountNum / 100)); 749 } 750 751 calculatedPriceBadge.textContent = `${formatPrice(finalPrice)} â½`; 752 753 // Ð¡Ð¾Ñ ÑанÑем в обÑÐµÐºÑ Ð´Ð°Ð½Ð½ÑÑ 754 marker.price = formatPrice(priceNum); 755 marker.discount = discountNum; 756 marker.priceWithDiscount = formatPrice(finalPrice); 757 758 updateExportCode(); 759} 760 761// Ðобавление новой ÑоÑки кликом по Ñ Ð¾Ð»ÑÑÑ 762safeAddListener(builderCanvas, 'click', (e) => { 763 // УбедимÑÑ, ÑÑо Ð¼Ñ ÐºÐ»Ð¸ÐºÐ°ÐµÐ¼ именно по Ñ Ð¾Ð»ÑÑÑ Ð¸Ð»Ð¸ каÑÑинке, а не по ÑÑÑеÑÑвÑÑÑей ÑоÑке 764 if (e.target.classList.contains('builder-edit-marker')) return; 765 766 const currentSlide = slides[currentSlideIndex]; 767 if (!currentSlide) return; 768 769 const rect = builderCanvas.getBoundingClientRect(); 770 let x = ((e.clientX - rect.left) / rect.width) * 100; 771 let y = ((e.clientY - rect.top) / rect.height) * 100; 772 773 // ÐкÑÑглÑем до 1 знака поÑле запÑÑой 774 x = Math.round(x * 10) / 10; 775 y = Math.round(y * 10) / 10; 776 777 // ÐгÑаниÑение гÑÐ°Ð½Ð¸Ñ 778 x = Math.max(1, Math.min(99, x)); 779 y = Math.max(1, Math.min(99, y)); 780 781 const newMarker = { 782 id: 'marker_' + Date.now(), 783 x: x, 784 y: y, 785 title: 'ÐовÑй ÑоваÑ', 786 price: '5 000', 787 discount: 0, 788 priceWithDiscount: '5 000', 789 link: '/catalog/' 790 }; 791 792 pushHistory(); 793 currentSlide.markers.push(newMarker); 794 795 renderBuilderMarkers(); 796 selectMarker(newMarker.id); 797 updateExportCode(); 798}); 799 800// Drag and drop логика 801function startDragging(markerId, pointerEvent) { 802 stateBeforeDrag = JSON.stringify(slides); 803 isDragging = true; 804 dragMarkerId = markerId; 805 selectMarker(markerId); 806 document.body.style.cursor = 'grabbing'; 807} 808 809function handleDragging(pointerEvent) { 810 if (!isDragging || !dragMarkerId) return; 811 812 const currentSlide = slides[currentSlideIndex]; 813 const marker = currentSlide.markers.find(m => m.id === dragMarkerId); 814 if (!marker) return; 815 816 const rect = builderCanvas.getBoundingClientRect(); 817 let x = ((pointerEvent.clientX - rect.left) / rect.width) * 100; 818 let y = ((pointerEvent.clientY - rect.top) / rect.height) * 100; 819 820 x = Math.round(x * 10) / 10; 821 y = Math.round(y * 10) / 10; 822 823 x = Math.max(1, Math.min(99, x)); 824 y = Math.max(1, Math.min(99, y)); 825 826 // ÐбновлÑем даннÑе маÑкеÑа 827 marker.x = x; 828 marker.y = y; 829 830 // ÐÑÑÑÑо двигаем DOM-ÑÐ»ÐµÐ¼ÐµÐ½Ñ 831 const markerEl = builderMarkersContainer.querySelector(`.builder-edit-marker[data-id="${dragMarkerId}"]`); 832 if (markerEl) { 833 markerEl.style.setProperty('--x', x); 834 markerEl.style.setProperty('--y', y); 835 } 836 837 // ÐбновлÑем кооÑдинаÑÑ Ð² ÑоÑме 838 markerCoordX.textContent = x; 839 markerCoordY.textContent = y; 840} 841 842function stopDragging() { 843 if (isDragging) { 844 isDragging = false; 845 dragMarkerId = null; 846 document.body.style.cursor = 'default'; 847 848 // ÐÑовеÑÑем, изменилиÑÑ Ð»Ð¸ кооÑдинаÑÑ 849 const currentStateStr = JSON.stringify(slides); 850 if (stateBeforeDrag && stateBeforeDrag !== currentStateStr) { 851 undoStack.push(stateBeforeDrag); 852 redoStack = []; 853 updateHistoryButtons(); 854 } 855 stateBeforeDrag = null; 856 857 updateExportCode(); 858 } 859} 860 861// ÐÑивÑзка обÑабоÑÑиков пеÑемеÑÐµÐ½Ð¸Ñ Ð¼ÑÑи на веÑÑ Ð´Ð¾Ðº
861ÑÐ¼ÐµÐ½Ñ Ð´Ð»Ñ Ð¿Ð»Ð°Ð²Ð½Ð¾ÑÑи 862document.addEventListener('mousemove', handleDragging); 863document.addEventListener('mouseup', stopDragging); 864 865document.addEventListener('touchmove', (e) => { 866 if (isDragging) { 867 // e.preventDefault(); // ÐÑедоÑвÑаÑаем ÑкÑолл ÑÑÑаниÑÑ Ð²Ð¾ вÑÐµÐ¼Ñ Ð¿ÐµÑеÑаÑÐºÐ¸Ð²Ð°Ð½Ð¸Ñ 868 handleDragging(e.touches[0]); 869 } 870}, { passive: false }); 871document.addEventListener('touchend', stopDragging); 872 873// ========================================================================== 874// 5. ÐбÑабоÑка изменений в ÑоÑÐ¼Ð°Ñ 875// ========================================================================== 876 877// ÐеÑеклÑÑение вÑбÑанного Ñлайда 878safeAddListener(slideSelect, 'change', (e) => { 879 currentSlideIndex = parseInt(e.target.value); 880 selectedMarkerId = null; 881 initBuilder(); 882}); 883 884// Ðобавление Ñлайда 885safeAddListener(addSlideBtn, 'click', () => { 886 pushHistory(); 887 const newSlide = { 888 id: 'slide_' + Date.now(), 889 title: 'ÐÑкÑÑÑÑво инÑеÑÑеÑа: ÐзглÑд ÐикÑоÑа', 890 name: 'ÐÐ¾Ð²Ð°Ñ ÐºÐ¾Ð»Ð»ÐµÐºÑÐ¸Ñ Ð¼ÐµÐ±ÐµÐ»Ð¸', 891 description: 'ÐпиÑание новой коллекÑии: ÑÑнкÑионалÑÐ½Ð°Ñ Ð¸ кÑаÑÐ¸Ð²Ð°Ñ Ð¼ÐµÐ±ÐµÐ»Ñ Ð´Ð»Ñ Ð²Ð°Ñего ÑÑÑа.', 892 image: 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?auto=format&fit=crop&w=1200&q=80', 893 link: '/catalog/', 894 badge: 'СÐÐÐТЫ ÐÐÐÐÐÐÐÐ Ð', 895 designerTitle: 'ÐÑкÑÑÑÑво инÑеÑÑеÑа', 896 designer: { 897 name: 'ÐикÑоÑ', 898 role: 'ÐедÑÑий дизайнеÑ', 899 avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=150&q=80' 900 }, 901 markers: [] 902 }; 903 904 slides.push(newSlide); 905 currentSlideIndex = slides.length - 1; 906 selectedMarkerId = null; 907 initBuilder(); 908}); 909 910// Удаление Ñлайда 911safeAddListener(deleteSlideBtn, 'click', () => { 912 if (slides.length <= 1) { 913 alert('ÐелÑÐ·Ñ ÑдалиÑÑ ÐµÐ´Ð¸Ð½ÑÑвеннÑй Ñлайд!'); 914 return; 915 } 916 917 if (confirm('ÐÑ Ð´ÐµÐ¹ÑÑвиÑелÑно Ñ Ð¾ÑиÑе ÑдалиÑÑ ÑÑÐ¾Ñ Ñлайд?')) { 918 pushHistory(); 919 slides.splice(currentSlideIndex, 1); 920 currentSlideIndex = Math.max(0, currentSlideIndex - 1); 921 selectedMarkerId = null; 922 initBuilder(); 923 } 924}); 925 926// СобÑÑÐ¸Ñ ÑоÑм ÑедакÑиÑÐ¾Ð²Ð°Ð½Ð¸Ñ ÑекÑÑего Ñлайда 927safeAddListener(inputSlideName, 'input', (e) => { 928 if (slides[currentSlideIndex]) { 929 slides[currentSlideIndex].name = e.target.value; 930 // ÐбновлÑем Ð¸Ð¼Ñ Ñлайда в ÑпиÑке вÑбоÑа 931 if (slideSelect) { 932 const opt = slideSelect.options[currentSlideIndex]; 933 if (opt) opt.textContent = `${currentSlideIndex + 1}. ${e.target.value}`; 934 } 935 // ÐбновлÑем в пÑедпÑоÑмоÑÑе 936 slideCollectionName.textContent = e.target.value; 937 updateExportCode(); 938 } 939}); 940 941safeAddListener(inputSlideDesc, 'input', (e) => { 942 if (slides[currentSlideIndex]) { 943 slides[currentSlideIndex].description = e.target.value; 944 // ÐбновлÑем в пÑедпÑоÑмоÑÑе 945 slideCollectionDesc.textContent = e.target.value; 946 updateExportCode(); 947 } 948}); 949 950safeAddListener(inputSlideImage, 'input', (e) => { 951 if (slides[currentSlideIndex]) { 952 slides[currentSlideIndex].image = e.target.value; 953 if (builderImagePreview) { 954 builderImagePreview.src = e.target.value || 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?auto=format&fit=crop&w=1200&q=80'; 955 } 956 // ÐбновлÑем каÑÑÐ¸Ð½ÐºÑ Ð°ÐºÑивного Ñлайда в пÑедпÑоÑмоÑÑе 957 const activeSlideItem = sliderWrapper.querySelector('.slider-item.active img'); 958 if (activeSlideItem) { 959 activeSlideItem.src = e.target.value || 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?auto=format&fit=crop&w=1200&q=80'; 960 } 961 updateExportCode(); 962 } 963}); 964 965safeAddListener(inputSlideLink, 'input', (e) => { 966 if (slides[currentSlideIndex]) { 967 slides[currentSlideIndex].link = e.target.value; 968 // ÐбновлÑем в пÑедпÑоÑмоÑÑе 969 slideCollectionLink.href = e.target.value || '#'; 970 updateExportCode(); 971 } 972}); 973 974safeAddListener(inputSlideBadge, 'input', (e) => { 975 if (slides[currentSlideIndex]) { 976 slides[currentSlideIndex].badge = e.target.value; 977 // ÐбновлÑем в пÑедпÑоÑмоÑÑе 978 slideBadge.textContent = e.target.value || 'СÐÐÐТЫ ÐÐÐÐÐÐÐÐ Ð'; 979 updateExportCode(); 980 } 981}); 982 983safeAddListener(inputSlideDesignerTitle, 'input', (e) => { 984 if (slides[currentSlideIndex]) { 985 slides[currentSlideIndex].designerTitle = e.target.value; 986 // ÐбновлÑем в пÑедпÑоÑмоÑÑе 987 designerTitleLabel.textContent = e.target.value || 'ÐÑкÑÑÑÑво инÑеÑÑеÑа'; 988 updateExportCode(); 989 } 990}); 991 992safeAddListener(inputDesignerName, 'input', (e) => { 993 if (slides[currentSlideIndex]) { 994 if (!slides[currentSlideIndex].designer) slides[currentSlideIndex].designer = {}; 995 slides[currentSlideIndex].designer.name = e.target.value; 996 // ÐбновлÑем в пÑедпÑоÑмоÑÑе 997 designerName.textContent = e.target.value || 'ÐикÑоÑ'; 998 updateExportCode(); 999 } 1000}); 1001 1002safeAddListener(inputDesignerAvatar, 'input', (e) => { 1003 if (slides[currentSlideIndex]) { 1004 if (!slides[currentSlideIndex].designer) slides[currentSlideIndex].designer = {}; 1005 slides[currentSlideIndex].designer.avatar = e.target.value; 1006 // ÐбновлÑем в пÑедпÑоÑмоÑÑе 1007 designerImg.src = e.target.value || 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=150&q=80'; 1008 updateExportCode(); 1009 } 1010}); 1011 1012// СобÑÑÐ¸Ñ ÑоÑм ÑедакÑиÑÐ¾Ð²Ð°Ð½Ð¸Ñ ÑоваÑа (ÑоÑки) 1013safeAddListener(inputMarkerTitle, 'input', (e) => { 1014 const currentSlide = slides[currentSlideIndex]; 1015 if (currentSlide && selectedMarkerId) { 1016 const marker = currentSlide.markers.find(m => m.id === selectedMarkerId); 1017 if (marker) { 1018 marker.title = e.target.value; 1019 updateExportCode(); 1020 } 1021 } 1022}); 1023 1024safeAddListener(inputMarkerPrice, 'input', () => { 1025 const currentSlide = slides[currentSlideIndex]; 1026 if (currentSlide && selectedMarkerId) { 1027 const marker = currentSlide.markers.find(m => m.id === selectedMarkerId); 1028 if (marker) { 1029 updateCalculatedPrice(marker); 1030 } 1031 } 1032}); 1033 1034safeAddListener(inputMarkerDiscount, 'input', () => { 1035 const currentSlide = slides[currentSlideIndex]; 1036 if (currentSlide && selectedMarkerId) { 1037 const marker = currentSlide.markers.find(m => m.id === selectedMarkerId); 1038 if (marker) { 1039 // ÐгÑаниÑение диапазона Ñкидки 1040 let disc = parseInt(inputMarkerDiscount.value) || 0; 1041 if (disc < 0) { 1042 disc = 0; 1043 inputMarkerDiscount.value = 0; 1044 } 1045 if (disc > 99) { 1046 disc = 99; 1047 inputMarkerDiscount.value = 99; 1048 } 1049 updateCalculatedPrice(marker); 1050 } 1051 } 1052}); 1053 1054safeAddListener(inputMarkerLink, 'input', (e) => { 1055 if (typeof clearImportStatus === 'function') clearImportStatus(); 1056 const currentSlide = slides[currentSlideIndex]; 1057 if (currentSlide && selectedMarkerId) { 1058 const marker = currentSlide.markers.find(m => m.id === selectedMarkerId); 1059 if (marker) { 1060 marker.link = e.target.value; 1061 updateExportCode(); 1062 } 1063 } 1064}); 1065 1066// --- Ðогика авÑÐ¾Ð·Ð°Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½ÑÑ ÑоваÑа по ÑÑÑлке (Scraping) --- 1067 1068function clearImportStatus() { 1069 importStatusMsg.style.display = 'none'; 1070 importStatusMsg.className = 'import-status-message';
1071 importStatusMsg.innerHTML = ''; 1072} 1073 1074function showImportStatus(text, type) { 1075 importStatusMsg.style.display = 'block'; 1076 importStatusMsg.className = `import-status-message ${type}`; 1077 if (type === 'loading') { 1078 importStatusMsg.innerHTML = `<i class="fa-solid fa-spinner fa-spin"></i> ${text}`; 1079 } else if (type === 'success') { 1080 importStatusMsg.innerHTML = `<i class="fa-solid fa-circle-check"></i> ${text}`; 1081 } else { 1082 importStatusMsg.innerHTML = `<i class="fa-solid fa-circle-exclamation"></i> ${text}`; 1083 } 1084} 1085 1086function makeAbsoluteUrl(url) { 1087 if (!url) return ''; 1088 url = url.trim(); 1089 if (url.startsWith('http://') || url.startsWith('https://')) { 1090 return url; 1091 } 1092 // ÐÑли ÑÑо оÑноÑиÑелÑнÑй пÑÑÑ 1093 const baseUrl = 'https://alleyadoma.ru'; 1094 if (url.startsWith('/')) { 1095 return baseUrl + url; 1096 } 1097 return baseUrl + '/' + url; 1098} 1099 1100async function fetchProductHTML(url) { 1101 // 0. ÐÑли ÑÑо локалÑнÑй ÑеÑÑовÑй Ñайл (напÑимеÑ, product1.html или ./product1.html) 1102 const isLocalTest = url.endsWith('.html') && !url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('//'); 1103 if (isLocalTest) { 1104 try { 1105 console.log("ÐопÑÑка 0: ÐагÑÑзка локалÑного ÑеÑÑового Ñайла...", url); 1106 const response = await fetch(url); 1107 if (response.ok) { 1108 const text = await response.text(); 1109 if (text && text.trim().length > 0) { 1110 console.log("УÑпеÑно загÑÑжен локалÑнÑй ÑеÑÑовÑй Ñайл"); 1111 return text; 1112 } 1113 } 1114 } catch (e) { 1115 console.warn("Ðе ÑдалоÑÑ Ð·Ð°Ð³ÑÑзиÑÑ Ð»Ð¾ÐºÐ°Ð»ÑнÑй Ñайл напÑÑмÑÑ:", e); 1116 } 1117 } 1118 1119 // 0.5. ÐÑли Ð¼Ñ Ð½Ð°Ñ Ð¾Ð´Ð¸Ð¼ÑÑ Ð½Ð° Ñом же домене, пÑобÑем пÑÑмой fetch (бÑаÑÐ·ÐµÑ Ð°Ð²ÑомаÑиÑеÑки пÑикÑÐµÐ¿Ð¸Ñ ÐºÑки авÑоÑизаÑии полÑзоваÑелÑ) 1120 try { 1121 const currentHost = window.location.hostname; 1122 const targetUrlObj = new URL(url); 1123 const targetHost = targetUrlObj.hostname.replace('www.', ''); 1124 const cleanCurrentHost = currentHost.replace('www.', ''); 1125 1126 if (cleanCurrentHost === targetHost || cleanCurrentHost.endsWith('.' + targetHost) || targetHost.endsWith('.' + cleanCurrentHost)) { 1127 console.log("ÐопÑÑка 0.5: ÐÑÑмой запÑÐ¾Ñ Ðº ÑайÑÑ (один домен)..."); 1128 const response = await fetch(url); 1129 if (response.ok) { 1130 const text = await response.text(); 1131 if (text && text.trim().length > 0) { 1132 console.log("УÑпеÑно загÑÑжено напÑÑмÑÑ ÑеÑез бÑаÑÐ·ÐµÑ (один домен)"); 1133 return text; 1134 } 1135 } 1136 } 1137 } catch (e) { 1138 console.log("ÐÑÑмой запÑÐ¾Ñ Ð² ÑÐ°Ð¼ÐºÐ°Ñ Ð¾Ð´Ð½Ð¾Ð³Ð¾ домена не ÑдалÑÑ (возможно, CORS пÑи локалÑной ÑазÑабоÑке), пÑобÑем пÑокÑи..."); 1139 } 1140 1141 const encodedUrl = encodeURIComponent(url); 1142 1143 // 1. СнаÑала пÑобÑем локалÑнÑй PHP-пÑокÑи (Ð´Ð»Ñ Ð¶Ð¸Ð²Ð¾Ð³Ð¾ ÑайÑа Ñ PHP) 1144 try { 1145 console.log("ÐопÑÑка 1: ÐокалÑнÑй PHP-пÑокÑи..."); 1146 const response = await fetch(`get-product.php?url=${encodedUrl}`); 1147 if (response.ok) { 1148 const data = await response.json(); 1149 if (data.contents) { 1150 console.log("УÑпеÑно загÑÑжено ÑеÑез локалÑнÑй PHP-пÑокÑи"); 1151 return data.contents; 1152 } 1153 if (data.error) console.warn("PHP Proxy error:", data.error); 1154 } 1155 } catch (e) { 1156 console.log("ÐокалÑнÑй PHP-пÑокÑи недоÑÑÑпен (возможно, запÑÑк без веб-ÑеÑвеÑа), пÑобÑем пÑблиÑнÑе CORS-пÑокÑи..."); 1157 } 1158 1159 // СпиÑок пÑблиÑнÑÑ Ð¿ÑокÑи-ÑеÑвеÑов Ð´Ð»Ñ ÐºÐ°Ñкадного опÑоÑа 1160 // ÐекоÑоÑÑе пÑокÑи возвÑаÑаÑÑ ÑÑÑой HTML, некоÑоÑÑе - JSON Ñ Ð¿Ð¾Ð»ÐµÐ¼ contents. 1161 const publicProxies = [ 1162 { 1163 name: "corsproxy.io", 1164 url: `https://corsproxy.io/?url=${encodedUrl}`, 1165 type: "raw" 1166 }, 1167 { 1168 name: "codetabs.com", 1169 url: `https://api.codetabs.com/v1/proxy?url=${encodedUrl}`, 1170 type: "raw" 1171 }, 1172 { 1173 name: "allorigins.win", 1174 url: `https://api.allorigins.win/get?url=${encodedUrl}`, 1175 type: "json" 1176 } 1177 ]; 1178 1179 for (const proxy of publicProxies) { 1180 try { 1181 console.log(`ÐопÑÑка загÑÑзки ÑеÑез ${proxy.name}...`); 1182 const response = await fetch(proxy.url); 1183 if (response.ok) { 1184 if (proxy.type === "raw") { 1185 const text = await response.text(); 1186 if (text && text.trim().length > 0) { 1187 console.log(`УÑпеÑно загÑÑжено ÑеÑез ${proxy.name}`); 1188 return text; 1189 } 1190 } else if (proxy.type === "json") { 1191 const data = await response.json(); 1192 if (data.contents) { 1193 console.log(`УÑпеÑно загÑÑжено ÑеÑез ${proxy.name}`); 1194 return data.contents; 1195 } 1196 } 1197 } else { 1198 console.warn(`${proxy.name} веÑнÑл ÑÑаÑÑÑ-код ${response.status}`); 1199 } 1200 } catch (err) { 1201 console.warn(`ÐÑибка пÑи запÑоÑе к ${proxy.name}:`, err); 1202 } 1203 } 1204 1205 // ÐÑли вÑе попÑÑки пÑовалилиÑÑ, вÑводим подÑобное ÑÑководÑÑво по иÑпÑÐ°Ð²Ð»ÐµÐ½Ð¸Ñ 1206 throw new Error( 1207 "Ðе ÑдалоÑÑ Ð·Ð°Ð³ÑÑзиÑÑ Ð´Ð°Ð½Ð½Ñе ÑоваÑа.\n\n" + 1208 "ÐозможнÑе пÑиÑÐ¸Ð½Ñ Ð¸ ÑеÑениÑ:\n" + 1209 "1. ÐÑли Ð²Ñ Ð·Ð°Ð¿ÑÑÑили конÑÑÑÑкÑÐ¾Ñ Ð»Ð¾ÐºÐ°Ð»Ñно ÑеÑез двойной клик по ÑÐ°Ð¹Ð»Ñ (пÑоÑокол file://), бÑаÑÐ·ÐµÑ Ð±Ð»Ð¾ÐºÐ¸ÑÑÐµÑ Ð·Ð°Ð¿ÑоÑÑ Ð±ÐµÐ·Ð¾Ð¿Ð°ÑноÑÑи. ÐапÑÑÑиÑе локалÑнÑй веб-ÑеÑÐ²ÐµÑ (напÑимеÑ, ÑеÑез ÑаÑÑиÑение Live Server в VS Code, или иÑполÑзÑÑ Node.js, Python, Open Server).\n" + 1210 "2. ÐÑли конÑÑÑÑкÑÐ¾Ñ Ð·Ð°Ð³ÑÑжен на Ñ Ð¾ÑÑинг, ÑбедиÑеÑÑ, ÑÑо Ñайл get-product.php Ð½Ð°Ñ Ð¾Ð´Ð¸ÑÑÑ Ð² Ñой же диÑекÑоÑии, ÑÑо и index.html, и на ÑеÑвеÑе ÑабоÑÐ°ÐµÑ PHP.\n" + 1211 "3. ÐÑблиÑнÑе CORS-пÑокÑи вÑеменно пеÑегÑÑÐ¶ÐµÐ½Ñ Ð¸Ð»Ð¸ блокиÑÑÑÑÑÑ Ð²Ð°Ñим пÑовайдеÑом. ÐопÑобÑйÑе еÑе Ñаз ÑеÑез неÑколÑко минÑÑ." 1212 ); 1213} 1214 1215function parseProductHTML(htmlString) { 1216 const parser = new DOMParser(); 1217 const doc = parser.parseFromString(htmlString, 'text/html'); 1218 1219 // 1. Ðаголовок ÑоваÑа 1220 const titleEl = doc.querySelector('h1[itemprop="name"]') || 1221 doc.querySelector('.right-inside-tovar h1') || 1222 doc.querySelector('h1') || 1223 doc.querySelector('.card-product-title'); 1224 if (!titleEl) throw new Error("Ðе ÑдалоÑÑ Ð½Ð°Ð¹Ñи название ÑоваÑа на ÑÑÑаниÑе. УбедиÑеÑÑ, ÑÑо ÑÑÑлка Ð²ÐµÐ´ÐµÑ Ð½Ð° каÑÑоÑÐºÑ ÑоваÑа."); 1225 const title = titleEl.textContent.trim().replace(/\s+/g, ' '); 1226 1227 // 2. ТекÑÑÐ°Ñ Ñена (Ñ ÑÑеÑом Ñкидки) 1228 const metaPriceEl = doc.querySelector('meta[itemprop="price"]');
1229 let currentPrice = 0; 1230 if (metaPriceEl) { 1231 currentPrice = parseFloat(metaPriceEl.getAttribute('content')) || 0; 1232 } 1233 1234 if (!currentPrice) { 1235 const priceTextEl = doc.querySelector('.fs-32.font-weight-700') || 1236 doc.querySelector('.actual-cost-inside .fs-32') || 1237 doc.querySelector('[data-entity="panel-price"]'); 1238 if (priceTextEl) { 1239 currentPrice = parseFloat(priceTextEl.textContent.replace(/\s/g, '').replace(/ /g, '').replace(/\u00a0/g, '').replace(/\D/g, '')) || 0; 1240 } 1241 } 1242 1243 if (!currentPrice) { 1244 throw new Error("Ðе ÑдалоÑÑ Ð¸Ð·Ð²Ð»ÐµÑÑ ÑÐµÐ½Ñ ÑоваÑа. Ðозможно, ÑоваÑа Ð½ÐµÑ Ð² налиÑии или изменилаÑÑ ÑÑÑÑкÑÑÑа ÑÑÑаниÑÑ."); 1245 } 1246 1247 // 3. СÑаÑÐ°Ñ Ñена (Ñена без Ñкидки) 1248 const oldPriceEl = doc.querySelector('.old-cost-inside') || 1249 doc.querySelector('.price-old') || 1250 doc.querySelector('.old-price'); 1251 let oldPrice = currentPrice; 1252 if (oldPriceEl) { 1253 oldPrice = parseFloat(oldPriceEl.textContent.replace(/\s/g, '').replace(/ /g, '').replace(/\u00a0/g, '').replace(/\D/g, '')) || currentPrice; 1254 } 1255 1256 // 4. ÐÑоÑÐµÐ½Ñ Ñкидки 1257 let discount = 0; 1258 const discountEl = doc.querySelector('.discount') || doc.querySelector('.percent'); 1259 if (discountEl) { 1260 discount = parseInt(discountEl.textContent.replace(/\s/g, '').replace(/\D/g, '')) || 0; 1261 } else if (oldPrice > currentPrice) { 1262 discount = Math.round(((oldPrice - currentPrice) / oldPrice) * 100); 1263 } 1264 1265 return { 1266 title, 1267 price: oldPrice, 1268 discount, 1269 priceWithDiscount: currentPrice 1270 }; 1271} 1272 1273safeAddListener(fetchProductBtn, 'click', async () => { 1274 const currentSlide = slides[currentSlideIndex]; 1275 if (!currentSlide || !selectedMarkerId) return; 1276 1277 const marker = currentSlide.markers.find(m => m.id === selectedMarkerId); 1278 if (!marker) return; 1279 1280 const urlInput = inputMarkerLink.value.trim(); 1281 if (!urlInput) { 1282 showImportStatus("ÐожалÑйÑÑа, ÑнаÑала введиÑе ÑÑÑÐ»ÐºÑ Ð½Ð° ÑоваÑ.", "error"); 1283 return; 1284 } 1285 1286 const isLocalTest = urlInput.endsWith('.html') && !urlInput.startsWith('http://') && !urlInput.startsWith('https://') && !urlInput.startsWith('//'); 1287 const absoluteUrl = isLocalTest ? urlInput : makeAbsoluteUrl(urlInput); 1288 if (!isLocalTest && !absoluteUrl.includes('alleyadoma.ru')) { 1289 showImportStatus("РазÑеÑÐµÐ½Ñ ÑолÑко ÑÑÑлки Ñ ÑайÑа alleyadoma.ru", "error"); 1290 return; 1291 } 1292 1293 try { 1294 fetchProductBtn.disabled = true; 1295 const btnText = fetchProductBtn.querySelector('span'); 1296 const btnIcon = fetchProductBtn.querySelector('i'); 1297 1298 const originalText = btnText.textContent; 1299 btnText.textContent = "ÐагÑÑзка..."; 1300 btnIcon.className = "fa-solid fa-spinner fa-spin"; 1301 1302 showImportStatus("ÐагÑÑзка даннÑÑ Ð¾ ÑоваÑе...", "loading"); 1303 1304 const html = await fetchProductHTML(absoluteUrl); 1305 const productData = parseProductHTML(html); 1306 1307 // ÐбновлÑем ÑоÑÐ¼Ñ Ð² боковой панели 1308 inputMarkerTitle.value = productData.title; 1309 inputMarkerPrice.value = formatPrice(productData.price); 1310 inputMarkerDiscount.value = productData.discount; 1311 1312 // ÐÑевÑаÑаем ÑÑÑÐ»ÐºÑ Ð¾Ð±ÑаÑно в оÑноÑиÑелÑнÑÑ Ð´Ð»Ñ ÐºÑаÑоÑÑ 1313 let cleanLink = urlInput; 1314 if (cleanLink.startsWith('http://') || cleanLink.startsWith('https://')) { 1315 try { 1316 const parsed = new URL(cleanLink); 1317 cleanLink = parsed.pathname + parsed.search + parsed.hash; 1318 } catch (e) {} 1319 } 1320 inputMarkerLink.value = cleanLink; 1321 1322 // Ð¡Ð¾Ñ ÑанÑем в маÑÐºÐµÑ Ð¸ пеÑеÑÑиÑÑваем ÑÐµÐ½Ñ Ñо Ñкидкой 1323 pushHistory(); 1324 marker.title = productData.title; 1325 marker.link = cleanLink; 1326 updateCalculatedPrice(marker); 1327 1328 // ÐеÑеÑиÑовÑваем каÑÑÑÐµÐ»Ñ Ð¸ Ñ Ð¾Ð»ÑÑ, ÑÑÐ¾Ð±Ñ ÑвидеÑÑ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¼Ð³Ð½Ð¾Ð²ÐµÐ½Ð½Ð¾ 1329 renderBuilderMarkers(); 1330 updateExportCode(); 1331 1332 showImportStatus("ÐаннÑе ÑоваÑа ÑÑпеÑно импоÑÑиÑованÑ!", "success"); 1333 1334 // ÐозвÑаÑаем ÐºÐ½Ð¾Ð¿ÐºÑ Ð² иÑÑ Ð¾Ð´Ð½Ð¾Ðµ ÑоÑÑоÑние 1335 btnText.textContent = originalText; 1336 btnIcon.className = "fa-solid fa-cloud-arrow-down"; 1337 fetchProductBtn.disabled = false;
1338 1339 } catch (error) { 1340 console.error("ÐÑибка пÑи импоÑÑе даннÑÑ ÑоваÑа:", error); 1341 showImportStatus(error.message || "ÐÑоизоÑла неизвеÑÑÐ½Ð°Ñ Ð¾Ñибка пÑи паÑÑинге ÑÑÑаниÑÑ.", "error"); 1342 1343 // ÐозвÑаÑаем ÐºÐ½Ð¾Ð¿ÐºÑ Ð² иÑÑ Ð¾Ð´Ð½Ð¾Ðµ ÑоÑÑоÑние 1344 const btnText = fetchProductBtn.querySelector('span'); 1345 const btnIcon = fetchProductBtn.querySelector('i'); 1346 btnText.textContent = "ÐмпоÑÑиÑоваÑÑ Ð´Ð°Ð½Ð½Ñе"; 1347 btnIcon.className = "fa-solid fa-cloud-arrow-down"; 1348 fetchProductBtn.disabled = false; 1349 } 1350}); 1351 1352// Удаление маÑкеÑа 1353safeAddListener(deleteMarkerBtn, 'click', () => { 1354 const currentSlide = slides[currentSlideIndex]; 1355 if (currentSlide && selectedMarkerId) { 1356 pushHistory(); 1357 currentSlide.markers = currentSlide.markers.filter(m => m.id !== selectedMarkerId); 1358 selectedMarkerId = null; 1359 if (markerEditorSection) markerEditorSection.style.display = 'none'; 1360 renderBuilderMarkers(); 1361 updateExportCode(); 1362 } 1363}); 1364 1365// ========================================================================== 1366// 6. ÐкÑпоÑÑ Ð¸ генеÑаÑÐ¸Ñ ÐºÐ¾Ð´Ð° 1367// ========================================================================== 1368 1369if (exportTabButtons && exportTabButtons.length > 0) { 1370 exportTabButtons.forEach(btn => { 1371 btn.addEventListener('click', () => { 1372 exportTabButtons.forEach(b => b.classList.remove('active')); 1373 btn.classList.add('active'); 1374 activeExportType = btn.getAttribute('data-export'); 1375 updateExportCode(); 1376 }); 1377 }); 1378} 1379 1380function updateExportCode() { 1381 saveToLocalStorage(); 1382 if (!exportTextarea) return; 1383 if (activeExportType === 'json') { 1384 // ФоÑмаÑиÑованнÑй JSON Ñ Ð¾ÑÑÑÑпами 1385 exportTextarea.value = JSON.stringify(slides, null, 4); 1386 } else { 1387 // ÐенеÑиÑÑем кÑаÑивÑй HTML-Ñаблон Ð´Ð»Ñ Ð²ÑÑавки на ÑÐ°Ð¹Ñ 1388 let htmlCode = `<!-- Ðлок «СовеÑÑ Ð´Ð¸Ð·Ð°Ð¹Ð½ÐµÑа» Ð´Ð»Ñ alleyadoma.ru -->\n`; 1389 htmlCode += `<div class="designer-advice__grid">\n`; 1390 htmlCode += ` <div class="designer-advice__media">\n`; 1391 htmlCode += ` <div class="designer-advice-slider">\n`; 1392 htmlCode += ` <div class="slider-wrapper">\n`; 1393 1394 slides.forEach((slide, sIdx) => { 1395 const activeClass = sIdx === 0 ? ' active' : ''; 1396 htmlCode += ` <!-- Слайд ${sIdx + 1}: ${slide.name} -->\n`; 1397 htmlCode += ` <div class="slider-item${activeClass}" data-slide-id="${slide.id}" \n`; 1398 htmlCode += ` data-slide-badge="${slide.badge || 'СÐÐÐТЫ ÐÐÐÐÐÐÐÐ Ð'}" \n`; 1399 htmlCode += ` data-designer-title="${slide.designerTitle || 'ÐÑкÑÑÑÑво инÑеÑÑеÑа'}" \n`; 1400 htmlCode += ` data-designer-name="${slide.designer?.name || 'ÐикÑоÑ'}" \n`; 1401 htmlCode += ` data-designer-avatar="${slide.designer?.avatar || ''}" \n`; 1402 htmlCode += ` data-collection-name="${slide.name || ''}" \n`; 1403 htmlCode += ` data-collection-desc="${slide.description || ''}" \n`; 1404 htmlCode += ` data-collection-link="${slide.link || ''}">\n`; 1405 htmlCode += ` <img src="${slide.image}" alt="${slide.name}">\n`; 1406 htmlCode += ` <div class="complect-markers">\n`; 1407 1408 slide.markers.forEach((marker, mIdx) => { 1409 const discountAttr = marker.discount > 0 ? ` data-discount="${marker.discount}"` : ''; 1410 const oldPriceAttr = marker.discount > 0 ? ` data-old-price="${marker.price}"` : ''; 1411 1412 htmlCode += ` <div class="complect-marker" style="--x: ${marker.x}; --y: ${marker.y};" \n`; 1413 htmlCode += ` data-title="${marker.title}" \n`; 1414 htmlCode += ` data-price="${marker.priceWithDiscount || marker.price}"${discountAttr}${oldPriceAttr} \n`;
1415 htmlCode += ` data-link="${marker.link}">\n`; 1416 htmlCode += ` <span data-href="${marker.link}" class="complect-marker__btn"></span>\n`; 1417 htmlCode += ` </div>\n`; 1418 }); 1419 1420 htmlCode += ` </div>\n`; 1421 htmlCode += ` </div>\n`; 1422 }); 1423 1424 htmlCode += ` </div>\n`; 1425 htmlCode += ` <!-- Ðнопки ÑпÑÐ°Ð²Ð»ÐµÐ½Ð¸Ñ -->\n`; 1426 htmlCode += ` <button class="slider-btn prev" aria-label="ÐÑедÑдÑÑий"><i class="fa-solid fa-chevron-left"></i></button>\n`; 1427 htmlCode += ` <button class="slider-btn next" aria-label="СледÑÑÑий"><i class="fa-solid fa-chevron-right"></i></button>\n`; 1428 htmlCode += ` <div class="slider-pagination"></div>\n`; 1429 htmlCode += ` </div>\n`; 1430 htmlCode += ` </div>\n\n`; 1431 1432 // ÐÑÐ°Ð²Ð°Ñ ÐºÐ¾Ð»Ð¾Ð½ÐºÐ° дизайнеÑа 1433 htmlCode += ` <div class="designer-advice__info">\n`; 1434 htmlCode += ` <div class="designer-advice__badge">${slides[0]?.badge || 'СÐÐÐТЫ ÐÐÐÐÐÐÐÐ Ð'}</div>\n`; 1435 htmlCode += ` <div class="designer-card">\n`; 1436 htmlCode += ` <div class="designer-avatar">\n`; 1437 htmlCode += ` <img src="${slides[0]?.designer?.avatar || ''}" class="designer-avatar-img" alt="ÐваÑаÑ">\n`; 1438 htmlCode += ` </div>\n`; 1439 htmlCode += ` <div class="designer-details">\n`; 1440 htmlCode += ` <span class="designer-title">${slides[0]?.designerTitle || 'ÐÑкÑÑÑÑво инÑеÑÑеÑа'}</span>\n`; 1441 htmlCode += ` <span class="designer-name" id="widget-designer-name">${slides[0]?.designer?.name || ''}</span>\n`; 1442 htmlCode += ` </div>\n`; 1443 htmlCode += ` </div>\n`; 1444 htmlCode += ` <div class="designer-advice__content">\n`; 1445 htmlCode += ` <h3 class="designer-advice__content-title" id="widget-collection-name">${slides[0]?.name || ''}</h3>\n`; 1446 htmlCode += ` <p class="designer-advice__content-desc" id="widget-collection-desc">${slides[0]?.description || ''}</p>\n`; 1447 htmlCode += ` </div>\n`; 1448 1449 htmlCode += ` </div>\n`; 1450 htmlCode += `</div>\n`; 1451 1452 exportTextarea.value = htmlCode; 1453 } 1454} 1455 1456// ÐопиÑование кода в бÑÑÐµÑ Ð¾Ð±Ð¼ÐµÐ½Ð° 1457safeAddListener(copyCodeBtn, 'click', () => { 1458 if (exportTextarea) { 1459 exportTextarea.select(); 1460 document.execCommand('copy'); 1461 } 1462 1463 // ÐизÑалÑное подÑвеÑждение 1464 const originalText = copyCodeBtn.innerHTML; 1465 copyCodeBtn.innerHTML = '<i class="fa-solid fa-check"></i> СкопиÑовано!'; 1466 copyCodeBtn.classList.remove('btn-secondary'); 1467 copyCodeBtn.classList.add('btn-primary'); 1468 1469 setTimeout(() => { 1470 copyCodeBtn.innerHTML = originalText; 1471 copyCodeBtn.classList.remove('btn-primary'); 1472 copyCodeBtn.classList.add('btn-secondary'); 1473 }, 2000); 1474}); 1475 1476// СбÑÐ¾Ñ Ð»Ð¾ÐºÐ°Ð»Ñного Ñ ÑанилиÑа (возвÑÐ°Ñ Ðº иÑÑ Ð¾Ð´Ð½Ñм даннÑм) 1477const resetStorageBtn = document.getElementById('reset-storage-btn'); 1478if (resetStorageBtn) { 1479 resetStorageBtn.addEventListener('click', () => { 1480 if (confirm("ÐÑ Ð´ÐµÐ¹ÑÑвиÑелÑно Ñ Ð¾ÑиÑе ÑбÑоÑиÑÑ Ð²Ñе ваÑи Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸ веÑнÑÑÑÑÑ Ðº демонÑÑÑаÑионнÑм даннÑм по ÑмолÑаниÑ?")) { 1481 localStorage.removeItem(STORAGE_KEY); 1482 location.reload(); 1483 } 1484 }); 1485} 1486 1487// ========================================================================== 1488// 6. СÑаÑÑ Ð¿ÑÐ¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¸ иниÑиализаÑÐ¸Ñ Ð½Ð¾Ð²ÑÑ ÑÑнкÑий 1489// ========================================================================== 1490 1491// ÐниÑиализаÑÐ¸Ñ Ñвайпов Ð´Ð»Ñ Ð¼Ð¾Ð±Ð¸Ð»ÑнÑÑ 1492let touchStartX = 0; 1493let touchStartY = 0; 1494let touchEndX = 0; 1495let touchEndY = 0; 1496 1497function initSwipeAndHover() { 1498 const sliderContainer = document.querySelector('.designer-advice-slider'); 1499 if (sliderContainer) { 1500 // ÐеÑÑÑ Ñвайпа 1501 sliderContainer.addEventListener('touchstart', (e) => { 1502 touchStartX = e.changedTouches[0].clientX; 1503 touchStartY = e.changedTouches[0].clientY; 1504 }, { passive: true }); 1505 1506 sliderContainer.addEventListener('touchend', (e) => { 1507 touchEndX = e.changedTouches[0].clientX; 1508 touchEndY = e.changedTouches[0].clientY; 1509 handleSwipe(); 1510 }, { passive: true }); 1511 1512 // ÐаÑза авÑопÑокÑÑÑки пÑи наведении 1513 sliderContainer.addEventListener('mouseenter', () => { 1514 isMouseInsideSlider = true; 1515 pauseAutoplay(); 1516 }); 1517 sliderContainer.addEventListener('mouseleave', () => { 1518 isMouseInsideSlider = false;
1519 resumeAutoplay(); 1520 }); 1521 } 1522} 1523 1524function handleSwipe() { 1525 const diffX = touchEndX - touchStartX; 1526 const diffY = touchEndY - touchStartY; 1527 1528 // ÐоÑог Ñвайпа по гоÑизонÑали > 50px 1529 if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 50) { 1530 if (diffX > 0) { 1531 goToSlide(currentSlideIndex - 1); 1532 } else { 1533 goToSlide(currentSlideIndex + 1); 1534 } 1535 } 1536} 1537 1538// ÐагÑÑзка Ñайлов Drag-and-Drop Ñ Canvas-ÑжаÑием 1539function compressImage(file, maxWidth, maxHeight, quality, callback) { 1540 const reader = new FileReader(); 1541 reader.onload = function (e) { 1542 const img = new Image(); 1543 img.onload = function () { 1544 const canvas = document.createElement('canvas'); 1545 let width = img.width; 1546 let height = img.height; 1547 1548 if (width > height) { 1549 if (width > maxWidth) { 1550 height = Math.round((height * maxWidth) / width); 1551 width = maxWidth; 1552 } 1553 } else { 1554 if (height > maxHeight) { 1555 width = Math.round((width * maxHeight) / height); 1556 height = maxHeight; 1557 } 1558 } 1559 1560 canvas.width = width; 1561 canvas.height = height; 1562 1563 const ctx = canvas.getContext('2d'); 1564 ctx.drawImage(img, 0, 0, width, height); 1565 1566 const dataUrl = canvas.toDataURL('image/jpeg', quality); 1567 callback(dataUrl); 1568 }; 1569 img.src = e.target.result; 1570 }; 1571 reader.readAsDataURL(file); 1572} 1573 1574function initDragAndDrop() { 1575 const canvas = document.getElementById('builder-canvas'); 1576 const avatarZone = document.getElementById('avatar-drop-zone'); 1577 const avatarInput = document.getElementById('avatar-file-input'); 1578 1579 if (canvas) { 1580 canvas.addEventListener('dragover', (e) => { 1581 e.preventDefault(); 1582 canvas.classList.add('drag-over'); 1583 }); 1584 1585 canvas.addEventListener('dragleave', () => { 1586 canvas.classList.remove('drag-over'); 1587 }); 1588 1589 canvas.addEventListener('drop', (e) => { 1590 e.preventDefault(); 1591 canvas.classList.remove('drag-over'); 1592 1593 const files = e.dataTransfer.files; 1594 if (files && files.length > 0) { 1595 const file = files[0]; 1596 if (file.type.startsWith('image/')) { 1597 pushHistory(); 1598 compressImage(file, 1280, 800, 0.75, (base64) => { 1599 const currentSlide = slides[currentSlideIndex]; 1600 if (currentSlide) { 1601 currentSlide.image = base64; 1602 if (inputSlideImage) inputSlideImage.value = base64; 1603 if (builderImagePreview) builderImagePreview.src = base64; 1604 1605 const activeSlideItem = sliderWrapper.querySelector('.slider-item.active img'); 1606 if (activeSlideItem) { 1607 activeSlideItem.src = base64; 1608 } 1609 updateExportCode(); 1610 } 1611 }); 1612 } 1613 } 1614 }); 1615 } 1616 1617 if (avatarZone && avatarInput) { 1618 avatarZone.addEventListener('click', () => { 1619 avatarInput.click(); 1620 }); 1621 1622 avatarInput.addEventListener('change', (e) => { 1623 const files = e.target.files; 1624 if (files && files.length > 0) { 1625 handleAvatarFile(files[0]); 1626 } 1627 }); 1628 1629 avatarZone.addEventListener('dragover', (e) => { 1630 e.preventDefault(); 1631 avatarZone.classList.add('drag-over'); 1632 }); 1633 1634 avatarZone.addEventListener('dragleave', () => { 1635 avatarZone.classList.remove('drag-over'); 1636 }); 1637 1638 avatarZone.addEventListener('drop', (e) => { 1639 e.preventDefault(); 1640 avatarZone.classList.remove('drag-over'); 1641 1642 const files = e.dataTransfer.files; 1643 if (files && files.length > 0) { 1644 handleAvatarFile(files[0]); 1645 } 1646 }); 1647 } 1648} 1649 1650function handleAvatarFile(file) { 1651 if (file.type.startsWith('image/')) { 1652 pushHistory(); 1653 compressImage(file, 150, 150, 0.75, (base64) => { 1654 const currentSlide = slides[currentSlideIndex]; 1655 if (currentSlide) { 1656 if (!currentSlide.designer) currentSlide.designer = {}; 1657 currentSlide.designer.avatar = base64; 1658 if (inputDesignerAvatar) inputDesignerAvatar.value = base64; 1659 1660 const previewImg = document.getElementById('avatar-drop-zone-preview'); 1661 const icon = document.getElementById('avatar-drop-zone-icon'); 1662 if (previewImg && icon) { 1663 previewImg.src = base64; 1664 previewImg.style.display = 'block'; 1665 icon.style.display = 'none'; 1666 } 1667 1668 designerImg.src = base64; 1669 updateExportCode(); 1670 } 1671 }); 1672 } 1673} 1674 1675// ÐниÑиализаÑÐ¸Ñ Ð¸ÑÑоÑии ввода ÑекÑÑовÑÑ Ð¿Ð¾Ð»ÐµÐ¹ и кнопок Undo/Redo 1676function initHistoryHandlers() {
1677 const undoBtn = document.getElementById('undo-btn'); 1678 const redoBtn = document.getElementById('redo-btn'); 1679 1680 if (undoBtn) undoBtn.addEventListener('click', undo); 1681 if (redoBtn) redoBtn.addEventListener('click', redo); 1682 1683 const inputs = [ 1684 inputSlideName, inputSlideDesc, inputSlideImage, inputSlideLink, 1685 inputSlideBadge, inputSlideDesignerTitle, inputDesignerName, inputDesignerAvatar, 1686 inputMarkerTitle, inputMarkerPrice, inputMarkerDiscount, inputMarkerLink 1687 ]; 1688 1689 inputs.forEach(input => { 1690 if (input) { 1691 input.addEventListener('focus', () => { 1692 stateBeforeInput = JSON.stringify(slides); 1693 }); 1694 input.addEventListener('change', () => { 1695 const currentStateStr = JSON.stringify(slides); 1696 if (stateBeforeInput && stateBeforeInput !== currentStateStr) { 1697 undoStack.push(stateBeforeInput); 1698 redoStack = []; 1699 updateHistoryButtons(); 1700 stateBeforeInput = currentStateStr; 1701 } 1702 }); 1703 } 1704 }); 1705 1706 // ÐоÑÑÑие клавиÑи (Ctrl+Z / Ctrl+Y) 1707 document.addEventListener('keydown', (e) => { 1708 const builderTab = document.getElementById('builder-tab'); 1709 if (!builderTab || !builderTab.classList.contains('active')) return; 1710 1711 if (e.ctrlKey || e.metaKey) { 1712 const key = e.key.toLowerCase(); 1713 if (key === 'z' || key === 'Ñ') { 1714 e.preventDefault(); 1715 undo(); 1716 } else if (key === 'y' || key === 'н') { 1717 e.preventDefault(); 1718 redo(); 1719 } 1720 } 1721 }); 1722} 1723 1724// ÐапÑÑк пÑи загÑÑзке докÑменÑа 1725window.addEventListener('DOMContentLoaded', () => { 1726 // 1. ÐниÑиализиÑÑем даннÑе Ñлайдов (ÑÐ»Ð°Ð¹Ð´Ñ Ð¸Ð· data.js или localStorage) 1727 initSlidesData(); 1728 1729 // 2. ÐниÑиализиÑÑем вÑе пеÑеменнÑе DOM 1730 initDOMElements(); 1731 1732 // 3. ÐÑли ÑÐ»Ð°Ð¹Ð´ÐµÑ Ð½Ð° ÑÑÑаниÑе не найден, полноÑÑÑÑ Ð¿ÑекÑаÑаем вÑполнение (ÑÑÐ¾Ð±Ñ ÑкÑÐ¸Ð¿Ñ Ð½Ðµ ÑабоÑал Ð²Ñ Ð¾Ð»Ð¾ÑÑÑÑ) 1733 if (!sliderWrapper) { 1734 console.warn("[Designer tips] ÐонÑÐµÐ¹Ð½ÐµÑ ÑлайдеÑа не найден на ÑÑÑаниÑе. СкÑÐ¸Ð¿Ñ Ð¾ÑÑановлен."); 1735 return; 1736 } 1737 1738 // 4. ÐниÑиализиÑÑем пÑивÑÐ·ÐºÑ Ð¾ÑновнÑÑ ÑобÑÑий (клики по ÑÑÑелоÑкам и Ñабам) 1739 initMainEvents(); 1740 1741 // 5. ÐапÑÑкаем ÑендеÑинг каÑÑÑели 1742 renderSlider(); 1743 1744 // 6. ÐапÑÑкаем авÑопÑокÑÑÑÐºÑ 1745 startAutoplay(); 1746 1747 // 7. ÐниÑиализиÑÑем дополниÑелÑнÑе обÑабоÑÑики 1748 initSwipeAndHover(); 1749 initDragAndDrop(); 1750 initHistoryHandlers(); 1751 1752 // 8. Ðогика ÑÑÑного импоÑÑа HTML ÑÑÑаниÑÑ ÑоваÑа (в конÑÑÑÑкÑоÑе) 1753 const toggleManualImportBtn = document.getElementById('toggle-manual-import-btn'); 1754 const manualImportContainer = document.getElementById('manual-html-import-container'); 1755 const parseManualHtmlBtn = document.getElementById('parse-manual-html-btn'); 1756 const manualHtmlInput = document.getElementById('manual-html-input'); 1757 1758 if (toggleManualImportBtn && manualImportContainer) { 1759 toggleManualImportBtn.addEventListener('click', (e) => { 1760 e.preventDefault(); 1761 const isHidden = manualImportContainer.style.display === 'none'; 1762 manualImportContainer.style.display = isHidden ? 'block' : 'none'; 1763 toggleManualImportBtn.textContent = isHidden ? 'СкÑÑÑÑ ÑÑÑной ввод' : 'Ðли вÑÑавиÑÑ Ð¸ÑÑ Ð¾Ð´Ð½Ñй код вÑÑÑнÑÑ'; 1764 }); 1765 } 1766 1767 if (parseManualHtmlBtn && manualHtmlInput) { 1768 parseManualHtmlBtn.addEventListener('click', () => { 1769 const currentSlide = slides[currentSlideIndex]; 1770 if (!currentSlide || !selectedMarkerId) { 1771 alert("ÐожалÑйÑÑа, вÑбеÑиÑе ÑоÑÐºÑ Ð½Ð° Ñ Ð¾Ð»ÑÑе."); 1772 return; 1773 } 1774 const marker = currentSlide.markers.find(m => m.id === selectedMarkerId); 1775 if (!marker) return; 1776 1777 const htmlContent = manualHtmlInput.value.trim(); 1778 if (!htmlContent) { 1779 alert("ÐожалÑйÑÑа, вÑÑавÑÑе HTML-код ÑÑÑаниÑÑ."); 1780 return; 1781 } 1782 1783 try { 1784 showImportStatus("РаÑпаковка даннÑÑ ...", "loading"); 1785 const productData = parseProductHTML(htmlContent); 1786 1787 // ÐаполнÑем ÑоÑÐ¼Ñ 1788 if (inputMarkerTitle) inputMarkerTitle.value = productData.title; 1789 if (inputMarkerPrice) inputMarkerPrice.value = formatPrice(productData.price); 1790 if (inputMarkerDiscount) inputMarkerDiscount.value = productData.discount; 1791 1792 // Ð¡Ð¾Ñ ÑанÑем в иÑÑоÑÐ¸Ñ Ð¸ маÑÐºÐµÑ 1793 pushHistory(); 1794 marker.title = productData.title; 1795 updateCalculatedPrice(marker); 1796 1797 renderBuilderMarkers(); 1798 updateExportCode(); 1799 1800 showImportStatus("ÐаннÑе ÑоваÑа ÑÑпеÑно импоÑÑиÑÐ¾Ð²Ð°Ð½Ñ Ð²ÑÑÑнÑÑ!", "success"); 1801 manualHtmlInput.value = ''; 1802 manualImportContainer.style.display = 'none'; 1803 if (toggleManualImportBtn) toggleManualImportBtn.textContent = 'Ðли вÑÑавиÑÑ Ð¸ÑÑ Ð¾Ð´Ð½Ñй код вÑÑÑнÑÑ'; 1804 } catch (err) { 1805 console.error("ÐÑибка ÑÑÑной ÑаÑпаковки:", err); 1806 showImportStatus(err.message || "ÐÑибка паÑÑинга HTML. ÐÑовеÑÑÑе пÑавилÑноÑÑÑ Ð²ÑÑавленного кода.", "error"); 1807 } 1808 }); 1809 } 1810});
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.