|
Поговорим о...
|
|
переделать кнопку закрытия, крестик не по центру круглой кнопки, также саму круглую кнопку нужно поправить пусть чуть меньше будет и отображена корректно с плавной анимацией простой при наведении. Остальной код не трогай, логику отображения не переделывай. также немного уменьши радиус скругления изображения. <!-- Lightbox для изображений в статьях --> <script> class ContentLightbox { constructor() { this.modal = null; this.modalImage = null; this.caption = null; this.images = []; this.currentIndex = 0; this.debug = false; this.initialized = false; this.observer = null; this.debounceTimeout = null; this.processing = false; this.scrollCheckTimeout = null; this.init(); } init() { this.createModal(); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => this.setup(), 300); }); } else { setTimeout(() => this.setup(), 300); } } createModal() { this.modal = document.createElement('div'); this.modal.className = 'content-lightbox'; this.modal.setAttribute('aria-hidden', 'true'); this.modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; z-index: 9999; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; backdrop-filter: blur(8px); `; const container = document.createElement('div'); container.className = 'content-lightbox__container'; container.style.cssText = ` position: relative; max-width: 95vw; max-height: 95vh; margin: 20px; `; const closeBtn = document.createElement('button'); closeBtn.className = 'content-lightbox__close'; closeBtn.setAttribute('aria-label', 'Закрыть'); closeBtn.innerHTML = '<span style="display: block; position: relative; top: -1px;">×</span>'; closeBtn.style.cssText = ` position: absolute; top: -50px; right: 0; background: rgba(255, 255, 255, 0.15); border: none; color: white; font-size: 34px; cursor: pointer; width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; z-index: 10; transition: all 0.2s ease; backdrop-filter: blur(5px); padding: 0; line-height: 1; margin: 0; `; closeBtn.addEventListener('mouseenter', () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.25)'; }); closeBtn.addEventListener('mouseleave', () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.15)'; closeBtn.style.transform = 'none'; }); closeBtn.addEventListener('mousedown', () => { closeBtn.style.transform = 'scale(0.9)'; }); closeBtn.addEventListener('mouseup', () => { closeBtn.style.transform = 'none'; }); closeBtn.addEventListener('click', (e) => { e.stopPropagation(); this.close(); }); this.modalImage = document.createElement('img'); this.modalImage.className = 'content-lightbox__image'; this.modalImage.style.cssText = ` display: block; max-width: 100%; max-height: 85vh; margin: 0 auto; border-radius: 4px; box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); cursor: default; transform: scale(0.96); opacity: 0; transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.4s ease; `; this.caption = document.createElement('div'); this.caption.className = 'content-lightbox__caption'; this.caption.style.cssText = ` text-align: center; color: rgba(255, 255, 255, 0.9); font-size: 15px; margin-top: 15px; padding: 8px 16px; background: rgba(255, 255, 255, 0.08); border-radius: 4px; backdrop-filter: blur(10px); opacity: 0; transform: translateY(10px); transition: opacity 0.4s ease, transform 0.4s ease; `; container.appendChild(closeBtn); container.appendChild(this.modalImage); container.appendChild(this.caption); this.modal.appendChild(container); document.body.appendChild(this.modal); this.modal.addEventListener('click', (e) => { if (e.target === this.modal) { this.close(); } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.isOpen()) { this.close(); } }); document.addEventListener('keydown', (e) => { if (!this.isOpen()) return; if (e.key === 'ArrowLeft') { e.preventDefault(); this.prev(); } else if (e.key === 'ArrowRight') { e.preventDefault(); this.next(); } }); } setup() { if (this.initialized || this.processing) return; this.processing = true; const currentPath = window.location.pathname.toLowerCase(); const isDocsPage = currentPath.includes('/hugo/docs/'); if (!isDocsPage) { this.processing = false; return; } const contentArea = this.findContentArea(); if (!contentArea) { this.processing = false; return; } this.initialized = true; this.processAllImages(contentArea); this.setupObserver(contentArea); this.setupScrollCheck(); this.processing = false; } findContentArea() { const contentSelectors = [ 'main', 'article', '.content', '.post-content', '.article-content', '.entry-content', '.prose', '[role="main"]' ]; for (const selector of contentSelectors) { const element = document.querySelector(selector); if (element) { return element; } } return document.body; } setupObserver(contentArea) { this.observer = new MutationObserver(() => { clearTimeout(this.debounceTimeout); this.debounceTimeout = setTimeout(() => { if (!this.processing) { this.processing = true; setTimeout(() => { this.processAllImages(contentArea); this.processing = false; }, 150); } }, 300); }); const config = { childList: true, subtree: true }; this.observer.observe(contentArea, config); } setupScrollCheck() { window.addEventListener('scroll', () => { clearTimeout(this.scrollCheckTimeout); this.scrollCheckTimeout = setTimeout(() => { this.checkVisibleImages(); }, 200); }); setTimeout(() => { this.checkVisibleImages(); }, 1000); } processAllImages(contentArea) { const images = contentArea.querySelectorAll('img:not(.content-lightbox-processed)'); if (images.length === 0) return; images.forEach(img => { this.processSingleImage(img); }); setTimeout(() => { this.checkVisibleImages(); }, 300); } processSingleImage(img) { const isInTable = img.closest('table, .table, .data-table, .grid, td, th'); img.classList.add('content-lightbox-processed'); // ВАЖНОЕ ИЗМЕНЕНИЕ: Не добавляем класс content-image изображениям в таблицах if (!isInTable && !this.isIcon(img)) { img.classList.add('content-image'); } else if (isInTable) { // Для изображений в таблицах НЕ добавляем никаких классов стилей // Только обрабатываем их как иконки if (this.isIcon(img)) { img.classList.add('not-zoomable'); img.style.cursor = 'default'; return; } } if (this.isIcon(img)) { img.classList.add('not-zoomable'); img.style.cursor = 'default'; return; } img.setAttribute('data-lightbox', 'pending'); if (img.src && !img.hasAttribute('data-original-src')) { img.setAttribute('data-original-src', img.src); } if (!img.complete) { img.addEventListener('load', () => { this.evaluateImage(img); }, { once: true }); img.addEventListener('error', () => { img.classList.add('not-zoomable'); }, { once: true }); } else { this.evaluateImage(img); } } isIcon(img) { const src = (img.src || '').toLowerCase(); const alt = (img.alt || '').toLowerCase(); const classes = (img.className || '').toLowerCase(); const isInTable = img.closest('table, .table, .data-table, .grid, td, th'); // ОСОБАЯ ПРОВЕРКА для таблиц: все изображения в таблицах считаем иконками // если они меньше определенного размера if (isInTable) { const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || 0; // В таблицах считаем иконками всё меньше 150px if (naturalWidth > 0 && naturalWidth < 150 && naturalHeight < 150) { return true; } if (displayedWidth > 0 && displayedWidth < 150) { return true; } // Также проверяем по имени файла const iconPatterns = ['icon', 'logo', 'avatar', 'favicon', 'svg', 'ico']; if (iconPatterns.some(pattern => src.includes(pattern))) { return true; } } const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || 0; const displayedHeight = img.offsetHeight || 0; if ((naturalWidth > 0 && naturalWidth < 50 && naturalHeight < 50) || (displayedWidth > 0 && displayedWidth < 50 && displayedHeight < 50)) { return true; } const iconPatterns = [ 'icon', 'logo', 'avatar', 'favicon', 'sprite', 'emoji', 'svg', 'ico', 'png-16', 'png-32', 'png-64', 'icon-', '-icon', 'thumb', 'thumbnail', 'placeholder' ]; return iconPatterns.some(pattern => src.includes(pattern) || alt.includes(pattern) || classes.includes(pattern) ); } evaluateImage(img) { if (img.classList.contains('not-zoomable')) return; const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || img.width || 0; const displayedHeight = img.offsetHeight || img.height || 0; const MIN_WIDTH = 400; const MIN_HEIGHT = 150; const MIN_DISPLAYED_WIDTH = 300; const MIN_DISPLAYED_HEIGHT = 100; const naturalAspectRatio = naturalWidth / naturalHeight; const isNaturalHorizontal = naturalAspectRatio > 3; const isNaturalVeryHorizontal = naturalAspectRatio > 5; const isNaturalExtremelyHorizontal = naturalAspectRatio > 10; const displayedAspectRatio = displayedWidth / displayedHeight; const isDisplayedExtremelyHorizontal = displayedAspectRatio > 10; const compressionRatio = naturalWidth > 0 ? displayedWidth / naturalWidth : 1; const isCompressed = compressionRatio < 0.9; let isNaturalLargeEnough; if (isNaturalExtremelyHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.2 && naturalHeight >= 50; } else if (isNaturalVeryHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.5 && naturalHeight >= MIN_HEIGHT * 0.6; } else if (isNaturalHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH && naturalHeight >= MIN_HEIGHT * 0.7; } else { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH && naturalHeight >= MIN_HEIGHT; } let isDisplayedLargeEnough; if (isDisplayedExtremelyHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.5 && displayedHeight >= 40; } else if (isNaturalVeryHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.2 && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.8; } else if (isNaturalHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.9; } else { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT; } const isLargeEnough = isNaturalLargeEnough || (isCompressed && isDisplayedLargeEnough); const isFullyVisible = this.isFullyVisible(img, displayedWidth, displayedHeight); const isLikelyScreenshot = (naturalWidth >= 800 && naturalWidth <= 1920) && (naturalHeight >= 600 && naturalHeight <= 1080) && Math.abs(naturalWidth / naturalHeight - 16/9) < 0.2; const isSuitableForZoom = (isLargeEnough || isLikelyScreenshot) && !isFullyVisible; if (isSuitableForZoom) { this.makeZoomable(img); } else { img.classList.add('not-zoomable'); img.style.cursor = 'default'; } } isFullyVisible(img, displayedWidth = null, displayedHeight = null) { const imgWidth = displayedWidth || img.offsetWidth || img.naturalWidth; const container = img.parentElement; if (!container) return false; const containerWidth = container.clientWidth || container.offsetWidth; return imgWidth < containerWidth * 0.90; } checkVisibleImages() { const unevaluatedImages = document.querySelectorAll('.content-image[data-lightbox="pending"]'); unevaluatedImages.forEach(img => { const rect = img.getBoundingClientRect(); const isVisible = ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); if (isVisible && !img.classList.contains('not-zoomable') && !img.classList.contains('is-zoomable')) { this.evaluateImage(img); } }); } makeZoomable(img) { const existingIndex = this.images.indexOf(img); if (existingIndex !== -1) { this.images.splice(existingIndex, 1); } this.images.push(img); img.classList.add('is-zoomable'); img.classList.remove('not-zoomable'); img.setAttribute('data-lightbox', 'true'); img.style.cursor = 'zoom-in'; img.onclick = null; img.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const index = this.images.indexOf(img); if (index !== -1) { this.currentIndex = index; this.open(img.dataset.originalSrc || img.src, img.alt || img.title || ''); } }, { once: false }); // ПЛАВНАЯ анимация при наведении img.addEventListener('mouseenter', () => { if (img.classList.contains('is-zoomable')) { img.style.transition = 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; img.style.opacity = '0.92'; img.style.transform = 'translateY(-3px)'; } }); img.addEventListener('mouseleave', () => { if (img.classList.contains('is-zoomable')) { img.style.transition = 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; img.style.opacity = '1'; img.style.transform = 'translateY(0)'; } }); } open(src, caption) { this.modalImage.src = src; this.modalImage.alt = caption; this.caption.textContent = caption; this.modal.setAttribute('aria-hidden', 'false'); this.modal.style.visibility = 'visible'; requestAnimationFrame(() => { this.modal.style.opacity = '1'; setTimeout(() => { this.modalImage.style.opacity = '1'; this.modalImage.style.transform = 'scale(1)'; this.caption.style.opacity = '1'; this.caption.style.transform = 'translateY(0)'; }, 50); }); document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; } close() { this.modalImage.style.opacity = '0'; this.modalImage.style.transform = 'scale(0.96)'; this.caption.style.opacity = '0'; this.caption.style.transform = 'translateY(10px)'; setTimeout(() => { this.modal.style.opacity = '0'; this.modal.style.visibility = 'hidden'; this.modal.setAttribute('aria-hidden', 'true'); document.body.style.overflow = ''; document.documentElement.style.overflow = ''; }, 400); } isOpen() { return this.modal.getAttribute('aria-hidden') === 'false'; } next() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex + 1) % this.images.length; const nextImg = this.images[this.currentIndex]; this.open(nextImg.dataset.originalSrc || nextImg.src, nextImg.alt || nextImg.title || ''); } } prev() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length; const prevImg = this.images[this.currentIndex]; this.open(prevImg.dataset.originalSrc || prevImg.src, prevImg.alt || prevImg.title || ''); } } } // Инициализация document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { window.contentLightbox = new ContentLightbox(); }, 500); const style = document.createElement('style'); style.textContent = ` /* Базовые стили для изображений в контенте (кроме таблиц) */ .content-image { max-width: 100%; height: auto; display: block; margin: 1.5rem auto; border-radius: 4px; transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); /* Плавная easing-функция */ } /* Стили для изображений, которые можно увеличивать */ .content-image.is-zoomable { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); cursor: zoom-in !important; } .content-image.is-zoomable:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); opacity: 0.92; transform: translateY(-3px); } /* Для изображений, которые нельзя увеличивать */ .content-image.not-zoomable { cursor: default !important; box-shadow: none !important; } .content-image.not-zoomable:hover { transform: none !important; opacity: 1 !important; box-shadow: none !important; } /* ВАЖНО: Изображения в таблицах НЕ получают никаких стилей от lightbox */ /* Используем более специфичные селекторы для таблиц */ table img, .table img, .data-table img, .grid img, td img, th img { /* Ваши оригинальные стили для таблиц остаются нетронутыми */ /* Мы НЕ переопределяем их */ all: unset !important; max-width: none !important; margin: 0 !important; display: inline !important; vertical-align: middle !important; } /* Гарантируем, что наши стили не применяются к таблицам */ table .content-image, .table .content-image, .data-table .content-image, .grid .content-image, td .content-image, th .content-image { all: unset !important; display: inline !important; margin: 0 !important; max-width: none !important; border-radius: 0 !important; box-shadow: none !important; cursor: default !important; transform: none !important; transition: none !important; } /* Стили для кнопки закрытия */ .content-lightbox__close { display: flex !important; align-items: center !important; justify-content: center !important; padding: 0 !important; line-height: 1 !important; font-family: Arial, sans-serif !important; font-weight: 300 !important; } .content-lightbox__close span { display: block; position: relative; top: -1px; } .content-lightbox__close:hover { background: rgba(255, 255, 255, 0.25) !important; } .content-lightbox__close:active { transform: scale(0.95) !important; } /* Стили для мобильных устройств */ @media (max-width: 768px) { .content-image { margin: 1rem auto; border-radius: 3px; } .content-image.is-zoomable { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .content-image.is-zoomable:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); } .content-lightbox__close { top: -40px !important; right: -10px !important; width: 36px !important; height: 36px !important; font-size: 28px !important; } .content-lightbox__close span { top: -0.5px; } } @media (hover: none) { .content-image.is-zoomable:hover { transform: none !important; } } /* Плавные переходы */ * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } `; document.head.appendChild(style); }); window.addEventListener('load', () => { setTimeout(() => { if (window.contentLightbox && window.contentLightbox.checkVisibleImages) { window.contentLightbox.checkVisibleImages(); } }, 1000); }); let searchDebounce; document.addEventListener('input', (e) => { if (e.target.type === 'search' || e.target.type === 'text') { clearTimeout(searchDebounce); searchDebounce = setTimeout(() => { if (window.contentLightbox && window.contentLightbox.processAllImages) { const contentArea = document.querySelector('main, article, .content') || document.body; window.contentLightbox.processAllImages(contentArea); } }, 600); } }); </script>
|
<!-- Lightbox для изображений в статьях --> <script> class ContentLightbox { constructor() { this.modal = null; this.modalImage = null; this.caption = null; this.images = []; this.currentIndex = 0; this.debug = false; this.initialized = false; this.observer = null; this.debounceTimeout = null; this.processing = false; this.scrollCheckTimeout = null; this.init(); }
init() { this.createModal(); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => this.setup(), 300); }); } else { setTimeout(() => this.setup(), 300); } }
createModal() { this.modal = document.createElement('div'); this.modal.className = 'content-lightbox'; this.modal.setAttribute('aria-hidden', 'true'); this.modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; z-index: 9999; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; backdrop-filter: blur(8px); `;
const container = document.createElement('div'); container.className = 'content-lightbox__container'; container.style.cssText = ` position: relative; max-width: 95vw; max-height: 95vh; margin: 20px; `;
const closeBtn = document.createElement('button'); closeBtn.className = 'content-lightbox__close'; closeBtn.setAttribute('aria-label', 'Закрыть');
closeBtn.innerHTML = '<span style="display: block; position: relative;">×</span>';
closeBtn.style.cssText = ` position: absolute; top: -45px; right: 0; background: rgba(255, 255, 255, 0.15); border: none; color: white; font-size: 28px; cursor: pointer; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; z-index: 10; transition: all 0.2s ease; backdrop-filter: blur(5px); padding: 0; line-height: 1; margin: 0; `;
closeBtn.addEventListener('mouseenter', () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.25)'; });
closeBtn.addEventListener('mouseleave', () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.15)'; closeBtn.style.transform = 'none'; });
closeBtn.addEventListener('mousedown', () => { closeBtn.style.transform = 'scale(0.9)'; });
closeBtn.addEventListener('mouseup', () => { closeBtn.style.transform = 'none'; });
closeBtn.addEventListener('click', (e) => { e.stopPropagation(); this.close(); });
this.modalImage = document.createElement('img'); this.modalImage.className = 'content-lightbox__image'; this.modalImage.style.cssText = ` display: block; max-width: 100%; max-height: 85vh; margin: 0 auto; border-radius: 3px; box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); cursor: default; transform: scale(0.96); opacity: 0; transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.4s ease; `;
this.caption = document.createElement('div'); this.caption.className = 'content-lightbox__caption'; this.caption.style.cssText = ` text-align: center; color: rgba(255, 255, 255, 0.9); font-size: 15px; margin-top: 15px; padding: 8px 16px; background: rgba(255, 255, 255, 0.08); border-radius: 3px; backdrop-filter: blur(10px); opacity: 0; transform: translateY(10px); transition: opacity 0.4s ease, transform 0.4s ease; `;
container.appendChild(closeBtn); container.appendChild(this.modalImage); container.appendChild(this.caption); this.modal.appendChild(container); document.body.appendChild(this.modal);
this.modal.addEventListener('click', (e) => { if (e.target === this.modal) { this.close(); } });
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.isOpen()) { this.close(); } });
document.addEventListener('keydown', (e) => { if (!this.isOpen()) return; if (e.key === 'ArrowLeft') { e.preventDefault(); this.prev(); } else if (e.key === 'ArrowRight') { e.preventDefault(); this.next(); } }); }
setup() { if (this.initialized || this.processing) return; this.processing = true;
const currentPath = window.location.pathname.toLowerCase(); const isDocsPage = currentPath.includes('/hugo/docs/');
if (!isDocsPage) { this.processing = false; return; }
const contentArea = this.findContentArea(); if (!contentArea) { this.processing = false; return; }
this.initialized = true; this.processAllImages(contentArea); this.setupObserver(contentArea); this.setupScrollCheck(); this.processing = false; }
findContentArea() { const contentSelectors = [ 'main', 'article', '.content', '.post-content', '.article-content', '.entry-content', '.prose', '[role="main"]' ];
for (const selector of contentSelectors) { const element = document.querySelector(selector); if (element) { return element; } }
return document.body; }
setupObserver(contentArea) { this.observer = new MutationObserver(() => { clearTimeout(this.debounceTimeout); this.debounceTimeout = setTimeout(() => { if (!this.processing) { this.processing = true; setTimeout(() => { this.processAllImages(contentArea); this.processing = false; }, 150); } }, 300); });
const config = { childList: true, subtree: true };
this.observer.observe(contentArea, config); }
setupScrollCheck() { window.addEventListener('scroll', () => { clearTimeout(this.scrollCheckTimeout); this.scrollCheckTimeout = setTimeout(() => { this.checkVisibleImages(); }, 200); });
setTimeout(() => { this.checkVisibleImages(); }, 1000); }
processAllImages(contentArea) { const images = contentArea.querySelectorAll('img:not(.content-lightbox-processed)');
if (images.length === 0) return;
images.forEach(img => { this.processSingleImage(img); });
setTimeout(() => { this.checkVisibleImages(); }, 300); }
processSingleImage(img) { const isInTable = img.closest('table, .table, .data-table, .grid, td, th');
img.classList.add('content-lightbox-processed');
// ВАЖНОЕ ИЗМЕНЕНИЕ: Не добавляем класс content-image изображениям в таблицах if (!isInTable && !this.isIcon(img)) { img.classList.add('content-image'); } else if (isInTable) { // Для изображений в таблицах НЕ добавляем никаких классов стилей // Только обрабатываем их как иконки if (this.isIcon(img)) { img.classList.add('not-zoomable'); img.style.cursor = 'default'; return; } }
if (this.isIcon(img)) { img.classList.add('not-zoomable'); img.style.cursor = 'default'; return; }
img.setAttribute('data-lightbox', 'pending'); if (img.src && !img.hasAttribute('data-original-src')) { img.setAttribute('data-original-src', img.src); }
if (!img.complete) { img.addEventListener('load', () => { this.evaluateImage(img); }, { once: true }); img.addEventListener('error', () => { img.classList.add('not-zoomable'); }, { once: true }); } else { this.evaluateImage(img); } }
isIcon(img) { const src = (img.src || '').toLowerCase(); const alt = (img.alt || '').toLowerCase(); const classes = (img.className || '').toLowerCase(); const isInTable = img.closest('table, .table, .data-table, .grid, td, th');
// ОСОБАЯ ПРОВЕРКА для таблиц: все изображения в таблицах считаем иконками // если они меньше определенного размера if (isInTable) { const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || 0;
// В таблицах считаем иконками всё меньше 150px if (naturalWidth > 0 && naturalWidth < 150 && naturalHeight < 150) { return true; } if (displayedWidth > 0 && displayedWidth < 150) { return true; }
// Также проверяем по имени файла const iconPatterns = ['icon', 'logo', 'avatar', 'favicon', 'svg', 'ico']; if (iconPatterns.some(pattern => src.includes(pattern))) { return true; } }
const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || 0; const displayedHeight = img.offsetHeight || 0;
if ((naturalWidth > 0 && naturalWidth < 50 && naturalHeight < 50) || (displayedWidth > 0 && displayedWidth < 50 && displayedHeight < 50)) { return true; }
const iconPatterns = [ 'icon', 'logo', 'avatar', 'favicon', 'sprite', 'emoji', 'svg', 'ico', 'png-16', 'png-32', 'png-64', 'icon-', '-icon', 'thumb', 'thumbnail', 'placeholder' ];
return iconPatterns.some(pattern => src.includes(pattern) || alt.includes(pattern) || classes.includes(pattern) ); }
evaluateImage(img) { if (img.classList.contains('not-zoomable')) return;
const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || img.width || 0; const displayedHeight = img.offsetHeight || img.height || 0;
const MIN_WIDTH = 400; const MIN_HEIGHT = 150; const MIN_DISPLAYED_WIDTH = 300; const MIN_DISPLAYED_HEIGHT = 100;
const naturalAspectRatio = naturalWidth / naturalHeight; const isNaturalHorizontal = naturalAspectRatio > 3; const isNaturalVeryHorizontal = naturalAspectRatio > 5; const isNaturalExtremelyHorizontal = naturalAspectRatio > 10;
const displayedAspectRatio = displayedWidth / displayedHeight; const isDisplayedExtremelyHorizontal = displayedAspectRatio > 10;
const compressionRatio = naturalWidth > 0 ? displayedWidth / naturalWidth : 1; const isCompressed = compressionRatio < 0.9;
let isNaturalLargeEnough;
if (isNaturalExtremelyHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.2 && naturalHeight >= 50; } else if (isNaturalVeryHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.5 && naturalHeight >= MIN_HEIGHT * 0.6; } else if (isNaturalHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH && naturalHeight >= MIN_HEIGHT * 0.7; } else { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH && naturalHeight >= MIN_HEIGHT; }
let isDisplayedLargeEnough;
if (isDisplayedExtremelyHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.5 && displayedHeight >= 40; } else if (isNaturalVeryHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.2 && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.8; } else if (isNaturalHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.9; } else { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT; }
const isLargeEnough = isNaturalLargeEnough || (isCompressed && isDisplayedLargeEnough);
const isFullyVisible = this.isFullyVisible(img, displayedWidth, displayedHeight);
const isLikelyScreenshot = (naturalWidth >= 800 && naturalWidth <= 1920) && (naturalHeight >= 600 && naturalHeight <= 1080) && Math.abs(naturalWidth / naturalHeight - 16/9) < 0.2;
const isSuitableForZoom = (isLargeEnough || isLikelyScreenshot) && !isFullyVisible;
if (isSuitableForZoom) { this.makeZoomable(img); } else { img.classList.add('not-zoomable'); img.style.cursor = 'default'; } }
isFullyVisible(img, displayedWidth = null, displayedHeight = null) { const imgWidth = displayedWidth || img.offsetWidth || img.naturalWidth; const container = img.parentElement; if (!container) return false;
const containerWidth = container.clientWidth || container.offsetWidth; return imgWidth < containerWidth * 0.90; }
checkVisibleImages() { const unevaluatedImages = document.querySelectorAll('.content-image[data-lightbox="pending"]');
unevaluatedImages.forEach(img => { const rect = img.getBoundingClientRect(); const isVisible = ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) );
if (isVisible && !img.classList.contains('not-zoomable') && !img.classList.contains('is-zoomable')) { this.evaluateImage(img); } }); }
makeZoomable(img) { const existingIndex = this.images.indexOf(img); if (existingIndex !== -1) { this.images.splice(existingIndex, 1); }
this.images.push(img);
img.classList.add('is-zoomable'); img.classList.remove('not-zoomable'); img.setAttribute('data-lightbox', 'true'); img.style.cursor = 'zoom-in';
img.onclick = null;
img.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const index = this.images.indexOf(img); if (index !== -1) { this.currentIndex = index; this.open(img.dataset.originalSrc || img.src, img.alt || img.title || ''); } }, { once: false });
// ПЛАВНАЯ анимация при наведении img.addEventListener('mouseenter', () => { if (img.classList.contains('is-zoomable')) { img.style.transition = 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; img.style.opacity = '0.92'; img.style.transform = 'translateY(-3px)'; } });
img.addEventListener('mouseleave', () => { if (img.classList.contains('is-zoomable')) { img.style.transition = 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; img.style.opacity = '1'; img.style.transform = 'translateY(0)'; } }); }
open(src, caption) { this.modalImage.src = src; this.modalImage.alt = caption; this.caption.textContent = caption; this.modal.setAttribute('aria-hidden', 'false'); this.modal.style.visibility = 'visible';
requestAnimationFrame(() => { this.modal.style.opacity = '1'; setTimeout(() => { this.modalImage.style.opacity = '1'; this.modalImage.style.transform = 'scale(1)'; this.caption.style.opacity = '1'; this.caption.style.transform = 'translateY(0)'; }, 50); });
document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; }
close() { this.modalImage.style.opacity = '0'; this.modalImage.style.transform = 'scale(0.96)'; this.caption.style.opacity = '0'; this.caption.style.transform = 'translateY(10px)';
setTimeout(() => { this.modal.style.opacity = '0'; this.modal.style.visibility = 'hidden'; this.modal.setAttribute('aria-hidden', 'true'); document.body.style.overflow = ''; document.documentElement.style.overflow = ''; }, 400); }
isOpen() { return this.modal.getAttribute('aria-hidden') === 'false'; }
next() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex + 1) % this.images.length; const nextImg = this.images[this.currentIndex]; this.open(nextImg.dataset.originalSrc || nextImg.src, nextImg.alt || nextImg.title || ''); } }
prev() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length; const prevImg = this.images[this.currentIndex]; this.open(prevImg.dataset.originalSrc || prevImg.src, prevImg.alt || prevImg.title || ''); } } }
// Инициализация document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { window.contentLightbox = new ContentLightbox(); }, 500);
const style = document.createElement('style'); style.textContent = ` /* Базовые стили для изображений в контенте (кроме таблиц) */ .content-image { max-width: 100%; height: auto; display: block; margin: 1.5rem auto; border-radius: 3px; transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); /* Плавная easing-функция */ }
/* Стили для изображений, которые можно увеличивать */ .content-image.is-zoomable { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); cursor: zoom-in !important; }
.content-image.is-zoomable:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); opacity: 0.92; transform: translateY(-3px); }
/* Для изображений, которые нельзя увеличивать */ .content-image.not-zoomable { cursor: default !important; box-shadow: none !important; }
.content-image.not-zoomable:hover { transform: none !important; opacity: 1 !important; box-shadow: none !important; }
/* ВАЖНО: Изображения в таблицах НЕ получают никаких стилей от lightbox */ /* Используем более специфичные селекторы для таблиц */ table img, .table img, .data-table img, .grid img, td img, th img { /* Ваши оригинальные стили для таблиц остаются нетронутыми */ /* Мы НЕ переопределяем их */ all: unset !important; max-width: none !important; margin: 0 !important; display: inline !important; vertical-align: middle !important; }
/* Гарантируем, что наши стили не применяются к таблицам */ table .content-image, .table .content-image, .data-table .content-image, .grid .content-image, td .content-image, th .content-image { all: unset !important; display: inline !important; margin: 0 !important; max-width: none !important; border-radius: 0 !important; box-shadow: none !important; cursor: default !important; transform: none !important; transition: none !important; }
/* Стили для кнопки закрытия */ .content-lightbox__close { display: flex !important; align-items: center !important; justify-content: center !important; padding: 0 !important; line-height: 1 !important; font-family: Arial, sans-serif !important; font-weight: 300 !important; }
.content-lightbox__close span { display: block; position: relative; }
.content-lightbox__close:hover { background: rgba(255, 255, 255, 0.25) !important; }
.content-lightbox__close:active { transform: scale(0.95) !important; }
/* Стили для мобильных устройств */ @media (max-width: 768px) { .content-image { margin: 1rem auto; border-radius: 2px; }
.content-image.is-zoomable { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); }
.content-image.is-zoomable:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); }
.content-lightbox__close { top: -40px !important; right: -10px !important; width: 36px !important; height: 36px !important; font-size: 28px !important; }
.content-lightbox__close span { } }
@media (hover: none) { .content-image.is-zoomable:hover { transform: none !important; } }
/* Плавные переходы */ * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } `; document.head.appendChild(style); });
window.addEventListener('load', () => { setTimeout(() => { if (window.contentLightbox && window.contentLightbox.checkVisibleImages) { window.contentLightbox.checkVisibleImages(); } }, 1000); });
let searchDebounce; document.addEventListener('input', (e) => { if (e.target.type === 'search' || e.target.type === 'text') { clearTimeout(searchDebounce); searchDebounce = setTimeout(() => { if (window.contentLightbox && window.contentLightbox.processAllImages) { const contentArea = document.querySelector('main, article, .content') || document.body; window.contentLightbox.processAllImages(contentArea); } }, 600); } }); </script>Добавлено (2025-12-29, 14:30) --------------------------------------------- <!-- Lightbox для изображений в статьях --> <script> class ContentLightbox { constructor() { this.modal = null; this.modalImage = null; this.caption = null; this.images = []; this.currentIndex = 0; this.debug = false; this.initialized = false; this.observer = null; this.debounceTimeout = null; this.processing = false; this.scrollCheckTimeout = null; this.init(); }
init() { this.createModal(); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => this.setup(), 300); }); } else { setTimeout(() => this.setup(), 300); } }
createModal() { this.modal = document.createElement('div'); this.modal.className = 'content-lightbox'; this.modal.setAttribute('aria-hidden', 'true'); this.modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; z-index: 9999; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; backdrop-filter: blur(8px); `;
const container = document.createElement('div'); container.className = 'content-lightbox__container'; container.style.cssText = ` position: relative; max-width: 95vw; max-height: 95vh; margin: 20px; `;
const closeBtn = document.createElement('button'); closeBtn.className = 'content-lightbox__close'; closeBtn.setAttribute('aria-label', 'Закрыть');
closeBtn.textContent = '×'; // Убираем span, используем простой текст для лучшего центрирования
closeBtn.style.cssText = ` position: absolute; top: -45px; right: 0; background: rgba(255, 255, 255, 0.15); border: none; color: white; font-size: 24px; // Снижаем размер для кнопки 36x36 cursor: pointer; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; z-index: 10; transition: all 0.2s ease; backdrop-filter: blur(5px); padding: 0; line-height: 1; margin: 0; font-family: Arial, sans-serif; font-weight: 300; `;
closeBtn.addEventListener('mouseenter', () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.25)'; });
closeBtn.addEventListener('mouseleave', () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.15)'; closeBtn.style.transform = 'none'; });
closeBtn.addEventListener('mousedown', () => { closeBtn.style.transform = 'scale(0.9)'; });
closeBtn.addEventListener('mouseup', () => { closeBtn.style.transform = 'none'; });
closeBtn.addEventListener('click', (e) => { e.stopPropagation(); this.close(); });
this.modalImage = document.createElement('img'); this.modalImage.className = 'content-lightbox__image'; this.modalImage.style.cssText = ` display: block; max-width: 100%; max-height: 85vh; margin: 0 auto; border-radius: 3px; box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); cursor: default; transform: scale(0.96); opacity: 0; transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.4s ease; `;
this.caption = document.createElement('div'); this.caption.className = 'content-lightbox__caption'; this.caption.style.cssText = ` text-align: center; color: rgba(255, 255, 255, 0.9); font-size: 15px; margin-top: 15px; padding: 8px 16px; background: rgba(255, 255, 255, 0.08); border-radius: 3px; backdrop-filter: blur(10px); opacity: 0; transform: translateY(10px); transition: opacity 0.4s ease, transform 0.4s ease; `;
container.appendChild(closeBtn); container.appendChild(this.modalImage); container.appendChild(this.caption); this.modal.appendChild(container); document.body.appendChild(this.modal);
this.modal.addEventListener('click', (e) => { if (e.target === this.modal) { this.close(); } });
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.isOpen()) { this.close(); } });
document.addEventListener('keydown', (e) => { if (!this.isOpen()) return; if (e.key === 'ArrowLeft') { e.preventDefault(); this.prev(); } else if (e.key === 'ArrowRight') { e.preventDefault(); this.next(); } }); }
setup() { if (this.initialized || this.processing) return; this.processing = true;
const currentPath = window.location.pathname.toLowerCase(); const isDocsPage = currentPath.includes('/hugo/docs/');
if (!isDocsPage) { this.processing = false; return; }
const contentArea = this.findContentArea(); if (!contentArea) { this.processing = false; return; }
this.initialized = true; this.processAllImages(contentArea); this.setupObserver(contentArea); this.setupScrollCheck(); this.processing = false; }
findContentArea() { const contentSelectors = [ 'main', 'article', '.content', '.post-content', '.article-content', '.entry-content', '.prose', '[role="main"]' ];
for (const selector of contentSelectors) { const element = document.querySelector(selector); if (element) { return element; } }
return document.body; }
setupObserver(contentArea) { this.observer = new MutationObserver(() => { clearTimeout(this.debounceTimeout); this.debounceTimeout = setTimeout(() => { if (!this.processing) { this.processing = true; setTimeout(() => { this.processAllImages(contentArea); this.processing = false; }, 150); } }, 300); });
const config = { childList: true, subtree: true };
this.observer.observe(contentArea, config); }
setupScrollCheck() { window.addEventListener('scroll', () => { clearTimeout(this.scrollCheckTimeout); this.scrollCheckTimeout = setTimeout(() => { this.checkVisibleImages(); }, 200); });
setTimeout(() => { this.checkVisibleImages(); }, 1000); }
processAllImages(contentArea) { const images = contentArea.querySelectorAll('img:not(.content-lightbox-processed)');
if (images.length === 0) return;
images.forEach(img => { this.processSingleImage(img); });
setTimeout(() => { this.checkVisibleImages(); }, 300); }
processSingleImage(img) { const isInTable = img.closest('table, .table, .data-table, .grid, td, th');
img.classList.add('content-lightbox-processed');
// ВАЖНОЕ ИЗМЕНЕНИЕ: Не добавляем класс content-image изображениям в таблицах if (!isInTable && !this.isIcon(img)) { img.classList.add('content-image'); } else if (isInTable) { // Для изображений в таблицах НЕ добавляем никаких классов стилей // Только обрабатываем их как иконки if (this.isIcon(img)) { img.classList.add('not-zoomable'); img.style.cursor = 'default'; return; } }
if (this.isIcon(img)) { img.classList.add('not-zoomable'); img.style.cursor = 'default'; return; }
img.setAttribute('data-lightbox', 'pending'); if (img.src && !img.hasAttribute('data-original-src')) { img.setAttribute('data-original-src', img.src); }
if (!img.complete) { img.addEventListener('load', () => { this.evaluateImage(img); }, { once: true }); img.addEventListener('error', () => { img.classList.add('not-zoomable'); }, { once: true }); } else { this.evaluateImage(img); } }
isIcon(img) { const src = (img.src || '').toLowerCase(); const alt = (img.alt || '').toLowerCase(); const classes = (img.className || '').toLowerCase(); const isInTable = img.closest('table, .table, .data-table, .grid, td, th');
// ОСОБАЯ ПРОВЕРКА для таблиц: все изображения в таблицах считаем иконками // если они меньше определенного размера if (isInTable) { const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || 0;
// В таблицах считаем иконками всё меньше 150px if (naturalWidth > 0 && naturalWidth < 150 && naturalHeight < 150) { return true; } if (displayedWidth > 0 && displayedWidth < 150) { return true; }
// Также проверяем по имени файла const iconPatterns = ['icon', 'logo', 'avatar', 'favicon', 'svg', 'ico']; if (iconPatterns.some(pattern => src.includes(pattern))) { return true; } }
const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || 0; const displayedHeight = img.offsetHeight || 0;
if ((naturalWidth > 0 && naturalWidth < 50 && naturalHeight < 50) || (displayedWidth > 0 && displayedWidth < 50 && displayedHeight < 50)) { return true; }
const iconPatterns = [ 'icon', 'logo', 'avatar', 'favicon', 'sprite', 'emoji', 'svg', 'ico', 'png-16', 'png-32', 'png-64', 'icon-', '-icon', 'thumb', 'thumbnail', 'placeholder' ];
return iconPatterns.some(pattern => src.includes(pattern) || alt.includes(pattern) || classes.includes(pattern) ); }
evaluateImage(img) { if (img.classList.contains('not-zoomable')) return;
const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || img.width || 0; const displayedHeight = img.offsetHeight || img.height || 0;
const MIN_WIDTH = 400; const MIN_HEIGHT = 150; const MIN_DISPLAYED_WIDTH = 300; const MIN_DISPLAYED_HEIGHT = 100;
const naturalAspectRatio = naturalWidth / naturalHeight; const isNaturalHorizontal = naturalAspectRatio > 3; const isNaturalVeryHorizontal = naturalAspectRatio > 5; const isNaturalExtremelyHorizontal = naturalAspectRatio > 10;
const displayedAspectRatio = displayedWidth / displayedHeight; const isDisplayedExtremelyHorizontal = displayedAspectRatio > 10;
const compressionRatio = naturalWidth > 0 ? displayedWidth / naturalWidth : 1; const isCompressed = compressionRatio < 0.9;
let isNaturalLargeEnough;
if (isNaturalExtremelyHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.2 && naturalHeight >= 50; } else if (isNaturalVeryHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.5 && naturalHeight >= MIN_HEIGHT * 0.6; } else if (isNaturalHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH && naturalHeight >= MIN_HEIGHT * 0.7; } else { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH && naturalHeight >= MIN_HEIGHT; }
let isDisplayedLargeEnough;
if (isDisplayedExtremelyHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.5 && displayedHeight >= 40; } else if (isNaturalVeryHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.2 && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.8; } else if (isNaturalHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.9; } else { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT; }
const isLargeEnough = isNaturalLargeEnough || (isCompressed && isDisplayedLargeEnough);
const isFullyVisible = this.isFullyVisible(img, displayedWidth, displayedHeight);
const isLikelyScreenshot = (naturalWidth >= 800 && naturalWidth <= 1920) && (naturalHeight >= 600 && naturalHeight <= 1080) && Math.abs(naturalWidth / naturalHeight - 16/9) < 0.2;
const isSuitableForZoom = (isLargeEnough || isLikelyScreenshot) && !isFullyVisible;
if (isSuitableForZoom) { this.makeZoomable(img); } else { img.classList.add('not-zoomable'); img.style.cursor = 'default'; } }
isFullyVisible(img, displayedWidth = null, displayedHeight = null) { const imgWidth = displayedWidth || img.offsetWidth || img.naturalWidth; const container = img.parentElement; if (!container) return false;
const containerWidth = container.clientWidth || container.offsetWidth; return imgWidth < containerWidth * 0.90; }
checkVisibleImages() { const unevaluatedImages = document.querySelectorAll('.content-image[data-lightbox="pending"]');
unevaluatedImages.forEach(img => { const rect = img.getBoundingClientRect(); const isVisible = ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) );
if (isVisible && !img.classList.contains('not-zoomable') && !img.classList.contains('is-zoomable')) { this.evaluateImage(img); } }); }
makeZoomable(img) { const existingIndex = this.images.indexOf(img); if (existingIndex !== -1) { this.images.splice(existingIndex, 1); }
this.images.push(img);
img.classList.add('is-zoomable'); img.classList.remove('not-zoomable'); img.setAttribute('data-lightbox', 'true'); img.style.cursor = 'zoom-in';
img.onclick = null;
img.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const index = this.images.indexOf(img); if (index !== -1) { this.currentIndex = index; this.open(img.dataset.originalSrc || img.src, img.alt || img.title || ''); } }, { once: false });
// ПЛАВНАЯ анимация при наведении img.addEventListener('mouseenter', () => { if (img.classList.contains('is-zoomable')) { img.style.transition = 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; img.style.opacity = '0.92'; img.style.transform = 'translateY(-3px)'; } });
img.addEventListener('mouseleave', () => { if (img.classList.contains('is-zoomable')) { img.style.transition = 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; img.style.opacity = '1'; img.style.transform = 'translateY(0)'; } }); }
open(src, caption) { this.modalImage.src = src; this.modalImage.alt = caption; this.caption.textContent = caption; this.modal.setAttribute('aria-hidden', 'false'); this.modal.style.visibility = 'visible';
requestAnimationFrame(() => { this.modal.style.opacity = '1'; setTimeout(() => { this.modalImage.style.opacity = '1'; this.modalImage.style.transform = 'scale(1)'; this.caption.style.opacity = '1'; this.caption.style.transform = 'translateY(0)'; }, 50); });
document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; }
close() { this.modalImage.style.opacity = '0'; this.modalImage.style.transform = 'scale(0.96)'; this.caption.style.opacity = '0'; this.caption.style.transform = 'translateY(10px)';
setTimeout(() => { this.modal.style.opacity = '0'; this.modal.style.visibility = 'hidden'; this.modal.setAttribute('aria-hidden', 'true'); document.body.style.overflow = ''; document.documentElement.style.overflow = ''; }, 400); }
isOpen() { return this.modal.getAttribute('aria-hidden') === 'false'; }
next() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex + 1) % this.images.length; const nextImg = this.images[this.currentIndex]; this.open(nextImg.dataset.originalSrc || nextImg.src, nextImg.alt || nextImg.title || ''); } }
prev() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length; const prevImg = this.images[this.currentIndex]; this.open(prevImg.dataset.originalSrc || prevImg.src, prevImg.alt || nextImg.title || ''); } } }
// Инициализация document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { window.contentLightbox = new ContentLightbox(); }, 500);
const style = document.createElement('style'); style.textContent = ` /* Базовые стили для изображений в контенте (кроме таблиц) */ .content-image { max-width: 100%; height: auto; display: block; margin: 1.5rem auto; border-radius: 3px; transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); /* Плавная easing-функция */ }
/* Стили для изображений, которые можно увеличивать */ .content-image.is-zoomable { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); cursor: zoom-in !important; }
.content-image.is-zoomable:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); opacity: 0.92; transform: translateY(-3px); }
/* Для изображений, которые нельзя увеличивать */ .content-image.not-zoomable { cursor: default !important; box-shadow: none !important; }
.content-image.not-zoomable:hover { transform: none !important; opacity: 1 !important; box-shadow: none !important; }
/* ВАЖНО: Изображения в таблицах НЕ получают никаких стилей от lightbox */ /* Используем более специфичные селекторы для таблиц */ table img, .table img, .data-table img, .grid img, td img, th img { /* Ваши оригинальные стили для таблиц остаются нетронутыми */ /* Мы НЕ переопределяем их */ all: unset !important; max-width: none !important; margin: 0 !important; display: inline !important; vertical-align: middle !important; }
/* Гарантируем, что наши стили не применяются к таблицам */ table .content-image, .table .content-image, .data-table .content-image, .grid .content-image, td .content-image, th .content-image { all: unset !important; display: inline !important; margin: 0 !important; max-width: none !important; border-radius: 0 !important; box-shadow: none !important; cursor: default !important; transform: none !important; transition: none !important; }
/* Стили для кнопки закрытия */ .content-lightbox__close { display: flex !important; align-items: center !important; justify-content: center !important; padding: 0 !important; line-height: 1 !important; font-family: Arial, sans-serif !important; font-weight: 300 !important; }
.content-lightbox__close:hover { background: rgba(255, 255, 255, 0.25) !important; }
.content-lightbox__close:active { transform: scale(0.95) !important; }
/* Стили для мобильных устройств */ @media (max-width: 768px) { .content-image { margin: 1rem auto; border-radius: 2px; }
.content-image.is-zoomable { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); }
.content-image.is-zoomable:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); }
.content-lightbox__close { top: -40px !important; right: -10px !important; width: 36px !important; height: 36px !important; font-size: 24px !important; // Соответствует размеру в JS } }
@media (hover: none) { .content-image.is-zoomable:hover { transform: none !important; } }
/* Плавные переходы */ * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } `; document.head.appendChild(style); });
window.addEventListener('load', () => { setTimeout(() => { if (window.contentLightbox && window.contentLightbox.checkVisibleImages) { window.contentLightbox.checkVisibleImages(); } }, 1000); });
let searchDebounce; document.addEventListener('input', (e) => { if (e.target.type === 'search' || e.target.type === 'text') { clearTimeout(searchDebounce); searchDebounce = setTimeout(() => { if (window.contentLightbox && window.contentLightbox.processAllImages) { const contentArea = document.querySelector('main, article, .content') || document.body; window.contentLightbox.processAllImages(contentArea); } }, 600); } }); </script> Добавлено (2025-12-29, 14:34) --------------------------------------------- <!-- Lightbox для изображений в статьях --> <script> class ContentLightbox { constructor() { this.modal = null; this.modalImage = null; this.caption = null; this.images = []; this.currentIndex = 0; this.debug = false; this.initialized = false; this.observer = null; this.debounceTimeout = null; this.processing = false; this.scrollCheckTimeout = null; this.init(); }
init() { this.createModal(); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => this.setup(), 300); }); } else { setTimeout(() => this.setup(), 300); } }
createModal() { this.modal = document.createElement('div'); this.modal.className = 'content-lightbox'; this.modal.setAttribute('aria-hidden', 'true'); this.modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; z-index: 9999; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; backdrop-filter: blur(8px); `;
const container = document.createElement('div'); container.className = 'content-lightbox__container'; container.style.cssText = ` position: relative; max-width: 95vw; max-height: 95vh; margin: 20px; `;
const closeBtn = document.createElement('button'); closeBtn.className = 'content-lightbox__close'; closeBtn.setAttribute('aria-label', 'Закрыть');
// Полностью центрируем крестик с помощью абсолютного позиционирования внутри span closeBtn.innerHTML = `<span class="content-lightbox__close-icon">×</span>`;
closeBtn.style.cssText = ` position: absolute; top: -45px; right: 0; width: 36px; height: 36px; border-radius: 50%; background: rgba(255, 255, 255, 0.15); border: none; outline: none; color: white; font-size: 24px; cursor: pointer; z-index: 10; transition: background-color 0.2s ease, transform 0.2s ease; backdrop-filter: blur(5px); display: flex; align-items: center; justify-content: center; `;
// Скрываем кнопку или закрываем лайтбокс? Нет, события для кнопки closeBtn.addEventListener('mouseenter', () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.25)'; });
closeBtn.addEventListener('mouseleave', () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.15)'; closeBtn.style.transform = 'none'; });
closeBtn.addEventListener('mousedown', () => { closeBtn.style.transform = 'scale(0.9)'; });
closeBtn.addEventListener('mouseup', () => { closeBtn.style.transform = 'none'; });
closeBtn.addEventListener('click', (e) => { e.stopPropagation(); this.close(); });
this.modalImage = document.createElement('img'); this.modalImage.className = 'content-lightbox__image'; this.modalImage.style.cssText = ` display: block; max-width: 100%; max-height: 85vh; margin: 0 auto; border-radius: 3px; box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); cursor: default; transform: scale(0.96); opacity: 0; transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.4s ease; `;
this.caption = document.createElement('div'); this.caption.className = 'content-lightbox__caption'; this.caption.style.cssText = ` text-align: center; color: rgba(255, 255, 255, 0.9); font-size: 15px; margin-top: 15px; padding: 8px 16px; background: rgba(255, 255, 255, 0.08); border-radius: 3px; backdrop-filter: blur(10px); opacity: 0; transform: translateY(10px); transition: opacity 0.4s ease, transform 0.4s ease; `;
container.appendChild(closeBtn); container.appendChild(this.modalImage); container.appendChild(this.caption); this.modal.appendChild(container); document.body.appendChild(this.modal);
this.modal.addEventListener('click', (e) => { if (e.target === this.modal) { this.close(); } });
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.isOpen()) { this.close(); } });
document.addEventListener('keydown', (e) => { if (!this.isOpen()) return; if (e.key === 'ArrowLeft') { e.preventDefault(); this.prev(); } else if (e.key === 'ArrowRight') { e.preventDefault(); this.next(); } }); }
setup() { if (this.initialized || this.processing) return; this.processing = true;
const currentPath = window.location.pathname.toLowerCase(); const isDocsPage = currentPath.includes('/hugo/docs/');
if (!isDocsPage) { this.processing = false; return; }
const contentArea = this.findContentArea(); if (!contentArea) { this.processing = false; return; }
this.initialized = true; this.processAllImages(contentArea); this.setupObserver(contentArea); this.setupScrollCheck(); this.processing = false; }
findContentArea() { const contentSelectors = [ 'main', 'article', '.content', '.post-content', '.article-content', '.entry-content', '.prose', '[role="main"]' ];
for (const selector of contentSelectors) { const element = document.querySelector(selector); if (element) { return element; } }
return document.body; }
setupObserver(contentArea) { this.observer = new MutationObserver(() => { clearTimeout(this.debounceTimeout); this.debounceTimeout = setTimeout(() => { if (!this.processing) { this.processing = true; setTimeout(() => { this.processAllImages(contentArea); this.processing = false; }, 150); } }, 300); });
const config = { childList: true, subtree: true };
this.observer.observe(contentArea, config); }
setupScrollCheck() { window.addEventListener('scroll', () => { clearTimeout(this.scrollCheckTimeout); this.scrollCheckTimeout = setTimeout(() => { this.checkVisibleImages(); }, 200); });
setTimeout(() => { this.checkVisibleImages(); }, 1000); }
processAllImages(contentArea) { const images = contentArea.querySelectorAll('img:not(.content-lightbox-processed)');
if (images.length === 0) return;
images.forEach(img => { this.processSingleImage(img); });
setTimeout(() => { this.checkVisibleImages(); }, 300); }
processSingleImage(img) { const isInTable = img.closest('table, .table, .data-table, .grid, td, th');
img.classList.add('content-lightbox-processed');
// ВАЖНОЕ ИЗМЕНЕНИЕ: Не добавляем класс content-image изображениям в таблицах if (!isInTable && !this.isIcon(img)) { img.classList.add('content-image'); } else if (isInTable) { // Для изображений в таблицах НЕ добавляем никаких классов стилей // Только обрабатываем их как иконки if (this.isIcon(img)) { img.classList.add('not-zoomable'); img.style.cursor = 'default'; return; } }
if (this.isIcon(img)) { img.classList.add('not-zoomable'); img.style.cursor = 'default'; return; }
img.setAttribute('data-lightbox', 'pending'); if (img.src && !img.hasAttribute('data-original-src')) { img.setAttribute('data-original-src', img.src); }
if (!img.complete) { img.addEventListener('load', () => { this.evaluateImage(img); }, { once: true }); img.addEventListener('error', () => { img.classList.add('not-zoomable'); }, { once: true }); } else { this.evaluateImage(img); } }
isIcon(img) { const src = (img.src || '').toLowerCase(); const alt = (img.alt || '').toLowerCase(); const classes = (img.className || '').toLowerCase(); const isInTable = img.closest('table, .table, .data-table, .grid, td, th');
// ОСОБАЯ ПРОВЕРКА для таблиц: все изображения в таблицах считаем иконками // если они меньше определенного размера if (isInTable) { const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || 0;
// В таблицах считаем иконками всё меньше 150px if (naturalWidth > 0 && naturalWidth < 150 && naturalHeight < 150) { return true; } if (displayedWidth > 0 && displayedWidth < 150) { return true; }
// Также проверяем по имени файла const iconPatterns = ['icon', 'logo', 'avatar', 'favicon', 'svg', 'ico']; if (iconPatterns.some(pattern => src.includes(pattern))) { return true; } }
const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || 0; const displayedHeight = img.offsetHeight || 0;
if ((naturalWidth > 0 && naturalWidth < 50 && naturalHeight < 50) || (displayedWidth > 0 && displayedWidth < 50 && displayedHeight < 50)) { return true; }
const iconPatterns = [ 'icon', 'logo', 'avatar', 'favicon', 'sprite', 'emoji', 'svg', 'ico', 'png-16', 'png-32', 'png-64', 'icon-', '-icon', 'thumb', 'thumbnail', 'placeholder' ];
return iconPatterns.some(pattern => src.includes(pattern) || alt.includes(pattern) || classes.includes(pattern) ); }
evaluateImage(img) { if (img.classList.contains('not-zoomable')) return;
const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || img.width || 0; const displayedHeight = img.offsetHeight || img.height || 0;
const MIN_WIDTH = 400; const MIN_HEIGHT = 150; const MIN_DISPLAYED_WIDTH = 300; const MIN_DISPLAYED_HEIGHT = 100;
const naturalAspectRatio = naturalWidth / naturalHeight; const isNaturalHorizontal = naturalAspectRatio > 3; const isNaturalVeryHorizontal = naturalAspectRatio > 5; const isNaturalExtremelyHorizontal = naturalAspectRatio > 10;
const displayedAspectRatio = displayedWidth / displayedHeight; const isDisplayedExtremelyHorizontal = displayedAspectRatio > 10;
const compressionRatio = naturalWidth > 0 ? displayedWidth / naturalWidth : 1; const isCompressed = compressionRatio < 0.9;
let isNaturalLargeEnough;
if (isNaturalExtremelyHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.2 && naturalHeight >= 50; } else if (isNaturalVeryHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.5 && naturalHeight >= MIN_HEIGHT * 0.6; } else if (isNaturalHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH && naturalHeight >= MIN_HEIGHT * 0.7; } else { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH && naturalHeight >= MIN_HEIGHT; }
let isDisplayedLargeEnough;
if (isDisplayedExtremelyHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.5 && displayedHeight >= 40; } else if (isNaturalVeryHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.2 && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.8; } else if (isNaturalHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.9; } else { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT; }
const isLargeEnough = isNaturalLargeEnough || (isCompressed && isDisplayedLargeEnough);
const isFullyVisible = this.isFullyVisible(img, displayedWidth, displayedHeight);
const isLikelyScreenshot = (naturalWidth >= 800 && naturalWidth <= 1920) && (naturalHeight >= 600 && naturalHeight <= 1080) && Math.abs(naturalWidth / naturalHeight - 16/9) < 0.2;
const isSuitableForZoom = (isLargeEnough || isLikelyScreenshot) && !isFullyVisible;
if (isSuitableForZoom) { this.makeZoomable(img); } else { img.classList.add('not-zoomable'); img.style.cursor = 'default'; } }
isFullyVisible(img, displayedWidth = null, displayedHeight = null) { const imgWidth = displayedWidth || img.offsetWidth || img.naturalWidth; const container = img.parentElement; if (!container) return false;
const containerWidth = container.clientWidth || container.offsetWidth; return imgWidth < containerWidth * 0.90; }
checkVisibleImages() { const unevaluatedImages = document.querySelectorAll('.content-image[data-lightbox="pending"]');
unevaluatedImages.forEach(img => { const rect = img.getBoundingClientRect(); const isVisible = ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) );
if (isVisible && !img.classList.contains('not-zoomable') && !img.classList.contains('is-zoomable')) { this.evaluateImage(img); } }); }
makeZoomable(img) { const existingIndex = this.images.indexOf(img); if (existingIndex !== -1) { this.images.splice(existingIndex, 1); }
this.images.push(img);
img.classList.add('is-zoomable'); img.classList.remove('not-zoomable'); img.setAttribute('data-lightbox', 'true'); img.style.cursor = 'zoom-in';
img.onclick = null;
img.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const index = this.images.indexOf(img); if (index !== -1) { this.currentIndex = index; this.open(img.dataset.originalSrc || img.src, img.alt || img.title || ''); } }, { once: false });
// ПЛАВНАЯ анимация при наведении img.addEventListener('mouseenter', () => { if (img.classList.contains('is-zoomable')) { img.style.transition = 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; img.style.opacity = '0.92'; img.style.transform = 'translateY(-3px)'; } });
img.addEventListener('mouseleave', () => { if (img.classList.contains('is-zoomable')) { img.style.transition = 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; img.style.opacity = '1'; img.style.transform = 'translateY(0)'; } }); }
open(src, caption) { this.modalImage.src = src; this.modalImage.alt = caption; this.caption.textContent = caption; this.modal.setAttribute('aria-hidden', 'false'); this.modal.style.visibility = 'visible';
requestAnimationFrame(() => { this.modal.style.opacity = '1'; setTimeout(() => { this.modalImage.style.opacity = '1'; this.modalImage.style.transform = 'scale(1)'; this.caption.style.opacity = '1'; this.caption.style.transform = 'translateY(0)'; }, 50); });
document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; }
close() { // Одновременно закрываем изображение, подпись и модал с плавными переходами this.modalImage.style.opacity = '0'; this.modalImage.style.transform = 'scale(0.96)'; this.caption.style.opacity = '0'; this.caption.style.transform = 'translateY(10px)';
setTimeout(() => { this.modal.style.opacity = '0'; this.modal.style.visibility = 'hidden'; this.modal.setAttribute('aria-hidden', 'true'); document.body.style.overflow = ''; document.documentElement.style.overflow = ''; }, 400); }
isOpen() { return this.modal.getAttribute('aria-hidden') === 'false'; }
next() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex + 1) % this.images.length; const nextImg = this.images[this.currentIndex]; this.open(nextImg.dataset.originalSrc || nextImg.src, nextImg.alt || nextImg.title || ''); } }
prev() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length; const prevImg = this.images[this.currentIndex]; this.open(prevImg.dataset.originalSrc || prevImg.src, prevImg.alt || prevImg.title || ''); } } }
// Инициализация document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { window.contentLightbox = new ContentLightbox(); }, 500);
const style = document.createElement('style'); style.textContent = ` /* Базовые стили для изображений в контенте (кроме таблиц) */ .content-image { max-width: 100%; height: auto; display: block; margin: 1.5rem auto; border-radius: 3px; transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); /* Плавная easing-функция */ }
/* Стили для изображений, которые можно увеличивать */ .content-image.is-zoomable { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); cursor: zoom-in !important; }
.content-image.is-zoomable:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); opacity: 0.92; transform: translateY(-3px); }
/* Для изображений, которые нельзя увеличивать */ .content-image.not-zoomable { cursor: default !important; box-shadow: none !important; }
.content-image.not-zoomable:hover { transform: none !important; opacity: 1 !important; box-shadow: none !important; }
/* ВАЖНО: Изображения в таблицах НЕ получают никаких стилей от lightbox */ /* Используем более специфичные селекторы для таблиц */ table img, .table img, .data-table img, .grid img, td img, th img { /* Ваши оригинальные стили для таблиц остаются нетронутыми */ /* Мы НЕ переопределяем их */ all: unset !important; max-width: none !important; margin: 0 !important; display: inline !important; vertical-align: middle !important; }
/* Гарантируем, что наши стили не применяются к таблицам */ table .content-image, .table .content-image, .data-table .content-image, .grid .content-image, td .content-image, th .content-image { all: unset !important; display: inline !important; margin: 0 !important; max-width: none !important; border-radius: 0 !important; box-shadow: none !important; cursor: default !important; transform: none !important; transition: none !important; }
/* Стили для центрирования крестика в кнопке закрытия */ .content-lightbox__close { position: relative !important; display: flex !important; align-items: center !important; justify-content: center !important; padding: 0 !important; margin: 0 !important; width: 36px !important; height: 36px !important; border-radius: 50% !important; border: none !important; outline: none !important; background: rgba(255, 255, 255, 0.15) !important; color: white !important; font-size: 24px !important; cursor: pointer !important; z-index: 10 !important; transition: background-color 0.2s ease, transform 0.2s ease !important; backdrop-filter: blur(5px) !important; }
.content-lightbox__close-icon { position: absolute !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; line-height: 1 !important; font-family: Arial, sans-serif !important; font-weight: 300 !important; }
.content-lightbox__close:hover { background: rgba(255, 255, 255, 0.25) !important; }
.content-lightbox__close:active { transform: scale(0.95) !important; }
/* Стили для мобильных устройств */ @media (max-width: 768px) { .content-image { margin: 1rem auto; border-radius: 2px; }
.content-image.is-zoomable { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); }
.content-image.is-zoomable:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); }
.content-lightbox__close { top: -40px !important; right: -10px !important; width: 36px !important; height: 36px !important; font-size: 24px !important; } }
@media (hover: none) { .content-image.is-zoomable:hover { transform: none !important; } }
/* Плавные переходы */ * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } `; document.head.appendChild(style); });
window.addEventListener('load', () => { setTimeout(() => { if (window.contentLightbox && window.contentLightbox.checkVisibleImages) { window.contentLightbox.checkVisibleImages(); } }, 1000); });
let searchDebounce; document.addEventListener('input', (e) => { if (e.target.type === 'search' || e.target.type === 'text') { clearTimeout(searchDebounce); searchDebounce = setTimeout(() => { if (window.contentLightbox && window.contentLightbox.processAllImages) { const contentArea = document.querySelector('main, article, .content') || document.body; window.contentLightbox.processAllImages(contentArea); } }, 600); } }); </script> Добавлено (2025-12-29, 14:38) --------------------------------------------- <!-- Lightbox для изображений в статьях --> <script> class ContentLightbox { constructor() { this.modal = null; this.modalImage = null; this.caption = null; this.images = []; this.currentIndex = 0; this.debug = false; this.initialized = false; this.observer = null; this.debounceTimeout = null; this.processing = false; this.scrollCheckTimeout = null; this.init(); }
init() { this.createModal(); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => this.setup(), 300); }); } else { setTimeout(() => this.setup(), 300); } }
createModal() { this.modal = document.createElement('div'); this.modal.className = 'content-lightbox'; this.modal.setAttribute('aria-hidden', 'true'); this.modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; z-index: 9999; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; backdrop-filter: blur(8px); `;
const container = document.createElement('div'); container.className = 'content-lightbox__container'; container.style.cssText = ` position: relative; max-width: 95vw; max-height: 95vh; margin: 20px; `;
const closeBtn = document.createElement('button'); closeBtn.className = 'content-lightbox__close'; closeBtn.setAttribute('aria-label', 'Закрыть'); closeBtn.textContent = '×'; // Просто устанавливаем текст крестика
closeBtn.style.cssText = ` position: absolute; top: -45px; right: 0; width: 40px; height: 40px; border-radius: 50%; background: rgba(0, 0, 0, 0.5); border: 2px solid rgba(255, 255, 255, 0.3); outline: none; color: white; font-size: 28px; font-weight: bold; font-family: Arial, sans-serif; cursor: pointer; z-index: 10; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; text-align: center; line-height: 1; `;
closeBtn.addEventListener('mouseenter', () => { closeBtn.style.background = 'rgba(0, 0, 0, 0.7)'; closeBtn.style.borderColor = 'rgba(255, 255, 255, 0.5)'; closeBtn.style.transform = 'scale(1.1)'; });
closeBtn.addEventListener('mouseleave', () => { closeBtn.style.background = 'rgba(0, 0, 0, 0.5)'; closeBtn.style.borderColor = 'rgba(255, 255, 255, 0.3)'; closeBtn.style.transform = 'scale(1)'; });
closeBtn.addEventListener('mousedown', () => { closeBtn.style.transform = 'scale(0.95)'; });
closeBtn.addEventListener('mouseup', () => { closeBtn.style.transform = 'scale(1)'; });
closeBtn.addEventListener('click', (e) => { e.stopPropagation(); this.close(); });
this.modalImage = document.createElement('img'); this.modalImage.className = 'content-lightbox__image'; this.modalImage.style.cssText = ` display: block; max-width: 100%; max-height: 85vh; margin: 0 auto; border-radius: 3px; box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); cursor: default; transform: scale(0.96); opacity: 0; transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.4s ease; `;
this.caption = document.createElement('div'); this.caption.className = 'content-lightbox__caption'; this.caption.style.cssText = ` text-align: center; color: rgba(255, 255, 255, 0.9); font-size: 15px; margin-top: 15px; padding: 8px 16px; background: rgba(0, 0, 255, 0.08); border-radius: 3px; backdrop-filter: blur(10px); opacity: 0; transform: translateY(10px); transition: opacity 0.4s ease, transform 0.4s ease; `;
container.appendChild(closeBtn); container.appendChild(this.modalImage); container.appendChild(this.caption); this.modal.appendChild(container); document.body.appendChild(this.modal);
this.modal.addEventListener('click', (e) => { if (e.target === this.modal) { this.close(); } });
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.isOpen()) { this.close(); } });
document.addEventListener('keydown', (e) => { if (!this.isOpen()) return; if (e.key === 'ArrowLeft') { e.preventDefault(); this.prev(); } else if (e.key === 'ArrowRight') { e.preventDefault(); this.next(); } }); }
setup() { if (this.initialized || this.processing) return; this.processing = true;
const currentPath = window.location.pathname.toLowerCase(); const isDocsPage = currentPath.includes('/hugo/docs/');
if (!isDocsPage) { this.processing = false; return; }
const contentArea = this.findContentArea(); if (!contentArea) { this.processing = false; return; }
this.initialized = true; this.processAllImages(contentArea); this.setupObserver(contentArea); this.setupScrollCheck(); this.processing = false; }
findContentArea() { const contentSelectors = [ 'main', 'article', '.content', '.post-content', '.article-content', '.entry-content', '.prose', '[role="main"]' ];
for (const selector of contentSelectors) { const element = document.querySelector(selector); if (element) { return element; } }
return document.body; }
setupObserver(contentArea) { this.observer = new MutationObserver(() => { clearTimeout(this.debounceTimeout); this.debounceTimeout = setTimeout(() => { if (!this.processing) { this.processing = true; setTimeout(() => { this.processAllImages(contentArea); this.processing = false; }, 150); } }, 300); });
const config = { childList: true, subtree: true };
this.observer.observe(contentArea, config); }
setupScrollCheck() { window.addEventListener('scroll', () => { clearTimeout(this.scrollCheckTimeout); this.scrollCheckTimeout = setTimeout(() => { this.checkVisibleImages(); }, 200); });
setTimeout(() => { this.checkVisibleImages(); }, 1000); }
processAllImages(contentArea) { const images = contentArea.querySelectorAll('img:not(.content-lightbox-processed)');
if (images.length === 0) return;
images.forEach(img => { this.processSingleImage(img); });
setTimeout(() => { this.checkVisibleImages(); }, 300); }
processSingleImage(img) { const isInTable = img.closest('table, .table, .data-table, .grid, td, th');
img.classList.add('content-lightbox-processed');
// ВАЖНОЕ ИЗМЕНЕНИЕ: Не добавляем класс content-image изображениям в таблицах if (!isInTable && !this.isIcon(img)) { img.classList.add('content-image'); } else if (isInTable) { // Для изображений в таблицах НЕ добавляем никаких классов стилей // Только обрабатываем их как иконки if (this.isIcon(img)) { img.classList.add('not-zoomable'); img.style.cursor = 'default'; return; } }
if (this.isIcon(img)) { img.classList.add('not-zoomable'); img.style.cursor = 'default'; return; }
img.setAttribute('data-lightbox', 'pending'); if (img.src && !img.hasAttribute('data-original-src')) { img.setAttribute('data-original-src', img.src); }
if (!img.complete) { img.addEventListener('load', () => { this.evaluateImage(img); }, { once: true }); img.addEventListener('error', () => { img.classList.add('not-zoomable'); }, { once: true }); } else { this.evaluateImage(img); } }
isIcon(img) { const src = (img.src || '').toLowerCase(); const alt = (img.alt || '').toLowerCase(); const classes = (img.className || '').toLowerCase(); const isInTable = img.closest('table, .table, .data-table, .grid, td, th');
// ОСОБАЯ ПРОВЕРКА для таблиц: все изображения в таблицах считаем иконками // если они меньше определенного размера if (isInTable) { const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || 0;
// В таблицах считаем иконками всё меньше 150px if (naturalWidth > 0 && naturalWidth < 150 && naturalHeight < 150) { return true; } if (displayedWidth > 0 && displayedWidth < 150) { return true; }
// Также проверяем по имени файла const iconPatterns = ['icon', 'logo', 'avatar', 'favicon', 'svg', 'ico']; if (iconPatterns.some(pattern => src.includes(pattern))) { return true; } }
const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || 0; const displayedHeight = img.offsetHeight || 0;
if ((naturalWidth > 0 && naturalWidth < 50 && naturalHeight < 50) || (displayedWidth > 0 && displayedWidth < 50 && displayedHeight < 50)) { return true; }
const iconPatterns = [ 'icon', 'logo', 'avatar', 'favicon', 'sprite', 'emoji', 'svg', 'ico', 'png-16', 'png-32', 'png-64', 'icon-', '-icon', 'thumb', 'thumbnail', 'placeholder' ];
return iconPatterns.some(pattern => src.includes(pattern) || alt.includes(pattern) || classes.includes(pattern) ); }
evaluateImage(img) { if (img.classList.contains('not-zoomable')) return;
const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || img.width || 0; const displayedHeight = img.offsetHeight || img.height || 0;
const MIN_WIDTH = 400; const MIN_HEIGHT = 150; const MIN_DISPLAYED_WIDTH = 300; const MIN_DISPLAYED_HEIGHT = 100;
const naturalAspectRatio = naturalWidth / naturalHeight; const isNaturalHorizontal = naturalAspectRatio > 3; const isNaturalVeryHorizontal = naturalAspectRatio > 5; const isNaturalExtremelyHorizontal = naturalAspectRatio > 10;
const displayedAspectRatio = displayedWidth / displayedHeight; const isDisplayedExtremelyHorizontal = displayedAspectRatio > 10;
const compressionRatio = naturalWidth > 0 ? displayedWidth / naturalWidth : 1; const isCompressed = compressionRatio < 0.9;
let isNaturalLargeEnough;
if (isNaturalExtremelyHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.2 && naturalHeight >= 50; } else if (isNaturalVeryHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.5 && naturalHeight >= MIN_HEIGHT * 0.6; } else if (isNaturalHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH && naturalHeight >= MIN_HEIGHT * 0.7; } else { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH && naturalHeight >= MIN_HEIGHT; }
let isDisplayedLargeEnough;
if (isDisplayedExtremelyHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.5 && displayedHeight >= 40; } else if (isNaturalVeryHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.2 && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.8; } else if (isNaturalHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.9; } else { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT; }
const isLargeEnough = isNaturalLargeEnough || (isCompressed && isDisplayedLargeEnough);
const isFullyVisible = this.isFullyVisible(img, displayedWidth, displayedHeight);
const isLikelyScreenshot = (naturalWidth >= 800 && naturalWidth <= 1920) && (naturalHeight >= 600 && naturalHeight <= 1080) && Math.abs(naturalWidth / naturalHeight - 16/9) < 0.2;
const isSuitableForZoom = (isLargeEnough || isLikelyScreenshot) && !isFullyVisible;
if (isSuitableForZoom) { this.makeZoomable(img); } else { img.classList.add('not-zoomable'); img.style.cursor = 'default'; } }
isFullyVisible(img, displayedWidth = null, displayedHeight = null) { const imgWidth = displayedWidth || img.offsetWidth || img.naturalWidth; const container = img.parentElement; if (!container) return false;
const containerWidth = container.clientWidth || container.offsetWidth; return imgWidth < containerWidth * 0.90; }
checkVisibleImages() { const unevaluatedImages = document.querySelectorAll('.content-image[data-lightbox="pending"]');
unevaluatedImages.forEach(img => { const rect = img.getBoundingClientRect(); const isVisible = ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) );
if (isVisible && !img.classList.contains('not-zoomable') && !img.classList.contains('is-zoomable')) { this.evaluateImage(img); } }); }
makeZoomable(img) { const existingIndex = this.images.indexOf(img); if (existingIndex !== -1) { this.images.splice(existingIndex, 1); }
this.images.push(img);
img.classList.add('is-zoomable'); img.classList.remove('not-zoomable'); img.setAttribute('data-lightbox', 'true'); img.style.cursor = 'zoom-in';
img.onclick = null;
img.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const index = this.images.indexOf(img); if (index !== -1) { this.currentIndex = index; this.open(img.dataset.originalSrc || img.src, img.alt || img.title || ''); } }, { once: false });
// ПЛАВНАЯ анимация при наведении img.addEventListener('mouseenter', () => { if (img.classList.contains('is-zoomable')) { img.style.transition = 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; img.style.opacity = '0.92'; img.style.transform = 'translateY(-3px)'; } });
img.addEventListener('mouseleave', () => { if (img.classList.contains('is-zoomable')) { img.style.transition = 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; img.style.opacity = '1'; img.style.transform = 'translateY(0)'; } }); }
open(src, caption) { this.modalImage.src = src; this.modalImage.alt = caption; this.caption.textContent = caption; this.modal.setAttribute('aria-hidden', 'false'); this.modal.style.visibility = 'visible';
requestAnimationFrame(() => { this.modal.style.opacity = '1'; setTimeout(() => { this.modalImage.style.opacity = '1'; this.modalImage.style.transform = 'scale(1)'; this.caption.style.opacity = '1'; this.caption.style.transform = 'translateY(0)'; }, 50); });
document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; }
close() { // Одновременно закрываем изображение, подпись и модал с плавными переходами this.modalImage.style.opacity = '0'; this.modalImage.style.transform = 'scale(0.96)'; this.caption.style.opacity = '0'; this.caption.style.transform = 'translateY(10px)';
setTimeout(() => { this.modal.style.opacity = '0'; this.modal.style.visibility = 'hidden'; this.modal.setAttribute('aria-hidden', 'true'); document.body.style.overflow = ''; document.documentElement.style.overflow = ''; }, 400); }
isOpen() { return this.modal.getAttribute('aria-hidden') === 'false'; }
next() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex + 1) % this.images.length; const nextImg = this.images[this.currentIndex]; this.open(nextImg.dataset.originalSrc || nextImg.src, nextImg.alt || nextImg.title || ''); } }
prev() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length; const prevImg = this.images[this.currentIndex]; this.open(prevImg.dataset.originalSrc || prevImg.src, prevImg.alt || nextImg.title || ''); } } }
// Инициализация document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { window.contentLightbox = new ContentLightbox(); }, 500);
const style = document.createElement('style'); style.textContent = ` /* Базовые стили для изображений в контенте (кроме таблиц) */ .content-image { max-width: 100%; height: auto; display: block; margin: 1.5rem auto; border-radius: 3px; transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); /* Плавная easing-функция */ }
/* Стили для изображений, которые можно увеличивать */ .content-image.is-zoomable { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); cursor: zoom-in !important; }
.content-image.is-zoomable:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); opacity: 0.92; transform: translateY(-3px); }
/* Для изображений, которые нельзя увеличивать */ .content-image.not-zoomable { cursor: default !important; box-shadow: none !important; }
.content-image.not-zoomable:hover { transform: none !important; opacity: 1 !important; box-shadow: none !important; }
/* ВАЖНО: Изображения в таблицах НЕ получают никаких стилей от lightbox */ /* Используем более специфичные селекторы для таблиц */ table img, .table img, .data-table img, .grid img, td img, th img { /* Ваши оригинальные стили для таблиц остаются нетронутыми */ /* Мы НЕ переопределяем их */ all: unset !important; max-width: none !important; margin: 0 !important; display: inline !important; vertical-align: middle !important; }
/* Гарантируем, что наши стили не применяются к таблицам */ table .content-image, .table .content-image, .data-table .content-image, .grid .content-image, td .content-image, th .content-image { all: unset !important; display: inline !important; margin: 0 !important; max-width: none !important; border-radius: 0 !important; box-shadow: none !important; cursor: default !important; transform: none !important; transition: none !important; }
/* Полностью переделанные стили для кнопки закрытия - теперь без span, используем flex для центрирования */ .content-lightbox__close { position: absolute !important; top: -50px !important; right: 0 !important; width: 40px !important; height: 40px !important; border-radius: 50% !important; border: 2px solid rgba(255, 255, 255, 0.3) !important; background: rgba(0, 0, 0, 0.5) !important; color: white !important; font-size: 28px !important; font-weight: bold !important; font-family: Arial, sans-serif !important; cursor: pointer !important; z-index: 10 !important; transition: all 0.2s ease !important; display: flex !important; align-items: center !important; justify-content: center !important; text-align: center !important; line-height: 1 !important; outline: none !important; padding: 0 !important; margin: 0 !important; overflow: hidden !important; }
.content-lightbox__close:hover { background: rgba(0, 0, 0, 0.7) !important; border-color: rgba(255, 255, 255, 0.5) !important; transform: scale(1.1) !important; }
.content-lightbox__close:active { transform: scale(0.95) !important; }
/* Стили для мобильных устройств */ @media (max-width: 768px) { .content-image { margin: 1rem auto; border-radius: 2px; }
.content-image.is-zoomable { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); }
.content-image.is-zoomable:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); }
.content-lightbox__close { top: -40px !important; right: -10px !important; width: 40px !important; height: 40px !important; font-size: 24px !important; } }
@media (hover: none) { .content-image.is-zoomable:hover { transform: none !important; } }
/* Плавные переходы */ * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } `; document.head.appendChild(style); });
window.addEventListener('load', () => { setTimeout(() => { if (window.contentLightbox && window.contentLightbox.checkVisibleImages) { window.contentLightbox.checkVisibleImages(); } }, 1000); });
let searchDebounce; document.addEventListener('input', (e) => { if (e.target.type === 'search' || e.target.type === 'text') { clearTimeout(searchDebounce); searchDebounce = setTimeout(() => { if (window.contentLightbox && window.contentLightbox.processAllImages) { const contentArea = document.querySelector('main, article, .content') || document.body; window.contentLightbox.processAllImages(contentArea); } }, 600); } }); </script> Добавлено (2025-12-29, 14:42) --------------------------------------------- <!-- Lightbox для изображений в статьях --> <script> class ContentLightbox { constructor() { this.modal = null; this.modalImage = null; this.caption = null; this.images = []; this.currentIndex = 0; this.debug = false; this.initialized = false; this.observer = null; this.debounceTimeout = null; this.processing = false; this.scrollCheckTimeout = null; this.closeBtn = null; // Добавлено: ссылка на кнопку закрытия this.init(); }
init() { this.createModal(); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => this.setup(), 300); }); } else { setTimeout(() => this.setup(), 300); } }
createModal() { this.modal = document.createElement('div'); this.modal.className = 'content-lightbox'; this.modal.setAttribute('aria-hidden', 'true'); this.modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; z-index: 9999; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; backdrop-filter: blur(8px); `;
const container = document.createElement('div'); container.className = 'content-lightbox__container'; container.style.cssText = ` position: relative; max-width: 95vw; max-height: 95vh; margin: 20px; `;
const closeBtn = document.createElement('button'); closeBtn.className = 'content-lightbox__close'; closeBtn.setAttribute('aria-label', 'Закрыть'); closeBtn.textContent = '×'; // Просто устанавливаем текст крестика
closeBtn.style.cssText = ` position: absolute; top: -45px; right: 0; width: 40px; height: 40px; border-radius: 50%; background: rgba(0, 0, 0, 0.5); border: 2px solid rgba(255, 255, 255, 0.3); outline: none; color: white; font-size: 28px; font-weight: bold; font-family: Arial, sans-serif; cursor: pointer; z-index: 10; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; text-align: center; line-height: 1; transform-origin: 100% 100%; // Новое: origin в нижнем правом углу для масштабирования без смещения `;
closeBtn.addEventListener('mouseenter', () => { closeBtn.style.background = 'rgba(0, 0, 0, 0.7)'; closeBtn.style.borderColor = 'rgba(255, 255, 255, 0.5)'; closeBtn.style.transform = 'scale(1.1)'; });
closeBtn.addEventListener('mouseleave', () => { closeBtn.style.background = 'rgba(0, 0, 0, 0.5)'; closeBtn.style.borderColor = 'rgba(255, 255, 255, 0.3)'; closeBtn.style.transform = 'scale(1)'; });
closeBtn.addEventListener('mousedown', () => { closeBtn.style.transform = 'scale(0.95)'; });
closeBtn.addEventListener('mouseup', () => { closeBtn.style.transform = 'scale(1)'; });
closeBtn.addEventListener('click', (e) => { e.stopPropagation(); this.close(); });
this.closeBtn = closeBtn; // Сохраняем ссылку на кнопку
this.modalImage = document.createElement('img'); this.modalImage.className = 'content-lightbox__image'; this.modalImage.style.cssText = ` display: block; max-width: 100%; max-height: 85vh; margin: 0 auto; border-radius: 3px; box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); cursor: default; transform: scale(0.96); opacity: 0; transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.4s ease; `;
this.caption = document.createElement('div'); this.caption.className = 'content-lightbox__caption'; this.caption.style.cssText = ` text-align: center; color: rgba(255, 255, 255, 0.9); font-size: 15px; margin-top: 15px; padding: 8px 16px; background: rgba(0, 0, 0, 0.08); border-radius: 3px; backdrop-filter: blur(10px); opacity: 0; transform: translateY(10px); transition: opacity 0.4s ease, transform 0.4s ease; `;
container.appendChild(closeBtn); container.appendChild(this.modalImage); container.appendChild(this.caption); this.modal.appendChild(container); document.body.appendChild(this.modal);
this.modal.addEventListener('click', (e) => { if (e.target === this.modal) { this.close(); } });
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.isOpen()) { this.close(); } });
document.addEventListener('keydown', (e) => { if (!this.isOpen()) return; if (e.key === 'ArrowLeft') { e.preventDefault(); this.prev(); } else if (e.key === 'ArrowRight') { e.preventDefault(); this.next(); } }); }
setup() { if (this.initialized || this.processing) return; this.processing = true;
const currentPath = window.location.pathname.toLowerCase(); const isDocsPage = currentPath.includes('/hugo/docs/');
if (!isDocsPage) { this.processing = false; return; }
const contentArea = this.findContentArea(); if (!contentArea) { this.processing = false; return; }
this.initialized = true; this.processAllImages(contentArea); this.setupObserver(contentArea); this.setupScrollCheck(); this.processing = false; }
findContentArea() { const contentSelectors = [ 'main', 'article', '.content', '.post-content', '.article-content', '.entry-content', '.prose', '[role="main"]' ];
for (const selector of contentSelectors) { const element = document.querySelector(selector); if (element) { return element; } }
return document.body; }
setupObserver(contentArea) { this.observer = new MutationObserver(() => { clearTimeout(this.debounceTimeout); this.debounceTimeout = setTimeout(() => { if (!this.processing) { this.processing = true; setTimeout(() => { this.processAllImages(contentArea); this.processing = false; }, 150); } }, 300); });
const config = { childList: true, subtree: true };
this.observer.observe(contentArea, config); }
setupScrollCheck() { window.addEventListener('scroll', () => { clearTimeout(this.scrollCheckTimeout); this.scrollCheckTimeout = setTimeout(() => { this.checkVisibleImages(); }, 200); });
setTimeout(() => { this.checkVisibleImages(); }, 1000); }
processAllImages(contentArea) { const images = contentArea.querySelectorAll('img:not(.content-lightbox-processed)');
if (images.length === 0) return;
images.forEach(img => { this.processSingleImage(img); });
setTimeout(() => { this.checkVisibleImages(); }, 300); }
processSingleImage(img) { const isInTable = img.closest('table, .table, .data-table, .grid, td, th');
img.classList.add('content-lightbox-processed');
// ВАЖНОЕ ИЗМЕНЕНИЕ: Не добавляем класс content-image изображениям в таблицах if (!isInTable && !this.isIcon(img)) { img.classList.add('content-image'); } else if (isInTable) { // Для изображений в таблицах НЕ добавляем никаких классов стилей // Только обрабатываем их как иконки if (this.isIcon(img)) { img.classList.add('not-zoomable'); img.style.cursor = 'default'; return; } }
if (this.isIcon(img)) { img.classList.add('not-zoomable'); img.style.cursor = 'default'; return; }
img.setAttribute('data-lightbox', 'pending'); if (img.src && !img.hasAttribute('data-original-src')) { img.setAttribute('data-original-src', img.src); }
if (!img.complete) { img.addEventListener('load', () => { this.evaluateImage(img); }, { once: true }); img.addEventListener('error', () => { img.classList.add('not-zoomable'); }, { once: true }); } else { this.evaluateImage(img); } }
isIcon(img) { const src = (img.src || '').toLowerCase(); const alt = (img.alt || '').toLowerCase(); const classes = (img.className || '').toLowerCase(); const isInTable = img.closest('table, .table, .data-table, .grid, td, th);
// ОСОБАЯ ПРОВЕРКА для таблиц: все изображения в таблицах считаем иконками // если они меньше определенного размера if (isInTable) { const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || 0;
// В таблицах считаем иконками всё меньше 150px if (naturalWidth > 0 && naturalWidth < 150 && naturalHeight < 150) { return true; } if (displayedWidth > 0 && displayedWidth < 150) { return true; }
// Также проверяем по имени файла const iconPatterns = ['icon', 'logo', 'avatar', 'favicon', 'svg', 'ico']; if (iconPatterns.some(pattern => src.includes(pattern))) { return true; } }
const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || 0; const displayedHeight = img.offsetHeight || 0;
if ((naturalWidth > 0 && naturalWidth < 50 && naturalHeight < 50) || (displayedWidth > 0 && displayedWidth < 50 && displayedHeight < 50)) { return true; }
const iconPatterns = [ 'icon', 'logo', 'avatar', 'favicon', 'sprite', 'emoji', 'svg', 'ico', 'png-16', 'png-32', 'png-64', 'icon-', '-icon', 'thumb', 'thumbnail', 'placeholder' ];
return iconPatterns.some(pattern => src.includes(pattern) || alt.includes(pattern) || classes.includes(pattern) ); }
evaluateImage(img) { if (img.classList.contains('not-zoomable')) return;
const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || img.width || 0; const displayedHeight = img.offsetHeight || img.height || 0;
const MIN_WIDTH = 400; const MIN_HEIGHT = 150; const MIN_DISPLAYED_WIDTH = 300; const MIN_DISPLAYED_HEIGHT = 100;
const naturalAspectRatio = naturalWidth / naturalHeight; const isNaturalHorizontal = naturalAspectRatio > 3; const isNaturalVeryHorizontal = naturalAspectRatio > 5; const isNaturalExtremelyHorizontal = naturalAspectRatio > 10;
const displayedAspectRatio = displayedWidth / displayedHeight; const isDisplayedExtremelyHorizontal = displayedAspectRatio > 10;
const compressionRatio = naturalWidth > 0 ? displayedWidth / naturalWidth : 1; const isCompressed = compressionRatio < 0.9;
let isNaturalLargeEnough;
if (isNaturalExtremelyHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.2 && naturalHeight >= 50; } else if (isNaturalVeryHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.5 && naturalHeight >= MIN_HEIGHT * 0.6; } else if (isNaturalHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH && naturalHeight >= MIN_HEIGHT * 0.7; } else { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH && naturalHeight >= MIN_HEIGHT; }
let isDisplayedLargeEnough;
if (isDisplayedExtremelyHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.5 && displayedHeight >= 40; } else if (isNaturalVeryHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.2 && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.8; } else if (isNaturalHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.9; } else { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT; }
const isLargeEnough = isNaturalLargeEnough || (isCompressed && isDisplayedLargeEnough);
const isFullyVisible = this.isFullyVisible(img, displayedWidth, displayedHeight);
const isLikelyScreenshot = (naturalWidth >= 800 && naturalWidth <= 1920) && (naturalHeight >= 600 && naturalHeight <= 1080) && Math.abs(naturalWidth / naturalHeight - 16/9) < 0.2;
const isSuitableForZoom = (isLargeEnough || isLikelyScreenshot) && !isFullyVisible;
if (isSuitableForZoom) { this.makeZoomable(img); } else { img.classList.add('not-zoomable'); img.style.cursor = 'default'; } }
isFullyVisible(img, displayedWidth = null, displayedHeight = null) { const imgWidth = displayedWidth || img.offsetWidth || img.naturalWidth; const container = img.parentElement; if (!container) return false;
const containerWidth = container.clientWidth || container.offsetWidth; return imgWidth < containerWidth * 0.90; }
checkVisibleImages() { const unevaluatedImages = document.querySelectorAll('.content-image[data-lightbox="pending"]');
unevaluatedImages.forEach(img => { const rect = img.getBoundingClientRect(); const isVisible = ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) );
if (isVisible && !img.classList.contains('not-zoomable') && !img.classList.contains('is-zoomable')) { this.evaluateImage(img); } }); }
makeZoomable(img) { const existingIndex = this.images.indexOf(img); if (existingIndex !== -1) { this.images.splice(existingIndex, 1); }
this.images.push(img);
img.classList.add('is-zoomable'); img.classList.remove('not-zoomable'); img.setAttribute('data-lightbox', 'true'); img.style.cursor = 'zoom-in';
img.onclick = null;
img.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const index = this.images.indexOf(img); if (index !== -1) { this.currentIndex = index; this.open(img.dataset.originalSrc || img.src, img.alt || img.title || ''); } }, { once: false });
// ПЛАВНАЯ анимация при наведении img.addEventListener('mouseenter', () => { if (img.classList.contains('is-zoomable')) { img.style.transition = 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; img.style.opacity = '0.92'; img.style.transform = 'translateY(-3px)'; } });
img.addEventListener('mouseleave', () => { if (img.classList.contains('is-zoomable')) { img.style.transition = 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; img.style.opacity = '1'; img.style.transform = 'translateY(0)'; } }); }
open(src, caption) { this.modalImage.src = src; this.modalImage.alt = caption; this.caption.textContent = caption; this.modal.setAttribute('aria-hidden', 'false'); this.modal.style.visibility = 'visible';
requestAnimationFrame(() => { this.modal.style.opacity = '1'; setTimeout(() => { this.modalImage.style.opacity = '1'; this.modalImage.style.transform = 'scale(1)'; this.caption.style.opacity = '1'; this.caption.style.transform = 'translateY(0)'; }, 50); });
document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; }
close() { // Одновременно закрываем изображение, подпись, кнопку и модал с плавными переходами this.modalImage.style.opacity = '0'; this.modalImage.style.transform = 'scale(0.96)'; this.caption.style.opacity = '0'; this.caption.style.transform = 'translateY(10px)'; this.closeBtn.style.opacity = '0'; // Новое: кнопка закрытия тоже анимируется
setTimeout(() => { this.modal.style.opacity = '0'; this.modal.style.visibility = 'hidden'; this.modal.setAttribute('aria-hidden', 'true'); this.closeBtn.style.opacity = '1'; // Восстанавливаем opacity кнопки после закрытия document.body.style.overflow = ''; document.documentElement.style.overflow = ''; }, 400); }
isOpen() { return this.modal.getAttribute('aria-hidden') === 'false'; }
next() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex + 1) % this.images.length; const nextImg = this.images[this.currentIndex]; this.open(nextImg.dataset.originalSrc || nextImg.src, nextImg.alt || nextImg.title || ''); } }
prev() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length; const prevImg = this.images[this.currentIndex]; this.open(prevImg.dataset.originalSrc || prevImg.src, prevImg.alt || prevImg.title || ''); } } }
// Инициализация document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { window.contentLightbox = new ContentLightbox(); }, 500);
const style = document.createElement('style'); style.textContent = ` /* Базовые стили для изображений в контенте (кроме таблиц) */ .content-image { max-width: 100%; height: auto; display: block; margin: 1.5rem auto; border-radius: 3px; transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); /* Плавная easing-функция */ }
/* Стили для изображений, которые можно увеличивать */ .content-image.is-zoomable { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); cursor: zoom-in !important; }
.content-image.is-zoomable:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); opacity: 0.92; transform: translateY(-3px); }
/* Для изображений, которые нельзя увеличивать */ .content-image.not-zoomable { cursor: default !important; box-shadow: none !important; }
.content-image.not-zoomable:hover { transform: none !important; opacity: 1 !important; box-shadow: none !important; }
/* ВАЖНО: Изображения в таблицах НЕ получают никаких стилей от lightbox */ /* Используем более специфичные селекторы для таблиц */ table img, .table img, .data-table img, .grid img, td img, th img { /* Ваши оригинальные стили для таблиц остаются нетронутыми */ /* Мы НЕ переопределяем их */ all: unset !important; max-width: none !important; margin: 0 !important; display: inline !important; vertical-align: middle !important; }
/* Гарантируем, что наши стили не применяются к таблицам */ table .content-image, .table .content-image, .data-table .content-image, .grid .content-image, td .content-image, th .content-image { all: unset !important; display: inline !important; margin: 0 !important; max-width: none !important; border-radius: 0 !important; box-shadow: none !important; cursor: default !important; transform: none !important; transition: none !important; }
/* Полностью переделанные стили для кнопки закрытия - теперь без span, используем flex для центрирования */ .content-lightbox__close { position: absolute !important; top: -50px !important; right: 0 !important; width: 40px !important; height: 40px !important; border-radius: 50% !important; border: 2px solid rgba(255, 255, 255, 0.3) !important; background: rgba(0, 0, 0, 0.5) !important; color: white !important; font-size: 28px !important; font-weight: bold !important; font-family: Arial, sans-serif !important; cursor: pointer !important; z-index: 10 !important; transition: all 0.2s ease !important; display: flex !important; align-items: center !important; justify-content: center !important; text-align: center !important; line-height: 1 !important; outline: none !important; padding: 0 !important; margin: 0 !important; overflow: hidden !important; transform-origin: 100% 100% !important; /* Новое: origin для масштабирования без смещения */ }
.content-lightbox__close:hover { background: rgba(0, 0, 0, 0.7) !important; border-color: rgba(255, 255, 255, 0.5) !important; transform: scale(1.1) !important; }
.content-lightbox__close:active { transform: scale(0.95) !important; }
/* Стили для мобильных устройств */ @media (max-width: 768px) { .content-image { margin: 1rem auto; border-radius: 2px; }
.content-image.is-zoomable { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); }
.content-image.is-zoomable:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); }
.content-lightbox__close { top: -40px !important; right: -10px !important; width: 40px !important; height: 40px !important; font-size: 24px !important; transform-origin: 100% 100% !important; /* Для мобильной версии тоже */ } }
@media (hover: none) { .content-image.is-zoomable:hover { transform: none !important; } }
/* Плавные переходы */ * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } `; document.head.appendChild(style); });
window.addEventListener('load', () => { setTimeout(() => { if (window.contentLightbox && window.contentLightbox.checkVisibleImages) { window.contentLightbox.checkVisibleImages(); } }, 1000); });
let searchDebounce; document.addEventListener('input', (e) => { if (e.target.type === 'search' || e.target.type === 'text') { clearTimeout(searchDebounce); searchDebounce = setTimeout(() => { if (window.contentLightbox && window.contentLightbox.processAllImages) { const contentArea = document.querySelector('main, article, .content') || document.body; window.contentLightbox.processAllImages(contentArea); } }, 600); } }); </script>
|
<!-- Lightbox для изображений в статьях --> <script> class ContentLightbox { constructor() { this.modal = null; this.modalImage = null; this.caption = null; this.images = []; this.currentIndex = 0; this.debug = false; this.initialized = false; this.observer = null; this.debounceTimeout = null; this.processing = false; this.scrollCheckTimeout = null; this.closeBtn = null; // Reference to the close button this.init(); }
init() { this.createModal(); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => this.setup(), 300); }); } else { setTimeout(() => this.setup(), 300); } }
createModal() { this.modal = document.createElement('div'); this.modal.className = 'content-lightbox'; this.modal.setAttribute('aria-hidden', 'true'); this.modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; z-index: 9999; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; backdrop-filter: blur(8px); `;
const container = document.createElement('div'); container.className = 'content-lightbox__container'; container.style.cssText = ` position: relative; max-width: 95vw; max-height: 95vh; margin: 20px; `;
const closeBtn = document.createElement('button'); closeBtn.className = 'content-lightbox__close'; closeBtn.setAttribute('aria-label', 'Закрыть'); closeBtn.textContent = '×'; // Simply set the cross text
closeBtn.style.cssText = ` position: absolute; top: -45px; right: 0; width: 40px; height: 40px; border-radius: 50%; background: rgba(0, 0, 0, 0.5); border: 2px solid rgba(255, 255, 255, 0.3); outline: none; color: white; font-size: 28px; font-weight: bold; font-family: Arial, sans-serif; cursor: pointer; z-index: 10; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; text-align: center; line-height: 1; transform-origin: 100% 100%; // Scale from bottom-right to avoid shifting the cross `;
closeBtn.addEventListener('mouseenter', () => { closeBtn.style.background = 'rgba(0, 0, 0, 0.7)'; closeBtn.style.borderColor = 'rgba(255, 255, 255, 0.5)'; closeBtn.style.transform = 'scale(1.1)'; });
closeBtn.addEventListener('mouseleave', () => { closeBtn.style.background = 'rgba(0, 0, 0, 0.5)'; closeBtn.style.borderColor = 'rgba(255, 255, 255, 0.3)'; closeBtn.style.transform = 'scale(1)'; });
closeBtn.addEventListener('mousedown', () => { closeBtn.style.transform = 'scale(0.95)'; });
closeBtn.addEventListener('mouseup', () => { closeBtn.style.transform = 'scale(1)'; });
closeBtn.addEventListener('click', (e) => { e.stopPropagation(); this.close(); });
this.closeBtn = closeBtn; // Keep reference to the button
this.modalImage = document.createElement('img'); this.modalImage.className = 'content-lightbox__image'; this.modalImage.style.cssText = ` display: block; max-width: 100%; max-height: 85vh; margin: 0 auto; border-radius: 3px; box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); cursor: default; transform: scale(0.96); opacity: 0; transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.4s ease; `;
this.caption = document.createElement('div'); this.caption.className = 'content-lightbox__caption'; this.caption.style.cssText = ` text-align: center; color: rgba(255, 255, 255, 0.9); font-size: 15px; margin-top: 15px; padding: 8px 16px; background: rgba(0, 0, 0, 0.08); border-radius: 3px; backdrop-filter: blur(10px); opacity: 0; transform: translateY(10px); transition: opacity 0.4s ease, transform 0.4s ease; `;
container.appendChild(closeBtn); container.appendChild(this.modalImage); container.appendChild(this.caption); this.modal.appendChild(container); document.body.appendChild(this.modal);
this.modal.addEventListener('click', (e) => { if (e.target === this.modal) { this.close(); } });
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.isOpen()) { this.close(); } });
document.addEventListener('keydown', (e) => { if (!this.isOpen()) return; if (e.key === 'ArrowLeft') { e.preventDefault(); this.prev(); } else if (e.key === 'ArrowRight') { e.preventDefault(); this.next(); } }); }
setup() { if (this.initialized || this.processing) return; this.processing = true;
const currentPath = window.location.pathname.toLowerCase(); const isDocsPage = currentPath.includes('/hugo/docs/');
if (!isDocsPage) { this.processing = false; return; }
const contentArea = this.findContentArea(); if (!contentArea) { this.processing = false; return; }
this.initialized = true; this.processAllImages(contentArea); this.setupObserver(contentArea); this.setupScrollCheck(); this.processing = false; }
findContentArea() { const contentSelectors = [ 'main', 'article', '.content', '.post-content', '.article-content', '.entry-content', '.prose', '[role="main"]' ];
for (const selector of contentSelectors) { const element = document.querySelector(selector); if (element) { return element; } }
return document.body; }
setupObserver(contentArea) { this.observer = new MutationObserver(() => { clearTimeout(this.debounceTimeout); this.debounceTimeout = setTimeout(() => { if (!this.processing) { this.processing = true; setTimeout(() => { this.processAllImages(contentArea); this.processing = false; }, 150); } }, 300); });
const config = { childList: true, subtree: true };
this.observer.observe(contentArea, config); }
setupScrollCheck() { window.addEventListener('scroll', () => { clearTimeout(this.scrollCheckTimeout); this.scrollCheckTimeout = setTimeout(() => { this.checkVisibleImages(); }, 200); });
setTimeout(() => { this.checkVisibleImages(); }, 1000); }
processAllImages(contentArea) { const images = contentArea.querySelectorAll('img:not(.content-lightbox-processed)');
if (images.length === 0) return;
images.forEach(img => { this.processSingleImage(img); });
setTimeout(() => { this.checkVisibleImages(); }, 300); }
processSingleImage(img) { const isInTable = img.closest('table, .table, .data-table, .grid, td, th');
img.classList.add('content-lightbox-processed');
// IMPORTANT CHANGE: Do not add content-image class to images in tables if (!isInTable && !this.isIcon(img)) { img.classList.add('content-image'); } else if (isInTable) { // For images in tables, DO NOT add any class styles // Just process them as icons if (this.isIcon(img)) { img.classList.add('not-zoomable'); img.style.cursor = 'default'; return; } }
if (this.isIcon(img)) { img.classList.add('not-zoomable'); img.style.cursor = 'default'; return; }
img.setAttribute('data-lightbox', 'pending'); if (img.src && !img.hasAttribute('data-original-src')) { img.setAttribute('data-original-src', img.src); }
if (!img.complete) { img.addEventListener('load', () => { this.evaluateImage(img); }, { once: true }); img.addEventListener('error', () => { img.classList.add('not-zoomable'); }, { once: true }); } else { this.evaluateImage(img); } }
isIcon(img) { const src = (img.src || '').toLowerCase(); const alt = (img.alt || '').toLowerCase(); const classes = (img.className || '').toLowerCase(); const isInTable = img.closest('table, .table, .data-table, .grid, td, th');
// SPECIAL CHECK for tables: consider all images in tables as icons // if they are smaller than a certain size if (isInTable) { const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || 0;
// In tables, consider everything smaller than 150px as icons if (naturalWidth > 0 && naturalWidth < 150 && naturalHeight < 150) { return true; } if (displayedWidth > 0 && displayedWidth < 150) { return true; }
// Also check by filename const iconPatterns = ['icon', 'logo', 'avatar', 'favicon', 'svg', 'ico']; if (iconPatterns.some(pattern => src.includes(pattern))) { return true; } }
const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || 0; const displayedHeight = img.offsetHeight || 0;
if ((naturalWidth > 0 && naturalWidth < 50 && naturalHeight < 50) || (displayedWidth > 0 && displayedWidth < 50 && displayedHeight < 50)) { return true; }
const iconPatterns = [ 'icon', 'logo', 'avatar', 'favicon', 'sprite', 'emoji', 'svg', 'ico', 'png-16', 'png-32', 'png-64', 'icon-', '-icon', 'thumb', 'thumbnail', 'placeholder' ];
return iconPatterns.some(pattern => src.includes(pattern) || alt.includes(pattern) || classes.includes(pattern) ); }
evaluateImage(img) { if (img.classList.contains('not-zoomable')) return;
const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || img.width || 0; const displayedHeight = img.offsetHeight || img.height || 0;
const MIN_WIDTH = 400; const MIN_HEIGHT = 150; const MIN_DISPLAYED_WIDTH = 300; const MIN_DISPLAYED_HEIGHT = 100;
const naturalAspectRatio = naturalWidth / naturalHeight; const isNaturalHorizontal = naturalAspectRatio > 3; const isNaturalVeryHorizontal = naturalAspectRatio > 5; const isNaturalExtremelyHorizontal = naturalAspectRatio > 10;
const displayedAspectRatio = displayedWidth / displayedHeight; const isDisplayedExtremelyHorizontal = displayedAspectRatio > 10;
const compressionRatio = naturalWidth > 0 ? displayedWidth / naturalWidth : 1; const isCompressed = compressionRatio < 0.9;
let isNaturalLargeEnough;
if (isNaturalExtremelyHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.2 && naturalHeight >= 50; } else if (isNaturalVeryHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.5 && naturalHeight >= MIN_HEIGHT * 0.6; } else if (isNaturalHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH && naturalHeight >= MIN_HEIGHT * 0.7; } else { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH && naturalHeight >= MIN_HEIGHT; }
let isDisplayedLargeEnough;
if (isDisplayedExtremelyHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.5 && displayedHeight >= 40; } else if (isNaturalVeryHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.2 && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.8; } else if (isNaturalHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.9; } else { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT; }
const isLargeEnough = isNaturalLargeEnough || (isCompressed && isDisplayedLargeEnough);
const isFullyVisible = this.isFullyVisible(img, displayedWidth, displayedHeight);
const isLikelyScreenshot = (naturalWidth >= 800 && naturalWidth <= 1920) && (naturalHeight >= 600 && naturalHeight <= 1080) && Math.abs(naturalWidth / naturalHeight - 16/9) < 0.2;
const isSuitableForZoom = (isLargeEnough || isLikelyScreenshot) && !isFullyVisible;
if (isSuitableForZoom) { this.makeZoomable(img); } else { img.classList.add('not-zoomable'); img.style.cursor = 'default'; } }
isFullyVisible(img, displayedWidth = null, displayedHeight = null) { const imgWidth = displayedWidth || img.offsetWidth || img.naturalWidth; const container = img.parentElement; if (!container) return false;
const containerWidth = container.clientWidth || container.offsetWidth; return imgWidth < containerWidth * 0.90; }
checkVisibleImages() { const unevaluatedImages = document.querySelectorAll('.content-image[data-lightbox="pending"]');
unevaluatedImages.forEach(img => { const rect = img.getBoundingClientRect(); const isVisible = ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) );
if (isVisible && !img.classList.contains('not-zoomable') && !img.classList.contains('is-zoomable')) { this.evaluateImage(img); } }); }
makeZoomable(img) { const existingIndex = this.images.indexOf(img); if (existingIndex !== -1) { this.images.splice(existingIndex, 1); }
this.images.push(img);
img.classList.add('is-zoomable'); img.classList.remove('not-zoomable'); img.setAttribute('data-lightbox', 'true'); img.style.cursor = 'zoom-in';
img.onclick = null;
img.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const index = this.images.indexOf(img); if (index !== -1) { this.currentIndex = index; this.open(img.dataset.originalSrc || img.src, img.alt || img.title || ''); } }, { once: false });
// Smooth animation on hover img.addEventListener('mouseenter', () => { if (img.classList.contains('is-zoomable')) { img.style.transition = 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; img.style.opacity = '0.92'; img.style.transform = 'translateY(-3px)'; } });
img.addEventListener('mouseleave', () => { if (img.classList.contains('is-zoomable')) { img.style.transition = 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; img.style.opacity = '1'; img.style.transform = 'translateY(0)'; } }); }
open(src, caption) { this.modalImage.src = src; this.modalImage.alt = caption; this.caption.textContent = caption; this.modal.setAttribute('aria-hidden', 'false'); this.modal.style.visibility = 'visible';
requestAnimationFrame(() => { this.modal.style.opacity = '1'; setTimeout(() => { this.modalImage.style.opacity = '1'; this.modalImage.style.transform = 'scale(1)'; this.caption.style.opacity = '1'; this.caption.style.transform = 'translateY(0)'; }, 50); });
document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; }
close() { // Simultaneously close image, caption, and modal with smooth transitions // No need to manually animate the close button's opacity; let it fade with the modal this.modalImage.style.opacity = '0'; this.modalImage.style.transform = 'scale(0.96)'; this.caption.style.opacity = '0'; this.caption.style.transform = 'translateY(10px)';
setTimeout(() => { this.modal.style.opacity = '0'; this.modal.style.visibility = 'hidden'; this.modal.setAttribute('aria-hidden', 'true'); document.body.style.overflow = ''; document.documentElement.style.overflow = ''; }, 400); }
isOpen() { return this.modal.getAttribute('aria-hidden') === 'false'; }
next() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex + 1) % this.images.length; const nextImg = this.images[this.currentIndex]; this.open(nextImg.dataset.originalSrc || nextImg.src, nextImg.alt || nextImg.title || ''); } }
prev() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length; const prevImg = this.images[this.currentIndex]; this.open(prevImg.dataset.originalSrc || prevImg.src, prevImg.alt || prevImg.title || ''); } } }
// Initialize document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { window.contentLightbox = new ContentLightbox(); }, 500);
const style = document.createElement('style'); style.textContent = ` /* Base styles for images in content (excluding tables) */ .content-image { max-width: 100%; height: auto; display: block; margin: 1.5rem auto; border-radius: 3px; transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); /* Smooth easing function */ }
/* Styles for images that can be enlarged */ .content-image.is-zoomable { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); cursor: zoom-in !important; }
.content-image.is-zoomable:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); opacity: 0.92; transform: translateY(-3px); }
/* For images that cannot be enlarged */ .content-image.not-zoomable { cursor: default !important; box-shadow: none !important; }
.content-image.not-zoomable:hover { transform: none !important; opacity: 1 !important; box-shadow: none !important; }
/* IMPORTANT: Images in tables do NOT get any styles from lightbox */ /* Use more specific selectors for tables */ table img, .table img, .data-table img, .grid img, td img, th img { /* Your original styles for tables remain untouched */ /* We do NOT override them */ all: unset !important; max-width: none !important; margin: 0 !important; display: inline !important; vertical-align: middle !important; }
/* Ensure our styles do not apply to tables */ table .content-image, .table .content-image, .data-table .content-image, .grid .content-image, td .content-image, th .content-image { all: unset !important; display: inline !important; margin: 0 !important; max-width: none !important; border-radius: 0 !important; box-shadow: none !important; cursor: default !important; transform: none !important; transition: none !important; }
/* Completely redesigned styles for the close button - now without span, using flex for centering */ .content-lightbox__close { position: absolute !important; top: -50px !important; /* Adjusted for better positioning */ right: 0 !important; width: 40px !important; height: 40px !important; border-radius: 50% !important; border: 2px solid rgba(255, 255, 255, 0.3) !important; background: rgba(0, 0, 0, 0.5) !important; color: white !important; font-size: 28px !important; font-weight: bold !important; font-family: Arial, sans-serif !important; cursor: pointer !important; z-index: 10 !important; transition: all 0.2s ease !important; display: flex !important; align-items: center !important; justify-content: center !important; text-align: center !important; line-height: 1 !important; outline: none !important; padding: 0 !important; margin: 0 !important; overflow: hidden !important; transform-origin: 100% 100% !important; /* Scale from bottom-right to avoid shifting the cross */ }
.content-lightbox__close:hover { background: rgba(0, 0, 0, 0.7) !important; border-color: rgba(255, 255, 255, 0.5) !important; transform: scale(1.1) !important; }
.content-lightbox__close:active { transform: scale(0.95) !important; }
/* Styles for mobile devices */ @media (max-width: 768px) { .content-image { margin: 1rem auto; border-radius: 2px; }
.content-image.is-zoomable { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); }
.content-image.is-zoomable:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); }
.content-lightbox__close { top: -40px !important; right: -10px !important; width: 40px !important; height: 40px !important; font-size: 24px !important; transform-origin: 100% 100% !important; /* For mobile too */ } }
@media (hover: none) { .content-image.is-zoomable:hover { transform: none !important; } }
/* Smooth transitions */ * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } `; document.head.appendChild(style); });
window.addEventListener('load', () => { setTimeout(() => { if (window.contentLightbox && window.contentLightbox.checkVisibleImages) { window.contentLightbox.checkVisibleImages(); } }, 1000); });
let searchDebounce; document.addEventListener('input', (e) => { if (e.target.type === 'search' || e.target.type === 'text') { clearTimeout(searchDebounce); searchDebounce = setTimeout(() => { if (window.contentLightbox && window.contentLightbox.processAllImages) { const contentArea = document.querySelector('main, article, .content') || document.body; window.contentLightbox.processAllImages(contentArea); } }, 600); } }); </script>Добавлено (2025-12-29, 14:54) --------------------------------------------- <!-- Lightbox для изображений в статьях --> <script> class ContentLightbox { constructor() { this.modal = null; this.modalImage = null; this.caption = null; this.images = []; this.currentIndex = 0; this.debug = false; this.initialized = false; this.observer = null; this.debounceTimeout = null; this.processing = false; this.scrollCheckTimeout = null; this.closeBtn = null; // Reference to the close button this.init(); }
init() { this.createModal(); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => this.setup(), 300); }); } else { setTimeout(() => this.setup(), 300); } }
createModal() { this.modal = document.createElement('div'); this.modal.className = 'content-lightbox'; this.modal.setAttribute('aria-hidden', 'true'); this.modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; z-index: 9999; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; backdrop-filter: blur(8px); `;
const container = document.createElement('div'); container.className = 'content-lightbox__container'; container.style.cssText = ` position: relative; max-width: 95vw; max-height: 95vh; margin: 20px; `;
const closeBtn = document.createElement('button'); closeBtn.className = 'content-lightbox__close'; closeBtn.setAttribute('aria-label', 'Закрыть'); closeBtn.textContent = '×'; // Simply set the cross text
closeBtn.style.cssText = ` position: absolute; top: -45px; right: 0; width: 40px; height: 40px; border-radius: 50%; background: rgba(0, 0, 0, 0.5); border: 2px solid rgba(255, 255, 255, 0.3); outline: none; color: white; font-size: 28px; font-weight: bold; font-family: Arial, sans-serif; cursor: pointer; z-index: 10; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; text-align: center; line-height: 1; transform-origin: 100% 100%; // Scale from bottom-right to avoid shifting the cross `;
closeBtn.addEventListener('mouseenter', () => { closeBtn.style.background = 'rgba(0, 0, 0, 0.7)'; closeBtn.style.borderColor = 'rgba(255, 255, 255, 0.5)'; closeBtn.style.transform = 'scale(1.1)'; });
closeBtn.addEventListener('mouseleave', () => { closeBtn.style.background = 'rgba(0, 0, 0, 0.5)'; closeBtn.style.borderColor = 'rgba(255, 255, 255, 0.3)'; closeBtn.style.transform = 'scale(1)'; });
closeBtn.addEventListener('mousedown', () => { closeBtn.style.transform = 'scale(0.95)'; });
closeBtn.addEventListener('mouseup', () => { closeBtn.style.transform = 'scale(1)'; });
closeBtn.addEventListener('click', (e) => { e.stopPropagation(); this.close(); });
this.closeBtn = closeBtn; // Keep reference to the button
this.modalImage = document.createElement('img'); this.modalImage.className = 'content-lightbox__image'; this.modalImage.style.cssText = ` display: block; max-width: 100%; max-height: 85vh; margin: 0 auto; border-radius: 3px; box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); cursor: default; transform: scale(0.96); opacity: 0; transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.3s ease; `;
this.caption = document.createElement('div'); this.caption.className = 'content-lightbox__caption'; this.caption.style.cssText = ` text-align: center; color: rgba(255, 255, 255, 0.9); font-size: 15px; margin-top: 15px; padding: 8px 16px; background: rgba(0, 0, 0, 0.08); border-radius: 3px; backdrop-filter: blur(10px); opacity: 0; transform: translateY(10px); transition: opacity 0.3s ease, transform 0.3s ease; `;
container.appendChild(closeBtn); container.appendChild(this.modalImage); container.appendChild(this.caption); this.modal.appendChild(container); document.body.appendChild(this.modal);
this.modal.addEventListener('click', (e) => { if (e.target === this.modal) { this.close(); } });
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.isOpen()) { this.close(); } });
document.addEventListener('keydown', (e) => { if (!this.isOpen()) return; if (e.key === 'ArrowLeft') { e.preventDefault(); this.prev(); } else if (e.key === 'ArrowRight') { e.preventDefault(); this.next(); } }); }
setup() { if (this.initialized || this.processing) return; this.processing = true;
const currentPath = window.location.pathname.toLowerCase(); const isDocsPage = currentPath.includes('/hugo/docs/');
if (!isDocsPage) { this.processing = false; return; }
const contentArea = this.findContentArea(); if (!contentArea) { this.processing = false; return; }
this.initialized = true; this.processAllImages(contentArea); this.setupObserver(contentArea); this.setupScrollCheck(); this.processing = false; }
findContentArea() { const contentSelectors = [ 'main', 'article', '.content', '.post-content', '.article-content', '.entry-content', '.prose', '[role="main"]' ];
for (const selector of contentSelectors) { const element = document.querySelector(selector); if (element) { return element; } }
return document.body; }
setupObserver(contentArea) { this.observer = new MutationObserver(() => { clearTimeout(this.debounceTimeout); this.debounceTimeout = setTimeout(() => { if (!this.processing) { this.processing = true; setTimeout(() => { this.processAllImages(contentArea); this.processing = false; }, 150); } }, 300); });
const config = { childList: true, subtree: true };
this.observer.observe(contentArea, config); }
setupScrollCheck() { window.addEventListener('scroll', () => { clearTimeout(this.scrollCheckTimeout); this.scrollCheckTimeout = setTimeout(() => { this.checkVisibleImages(); }, 200); });
setTimeout(() => { this.checkVisibleImages(); }, 1000); }
processAllImages(contentArea) { const images = contentArea.querySelectorAll('img:not(.content-lightbox-processed)');
if (images.length === 0) return;
images.forEach(img => { this.processSingleImage(img); });
setTimeout(() => { this.checkVisibleImages(); }, 300); }
processSingleImage(img) { const isInTable = img.closest('table, .table, .data-table, .grid, td, th');
img.classList.add('content-lightbox-processed');
// IMPORTANT CHANGE: Do not add content-image class to images in tables if (!isInTable && !this.isIcon(img)) { img.classList.add('content-image'); } else if (isInTable) { // For images in tables, DO NOT add any class styles // Just process them as icons if (this.isIcon(img)) { img.classList.add('not-zoomable'); img.style.cursor = 'default'; return; } }
if (this.isIcon(img)) { img.classList.add('not-zoomable'); img.style.cursor = 'default'; return; }
img.setAttribute('data-lightbox', 'pending'); if (img.src && !img.hasAttribute('data-original-src')) { img.setAttribute('data-original-src', img.src); }
if (!img.complete) { img.addEventListener('load', () => { this.evaluateImage(img); }, { once: true }); img.addEventListener('error', () => { img.classList.add('not-zoomable'); }, { once: true }); } else { this.evaluateImage(img); } }
isIcon(img) { const src = (img.src || '').toLowerCase(); const alt = (img.alt || '').toLowerCase(); const classes = (img.className || '').toLowerCase(); const isInTable = img.closest('table, .table, .data-table, .grid, td, th');
// SPECIAL CHECK for tables: consider all images in tables as icons // if they are smaller than a certain size if (isInTable) { const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || 0;
// In tables, consider everything smaller than 150px as icons if (naturalWidth > 0 && naturalWidth < 150 && naturalHeight < 150) { return true; } if (displayedWidth > 0 && displayedWidth < 150) { return true; }
// Also check by filename const iconPatterns = ['icon', 'logo', 'avatar', 'favicon', 'svg', 'ico']; if (iconPatterns.some(pattern => src.includes(pattern))) { return true; } }
const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || 0; const displayedHeight = img.offsetHeight || 0;
if ((naturalWidth > 0 && naturalWidth < 50 && naturalHeight < 50) || (displayedWidth > 0 && displayedWidth < 50 && displayedHeight < 50)) { return true; }
const iconPatterns = [ 'icon', 'logo', 'avatar', 'favicon', 'sprite', 'emoji', 'svg', 'ico', 'png-16', 'png-32', 'png-64', 'icon-', '-icon', 'thumb', 'thumbnail', 'placeholder' ];
return iconPatterns.some(pattern => src.includes(pattern) || alt.includes(pattern) || classes.includes(pattern) ); }
evaluateImage(img) { if (img.classList.contains('not-zoomable')) return;
const naturalWidth = img.naturalWidth || 0; const naturalHeight = img.naturalHeight || 0; const displayedWidth = img.offsetWidth || img.width || 0; const displayedHeight = img.offsetHeight || img.height || 0;
const MIN_WIDTH = 400; const MIN_HEIGHT = 150; const MIN_DISPLAYED_WIDTH = 300; const MIN_DISPLAYED_HEIGHT = 100;
const naturalAspectRatio = naturalWidth / naturalHeight; const isNaturalHorizontal = naturalAspectRatio > 3; const isNaturalVeryHorizontal = naturalAspectRatio > 5; const isNaturalExtremelyHorizontal = naturalAspectRatio > 10;
const displayedAspectRatio = displayedWidth / displayedHeight; const isDisplayedExtremelyHorizontal = displayedAspectRatio > 10;
const compressionRatio = naturalWidth > 0 ? displayedWidth / naturalWidth : 1; const isCompressed = compressionRatio < 0.9;
let isNaturalLargeEnough;
if (isNaturalExtremelyHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.2 && naturalHeight >= 50; } else if (isNaturalVeryHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.5 && naturalHeight >= MIN_HEIGHT * 0.6; } else if (isNaturalHorizontal) { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH && naturalHeight >= MIN_HEIGHT * 0.7; } else { isNaturalLargeEnough = naturalWidth >= MIN_WIDTH && naturalHeight >= MIN_HEIGHT; }
let isDisplayedLargeEnough;
if (isDisplayedExtremelyHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.5 && displayedHeight >= 40; } else if (isNaturalVeryHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.2 && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.8; } else if (isNaturalHorizontal) { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.9; } else { isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT; }
const isLargeEnough = isNaturalLargeEnough || (isCompressed && isDisplayedLargeEnough);
const isFullyVisible = this.isFullyVisible(img, displayedWidth, displayedHeight);
const isLikelyScreenshot = (naturalWidth >= 800 && naturalWidth <= 1920) && (naturalHeight >= 600 && naturalHeight <= 1080) && Math.abs(naturalWidth / naturalHeight - 16/9) < 0.2;
const isSuitableForZoom = (isLargeEnough || isLikelyScreenshot) && !isFullyVisible;
if (isSuitableForZoom) { this.makeZoomable(img); } else { img.classList.add('not-zoomable'); img.style.cursor = 'default'; } }
isFullyVisible(img, displayedWidth = null, displayedHeight = null) { const imgWidth = displayedWidth || img.offsetWidth || img.naturalWidth; const container = img.parentElement; if (!container) return false;
const containerWidth = container.clientWidth || container.offsetWidth; return imgWidth < containerWidth * 0.90; }
checkVisibleImages() { const unevaluatedImages = document.querySelectorAll('.content-image[data-lightbox="pending"]');
unevaluatedImages.forEach(img => { const rect = img.getBoundingClientRect(); const isVisible = ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) );
if (isVisible && !img.classList.contains('not-zoomable') && !img.classList.contains('is-zoomable')) { this.evaluateImage(img); } }); }
makeZoomable(img) { const existingIndex = this.images.indexOf(img); if (existingIndex !== -1) { this.images.splice(existingIndex, 1); }
this.images.push(img);
img.classList.add('is-zoomable'); img.classList.remove('not-zoomable'); img.setAttribute('data-lightbox', 'true'); img.style.cursor = 'zoom-in';
img.onclick = null;
img.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const index = this.images.indexOf(img); if (index !== -1) { this.currentIndex = index; this.open(img.dataset.originalSrc || img.src, img.alt || img.title || ''); } }, { once: false });
// Smooth animation on hover img.addEventListener('mouseenter', () => { if (img.classList.contains('is-zoomable')) { img.style.transition = 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; img.style.opacity = '0.92'; img.style.transform = 'translateY(-3px)'; } });
img.addEventListener('mouseleave', () => { if (img.classList.contains('is-zoomable')) { img.style.transition = 'all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; img.style.opacity = '1'; img.style.transform = 'translateY(0)'; } }); }
open(src, caption) { this.modalImage.src = src; this.modalImage.alt = caption; this.caption.textContent = caption; this.modal.setAttribute('aria-hidden', 'false'); this.modal.style.visibility = 'visible';
requestAnimationFrame(() => { this.modal.style.opacity = '1'; setTimeout(() => { this.modalImage.style.opacity = '1'; this.modalImage.style.transform = 'scale(1)'; this.caption.style.opacity = '1'; this.caption.style.transform = 'translateY(0)'; }, 50); });
document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; }
close() { // Fade out image, caption, and modal simultaneously this.modalImage.style.opacity = '0'; this.modalImage.style.transform = 'scale(0.96)'; this.caption.style.opacity = '0'; this.caption.style.transform = 'translateY(10px)'; this.modal.style.opacity = '0'; setTimeout(() => { this.modal.style.visibility = 'hidden'; this.modal.setAttribute('aria-hidden', 'true'); document.body.style.overflow = ''; document.documentElement.style.overflow = ''; }, 300); }
isOpen() { return this.modal.getAttribute('aria-hidden') === 'false'; }
next() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex + 1) % this.images.length; const nextImg = this.images[this.currentIndex]; this.open(nextImg.dataset.originalSrc || nextImg.src, nextImg.alt || nextImg.title || ''); } }
prev() { if (this.images.length > 1) { this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length; const prevImg = this.images[this.currentIndex]; this.open(prevImg.dataset.originalSrc || prevImg.src, prevImg.alt || prevImg.title || ''); } } }
// Initialize document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { window.contentLightbox = new ContentLightbox(); }, 500);
const style = document.createElement('style'); style.textContent = ` /* Base styles for images in content (excluding tables) */ .content-image { max-width: 100%; height: auto; display: block; margin: 1.5rem auto; border-radius: 3px; transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); /* Smooth easing function */ }
/* Styles for images that can be enlarged */ .content-image.is-zoomable { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); cursor: zoom-in !important; }
.content-image.is-zoomable:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); opacity: 0.92; transform: translateY(-3px); }
/* For images that cannot be enlarged */ .content-image.not-zoomable { cursor: default !important; box-shadow: none !important; }
.content-image.not-zoomable:hover { transform: none !important; opacity: 1 !important; box-shadow: none !important; }
/* IMPORTANT: Images in tables do NOT get any styles from lightbox */ /* Use more specific selectors for tables */ table img, .table img, .data-table img, .grid img, td img, th img { /* Your original styles for tables remain untouched */ /* We do NOT override them */ all: unset !important; max-width: none !important; margin: 0 !important; display: inline !important; vertical-align: middle !important; }
/* Ensure our styles do not apply to tables */ table .content-image, .table .content-image, .data-table .content-image, .grid .content-image, td .content-image, th .content-image { all: unset !important; display: inline !important; margin: 0 !important; max-width: none !important; border-radius: 0 !important; box-shadow: none !important; cursor: default !important; transform: none !important; transition: none !important; }
/* Completely redesigned styles for the close button - now without span, using flex for centering */ .content-lightbox__close { position: absolute !important; top: -50px !important; /* Adjusted for better positioning */ right: 0 !important; width: 40px !important; height: 40px !important; border-radius: 50% !important; border: 2px solid rgba(255, 255, 255, 0.3) !important; background: rgba(0, 0, 0, 0.5) !important; color: white !important; font-size: 28px !important; font-weight: bold !important; font-family: Arial, sans-serif !important; cursor: pointer !important; z-index: 10 !important; transition: all 0.2s ease !important; display: flex !important; align-items: center !important; justify-content: center !important; text-align: center !important; line-height: 1 !important; outline: none !important; padding: 0 !important; margin: 0 !important; overflow: hidden !important; transform-origin: 100% 100% !important; /* Scale from bottom-right to avoid shifting the cross */ }
.content-lightbox__close:hover { background: rgba(0, 0, 0, 0.7) !important; border-color: rgba(255, 255, 255, 0.5) !important; transform: scale(1.1) !important; }
.content-lightbox__close:active { transform: scale(0.95) !important; }
/* Styles for mobile devices */ @media (max-width: 768px) { .content-image { margin: 1rem auto; border-radius: 2px; }
.content-image.is-zoomable { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); }
.content-image.is-zoomable:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); }
.content-lightbox__close { top: -40px !important; right: -10px !important; width: 40px !important; height: 40px !important; font-size: 24px !important; transform-origin: 100% 100% !important; /* For mobile too */ } }
@media (hover: none) { .content-image.is-zoomable:hover { transform: none !important; } }
/* Smooth transitions */ * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } `; document.head.appendChild(style); });
window.addEventListener('load', () => { setTimeout(() => { if (window.contentLightbox && window.contentLightbox.checkVisibleImages) { window.contentLightbox.checkVisibleImages(); } }, 1000); });
let searchDebounce; document.addEventListener('input', (e) => { if (e.target.type === 'search' || e.target.type === 'text') { clearTimeout(searchDebounce); searchDebounce = setTimeout(() => { if (window.contentLightbox && window.contentLightbox.processAllImages) { const contentArea = document.querySelector('main, article, .content') || document.body; window.contentLightbox.processAllImages(contentArea); } }, 600); } }); </script> Добавлено (2025-12-29, 16:31) --------------------------------------------- const style = document.createElement('style'); style.textContent = ` /* Base styles for images in content (excluding tables) */ .content-image { max-width: 100%; height: auto; display: block; margin: 1.5rem auto; border-radius: 3px; transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); /* Improve image sharpness when scaled down */ image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges; /* Optional: for smoother antialiasing on high-DPI screens */ -moz-osx-font-smoothing: grayscale; /* For Firefox */ -webkit-font-smoothing: antialiased; /* For WebKit browsers */ }
/* Styles for images that can be enlarged */ .content-image.is-zoomable { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); cursor: zoom-in !important; }
.content-image.is-zoomable:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); opacity: 0.92; transform: translateY(-3px); }
/* For images that cannot be enlarged */ .content-image.not-zoomable { cursor: default !important; box-shadow: none !important; }
.content-image.not-zoomable:hover { transform: none !important; opacity: 1 !important; box-shadow: none !important; }
/* IMPORTANT: Images in tables do NOT get any styles from lightbox */ table img, .table img, .data-table img, .grid img, td img, th img { all: unset !important; max-width: none !important; margin: 0 !important; display: inline !important; vertical-align: middle !important; }
/* Ensure our styles do not apply to tables */ table .content-image, .table .content-image, .data-table .content-image, .grid .content-image, td .content-image, .th .content-image { all: unset !important; display: inline !important; margin: 0 !important; max-width: none !important; border-radius: 0 !important; box-shadow: none !important; cursor: default !important; transform: none !important; transition: none !important; }
/* Completely redesigned styles for the close button */ .content-lightbox__close { position: absolute !important; top: -50px !important; right: 0 !important; width: 40px !important; height: 40px !important; border-radius: 50% !important; border: 2px solid rgba(255, 255, 255, 0.3) !important; background: rgba(0, 0, 0, 0.5) !important; color: white !important; font-size: 28px !important; font-weight: bold !important; font-family: Arial, sans-serif !important; cursor: pointer !important; z-index: 10 !important; transition: all 0.2s ease !important; display: flex !important; align-items: center !important; justify-content: center !important; text-align: center !important; line-height: 1 !important; outline: none !important; padding: 0 !important; margin: 0 !important; overflow: hidden !important; transform-origin: 100% 100% !important; }
.content-lightbox__close:hover { background: rgba(0, 0, 0, 0.7) !important; border-color: rgba(255, 255, 255, 0.5) !important; transform: scale(1.1) !important; }
.content-lightbox__close:active { transform: scale(0.95) !important; }
/* Styles for mobile devices */ @media (max-width: 768px) { .content-image { margin: 1rem auto; border-radius: 2px; }
.content-image.is-zoomable { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); }
.content-image.is-zoomable:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); }
.content-lightbox__close { top: -40px !important; right: -10px !important; width: 40px !important; height: 40px !important; font-size: 24px !important; } }
@media (hover: none) { .content-image.is-zoomable:hover { transform: none !important; } }
/* Smooth transitions */ * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } `; document.head.appendChild(style);
|
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>С Новым 2026 Годом!</title> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background: linear-gradient(to bottom, #001122, #003344); color: #fff; min-height: 100vh; overflow-x: hidden; position: relative; } .snow-container { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; } .container { max-width: 1200px; margin: 0 auto; padding: 20px; position: relative; z-index: 2; } header { text-align: center; padding: 30px 0; position: relative; } h1 { font-size: 4rem; text-shadow: 0 0 15px #ff0066, 0 0 30px #ff0066; margin-bottom: 10px; background: linear-gradient(to right, #ff3366, #ff9933, #ffff33, #33ff66, #3366ff); -webkit-background-clip: text; background-clip: text; color: transparent; animation: glow 3s ease-in-out infinite alternate; } .subtitle { font-size: 1.8rem; margin-bottom: 20px; color: #ffcc00; text-shadow: 0 0 10px #ff9900; } .content { display: flex; flex-wrap: wrap; justify-content: space-around; align-items: flex-start; gap: 40px; margin-top: 30px; } .greeting-box { flex: 1; min-width: 300px; background: rgba(0, 40, 60, 0.7); border-radius: 20px; padding: 30px; box-shadow: 0 0 30px rgba(0, 255, 255, 0.3); backdrop-filter: blur(5px); border: 1px solid rgba(0, 255, 255, 0.2); } .tree-container { flex: 1; min-width: 300px; display: flex; justify-content: center; align-items: center; position: relative; } .tree { position: relative; width: 250px; height: 350px; } .tree-part { position: absolute; border-left: 75px solid transparent; border-right: 75px solid transparent; border-bottom: 100px solid #0a5c0a; } .tree-part-1 { bottom: 0; width: 0; height: 0; border-left: 125px solid transparent; border-right: 125px solid transparent; border-bottom: 150px solid #0a7a0a; } .tree-part-2 { bottom: 100px; width: 0; height: 0; border-left: 100px solid transparent; border-right: 100px solid transparent; border-bottom: 130px solid #0a6a0a; } .tree-part-3 { bottom: 200px; width: 0; height: 0; border-left: 75px solid transparent; border-right: 75px solid transparent; border-bottom: 100px solid #0a5c0a; } .trunk { position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 40px; height: 50px; background: #8B4513; } .light { position: absolute; width: 12px; height: 12px; border-radius: 50%; animation: twinkle 1.5s infinite alternate; cursor: pointer; } .gifts-container { display: flex; justify-content: center; gap: 20px; margin-top: 40px; flex-wrap: wrap; } .gift { width: 100px; height: 100px; position: relative; cursor: pointer; transition: transform 0.3s; } .gift:hover { transform: translateY(-10px); } .gift-box { width: 100%; height: 70px; position: absolute; bottom: 0; } .gift-lid { width: 110%; height: 30px; position: absolute; top: 0; left: -5%; } .gift-ribbon { position: absolute; background: #ff0000; width: 20px; height: 100%; left: 50%; transform: translateX(-50%); } .gift-ribbon-horizontal { position: absolute; background: #ff0000; width: 100%; height: 20px; top: 50%; transform: translateY(-50%); } .message { font-size: 1.2rem; line-height: 1.6; margin-bottom: 20px; text-align: justify; } .signature { text-align: right; font-style: italic; font-size: 1.5rem; color: #ffcc00; margin-top: 20px; } .counter { text-align: center; font-size: 1.8rem; margin: 30px 0; color: #ffcc00; text-shadow: 0 0 10px #ff9900; } .fireworks-btn { display: block; margin: 30px auto; padding: 15px 40px; background: linear-gradient(45deg, #ff3366, #ff9933); border: none; border-radius: 50px; color: white; font-size: 1.2rem; font-weight: bold; cursor: pointer; transition: all 0.3s; box-shadow: 0 0 20px rgba(255, 102, 0, 0.5); } .fireworks-btn:hover { transform: scale(1.05); box-shadow: 0 0 30px rgba(255, 102, 0, 0.8); } footer { text-align: center; padding: 30px 0; margin-top: 50px; border-top: 1px solid rgba(255, 255, 255, 0.1); color: #aaddff; } .firework { position: fixed; width: 5px; height: 5px; border-radius: 50%; pointer-events: none; z-index: 1000; } @keyframes glow { 0% { text-shadow: 0 0 15px #ff0066, 0 0 30px #ff0066; } 100% { text-shadow: 0 0 20px #00ccff, 0 0 40px #00ccff; } } @keyframes twinkle { 0% { opacity: 0.3; } 100% { opacity: 1; } } @keyframes fall { 0% { transform: translateY(-100px) rotate(0deg); } 100% { transform: translateY(100vh) rotate(360deg); } } @media (max-width: 768px) { h1 { font-size: 2.8rem; } .content { flex-direction: column; align-items: center; } .greeting-box, .tree-container { width: 100%; } } </style> </head> <body> <div class="snow-container" id="snow"></div> <div class="container"> <header> <h1>С Наступающим Новым 2026 Годом!</h1> <div class="subtitle">Пусть мечты сбываются, а счастье наполняет каждый день!</div> <div class="counter" id="counter">До 2026 года осталось: <span id="countdown"></span></div> </header> <div class="content"> <div class="greeting-box"> <h2><i class="fas fa-star"></i> Дорогие друзья! <i class="fas fa-star"></i></h2> <div class="message"> <p>Вот и подходит к концу ещё один год, полный событий, эмоций и впечатлений. Пусть уходящий год заберёт с собой все печали и разочарования, а наступающий 2026 принесёт только радость, здоровье, удачу и благополучие!</p> <p>Желаем, чтобы каждый день нового года был наполнен светом, теплом и любовью близких. Пусть сбудутся самые заветные мечты, а планы реализуются легко и гармонично.</p> <p>Пусть Новый год станет для вас временем новых возможностей, интересных знакомств и ярких событий. Желаем профессиональных успехов, финансового благополучия и душевного спокойствия.</p> </div> <div class="signature">С наилучшими пожеланиями!</div> </div> <div class="tree-container"> <div class="tree" id="tree"> <div class="tree-part tree-part-1"></div> <div class="tree-part tree-part-2"></div> <div class="tree-part tree-part-3"></div> <div class="trunk"></div> <!-- Огоньки будут добавлены через JS --> </div> </div> </div> <button class="fireworks-btn" id="fireworksBtn"> <i class="fas fa-fire"></i> Запустить фейерверк! <i class="fas fa-fire"></i> </button> <div class="gifts-container" id="giftsContainer"> <!-- Подарки будут добавлены через JS --> </div> <footer> <p>© 2025 - 2026 С Новым Годом!</p> <p>Создано с ❤️ и праздничным настроением</p> </footer> </div>
<script> $(document).ready(function() { // Создаем снег function createSnow() { const snowContainer = $('#snow'); for (let i = 0; i < 150; i++) { const snowflake = $('<div class="snowflake"></div>'); snowflake.css({ position: 'absolute', width: Math.random() * 10 + 5 + 'px', height: Math.random() * 10 + 5 + 'px', background: '#fff', borderRadius: '50%', top: '-20px', left: Math.random() * 100 + 'vw', opacity: Math.random() * 0.5 + 0.3, animation: `fall ${Math.random() * 5 + 5}s linear infinite`, animationDelay: Math.random() * 5 + 's' }); snowContainer.append(snowflake); } } // Добавляем огоньки на ёлку function addLightsToTree() { const tree = $('#tree'); const colors = ['#ff0000', '#00ff00', '#ffff00', '#ff00ff', '#00ffff', '#ff9900']; // Координаты для огоньков на ёлке const lightsPositions = [ // Нижний ярус {left: '50%', bottom: '20px'}, {left: '30%', bottom: '40px'}, {left: '70%', bottom: '40px'}, {left: '40%', bottom: '60px'}, {left: '60%', bottom: '60px'}, {left: '20%', bottom: '80px'}, {left: '80%', bottom: '80px'}, // Средний ярус {left: '50%', bottom: '120px'}, {left: '35%', bottom: '140px'}, {left: '65%', bottom: '140px'}, {left: '25%', bottom: '160px'}, {left: '75%', bottom: '160px'}, // Верхний ярус {left: '50%', bottom: '200px'}, {left: '40%', bottom: '220px'}, {left: '60%', bottom: '220px'}, {left: '30%', bottom: '240px'}, {left: '70%', bottom: '240px'}, // Верхушка {left: '50%', bottom: '280px'} ]; lightsPositions.forEach((pos, index) => { const light = $('<div class="light"></div>'); const color = colors[Math.floor(Math.random() * colors.length)]; light.css({ left: pos.left, bottom: pos.bottom, backgroundColor: color, boxShadow: `0 0 10px ${color}, 0 0 20px ${color}`, animationDelay: Math.random() * 1.5 + 's' }); // При клике на огонёк меняем его цвет light.click(function() { const newColor = colors[Math.floor(Math.random() * colors.length)]; $(this).css({ backgroundColor: newColor, boxShadow: `0 0 10px ${newColor}, 0 0 20px ${newColor}` }); }); tree.append(light); }); } // Создаем подарки function createGifts() { const giftsContainer = $('#giftsContainer'); const giftColors = [ {box: '#ff3366', ribbon: '#ffcc00'}, {box: '#3366ff', ribbon: '#ffcc00'}, {box: '#33cc33', ribbon: '#ff0066'}, {box: '#ff9933', ribbon: '#3366ff'}, {box: '#cc33ff', ribbon: '#33cc33'} ]; giftColors.forEach((color, index) => { const gift = $('<div class="gift"></div>'); const giftBox = $('<div class="gift-box"></div>'); const giftLid = $('<div class="gift-lid"></div>'); const giftRibbon = $('<div class="gift-ribbon"></div>'); const giftRibbonHorizontal = $('<div class="gift-ribbon-horizontal"></div>'); giftBox.css('backgroundColor', color.box); giftLid.css('backgroundColor', color.box); giftRibbon.css('backgroundColor', color.ribbon); giftRibbonHorizontal.css('backgroundColor', color.ribbon); giftBox.append(giftRibbon); giftBox.append(giftRibbonHorizontal); gift.append(giftLid); gift.append(giftBox); // При клике на подарок gift.click(function() { const messages = [ "Удача в делах!", "Крепкого здоровья!", "Много путешествий!", "Любви и гармонии!", "Исполнения мечты!" ]; alert(`С Новым годом! ${messages[index]}`); // Анимация встряхивания подарка $(this).css('transform', 'rotate(20deg)'); setTimeout(() => { $(this).css('transform', 'rotate(-20deg)'); setTimeout(() => { $(this).css('transform', 'rotate(0)'); }, 200); }, 200); }); giftsContainer.append(gift); }); } // Таймер до Нового года function updateCountdown() { const now = new Date(); const newYear = new Date(now.getFullYear() + 1, 0, 1, 0, 0, 0); // Если уже 2026 год if (now.getFullYear() >= 2026) { $('#countdown').html('С Новым 2026 Годом! 🎉'); return; } const diff = newYear - now; const days = Math.floor(diff / (1000 * 60 * 60 * 24)); const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((diff % (1000 * 60)) / 1000); $('#countdown').html(`${days}д ${hours}ч ${minutes}м ${seconds}с`); } // Фейерверк function createFirework(x, y) { const colors = ['#ff0000', '#00ff00', '#ffff00', '#ff00ff', '#00ffff', '#ff9900']; const particles = 30; for (let i = 0; i < particles; i++) { const particle = $('<div class="firework"></div>'); const color = colors[Math.floor(Math.random() * colors.length)]; const angle = (Math.PI * 2 * i) / particles; const velocity = 2 + Math.random() * 2; const vx = Math.cos(angle) * velocity; const vy = Math.sin(angle) * velocity; particle.css({ left: x, top: y, backgroundColor: color, boxShadow: `0 0 5px ${color}, 0 0 10px ${color}` }); $('body').append(particle); // Анимация частицы let posX = x; let posY = y; const gravity = 0.05; const moveParticle = setInterval(() => { posX += vx; posY += vy; vy += gravity; particle.css({ left: posX + 'px', top: posY + 'px', opacity: parseFloat(particle.css('opacity')) - 0.02 }); if (parseFloat(particle.css('opacity')) <= 0) { clearInterval(moveParticle); particle.remove(); } }, 30); } } // Запуск фейерверков function startFireworks(count = 10) { for (let i = 0; i < count; i++) { setTimeout(() => { const x = Math.random() * window.innerWidth; const y = Math.random() * window.innerHeight / 2; createFirework(x, y); }, i * 300); } } // Инициализация createSnow(); addLightsToTree(); createGifts(); updateCountdown(); setInterval(updateCountdown, 1000); // Обработчики событий $('#fireworksBtn').click(function() { startFireworks(15); }); // Случайные фейерверки каждые 10 секунд setInterval(() => { if (Math.random() > 0.7) { createFirework( Math.random() * window.innerWidth, Math.random() * window.innerHeight / 2 ); } }, 10000); // Фейерверк при клике в любое место $(document).click(function(e) { if (e.target.id !== 'fireworksBtn' && !$(e.target).hasClass('light') && !$(e.target).hasClass('gift')) { createFirework(e.pageX, e.pageY); } }); }); </script> </body> </html>Добавлено (2025-12-30, 13:44) --------------------------------------------- <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>С Новым 2026 Годом!</title> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background: radial-gradient(circle at center, #0a1929 0%, #05111f 70%, #020a15 100%); color: #fff; min-height: 100vh; overflow-x: hidden; position: relative; } .snow-container { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; } .container { max-width: 1200px; margin: 0 auto; padding: 20px; position: relative; z-index: 2; } header { text-align: center; padding: 30px 0; position: relative; } h1 { font-size: 4.2rem; text-shadow: 0 0 20px #ff0066, 0 0 40px #ff0066, 0 0 60px #ff0066; margin-bottom: 10px; background: linear-gradient(to right, #ff3366, #ff9933, #ffff33, #33ff66, #3366ff); -webkit-background-clip: text; background-clip: text; color: transparent; animation: glow 3s ease-in-out infinite alternate; letter-spacing: 2px; padding: 10px 0; } .subtitle { font-size: 2rem; margin-bottom: 20px; color: #ffdd44; text-shadow: 0 0 15px #ffaa00, 0 0 25px #ffaa00; font-weight: 300; } .content { display: flex; flex-wrap: wrap; justify-content: space-around; align-items: flex-start; gap: 40px; margin-top: 30px; } .greeting-box { flex: 1; min-width: 300px; background: rgba(0, 30, 50, 0.8); border-radius: 20px; padding: 35px; box-shadow: 0 0 40px rgba(0, 200, 255, 0.4), inset 0 0 20px rgba(0, 200, 255, 0.1); backdrop-filter: blur(10px); border: 1px solid rgba(0, 200, 255, 0.3); transform-style: preserve-3d; perspective: 1000px; transition: transform 0.5s; } .greeting-box:hover { transform: translateY(-10px) rotateX(5deg); } .tree-container { flex: 1; min-width: 300px; display: flex; justify-content: center; align-items: center; position: relative; perspective: 1000px; } .tree { position: relative; width: 320px; height: 450px; transform-style: preserve-3d; } .tree-part { position: absolute; border-left: 100px solid transparent; border-right: 100px solid transparent; border-bottom: 140px solid #0d6b0d; filter: drop-shadow(0 10px 15px rgba(0, 0, 0, 0.5)); transform-style: preserve-3d; } .tree-part::before { content: ''; position: absolute; top: 5px; left: -95px; border-left: 95px solid transparent; border-right: 95px solid transparent; border-bottom: 135px solid #0e7a0e; z-index: -1; } .tree-part-1 { bottom: 0; width: 0; height: 0; border-left: 150px solid transparent; border-right: 150px solid transparent; border-bottom: 180px solid #0b5c0b; z-index: 3; } .tree-part-2 { bottom: 120px; width: 0; height: 0; border-left: 120px solid transparent; border-right: 120px solid transparent; border-bottom: 150px solid #0d6b0d; z-index: 2; } .tree-part-3 { bottom: 230px; width: 0; height: 0; border-left: 90px solid transparent; border-right: 90px solid transparent; border-bottom: 120px solid #0e7a0e; z-index: 1; } .trunk { position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 50px; height: 70px; background: linear-gradient(to right, #8B4513, #A0522D, #8B4513); border-radius: 0 0 10px 10px; box-shadow: inset 0 -10px 20px rgba(0, 0, 0, 0.5), 0 5px 15px rgba(0, 0, 0, 0.7); z-index: 4; } .star { position: absolute; top: -20px; left: 50%; transform: translateX(-50%); color: gold; font-size: 3.5rem; text-shadow: 0 0 20px gold, 0 0 40px orange; z-index: 5; animation: spin 4s linear infinite; } .light { position: absolute; width: 16px; height: 16px; border-radius: 50%; animation: twinkle 1.5s infinite alternate; cursor: pointer; filter: drop-shadow(0 0 8px currentColor); transition: transform 0.3s, filter 0.3s; z-index: 10; } .light:hover { transform: scale(1.3); filter: drop-shadow(0 0 15px currentColor); } .gifts-container { display: flex; justify-content: center; gap: 30px; margin-top: 50px; flex-wrap: wrap; } .gift { width: 120px; height: 120px; position: relative; cursor: pointer; transition: transform 0.5s; transform-style: preserve-3d; } .gift:hover { transform: translateY(-15px) rotateY(15deg); } .gift-box { width: 100%; height: 80px; position: absolute; bottom: 0; border-radius: 10px; box-shadow: 0 10px 20px rgba(0, 0, 0, 0.5), inset 0 -5px 10px rgba(0, 0, 0, 0.3); transform-style: preserve-3d; } .gift-lid { width: 110%; height: 30px; position: absolute; top: 0; left: -5%; border-radius: 10px 10px 0 0; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); } .gift-ribbon { position: absolute; background: linear-gradient(45deg, #ff0000, #ff6666, #ff0000); width: 25px; height: 100%; left: 50%; transform: translateX(-50%); border-radius: 5px; box-shadow: 0 0 10px rgba(255, 0, 0, 0.5); } .gift-ribbon-horizontal { position: absolute; background: linear-gradient(45deg, #ff0000, #ff6666, #ff0000); width: 100%; height: 25px; top: 50%; transform: translateY(-50%); border-radius: 5px; box-shadow: 0 0 10px rgba(255, 0, 0, 0.5); } .gift-bow { position: absolute; width: 40px; height: 40px; top: -15px; left: 50%; transform: translateX(-50%); background: linear-gradient(45deg, #ff0000, #ff6666); border-radius: 50%; z-index: 2; box-shadow: 0 0 15px rgba(255, 0, 0, 0.7); } .gift-bow::before, .gift-bow::after { content: ''; position: absolute; width: 50px; height: 30px; background: linear-gradient(45deg, #ff0000, #ff6666); border-radius: 50%; top: 5px; } .gift-bow::before { left: -30px; transform: rotate(-45deg); } .gift-bow::after { right: -30px; transform: rotate(45deg); } .message { font-size: 1.25rem; line-height: 1.7; margin-bottom: 25px; text-align: justify; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); } .signature { text-align: right; font-style: italic; font-size: 1.6rem; color: #ffdd44; margin-top: 25px; text-shadow: 0 0 10px #ffaa00; } .counter { text-align: center; font-size: 2rem; margin: 30px 0; color: #ffdd44; text-shadow: 0 0 15px #ffaa00; background: rgba(0, 20, 40, 0.6); padding: 20px; border-radius: 15px; display: inline-block; box-shadow: 0 0 20px rgba(255, 170, 0, 0.3); border: 1px solid rgba(255, 200, 0, 0.2); } .fireworks-btn { display: block; margin: 40px auto; padding: 18px 45px; background: linear-gradient(45deg, #ff3366, #ff9933, #ff3366); border: none; border-radius: 50px; color: white; font-size: 1.3rem; font-weight: bold; cursor: pointer; transition: all 0.4s; box-shadow: 0 0 25px rgba(255, 102, 0, 0.7), 0 5px 15px rgba(0, 0, 0, 0.3); text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); position: relative; overflow: hidden; z-index: 2; } .fireworks-btn:hover { transform: translateY(-5px) scale(1.05); box-shadow: 0 0 35px rgba(255, 102, 0, 0.9), 0 10px 25px rgba(0, 0, 0, 0.4); } .fireworks-btn:before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: 0.5s; z-index: -1; } .fireworks-btn:hover:before { left: 100%; } footer { text-align: center; padding: 40px 0; margin-top: 70px; border-top: 1px solid rgba(255, 255, 255, 0.1); color: #bbddff; font-size: 1.1rem; } .firework { position: fixed; width: 8px; height: 8px; border-radius: 50%; pointer-events: none; z-index: 1000; } .snowflake { position: absolute; background: linear-gradient(transparent, white); border-radius: 50%; opacity: 0.8; filter: blur(0.5px); box-shadow: 0 0 10px white, 0 0 20px rgba(255, 255, 255, 0.5); } .snowflake:nth-child(3n) { background: linear-gradient(transparent, #e6f2ff); } .snowflake:nth-child(5n) { background: linear-gradient(transparent, #cce6ff); } @keyframes glow { 0% { text-shadow: 0 0 20px #ff0066, 0 0 40px #ff0066, 0 0 60px #ff0066; } 100% { text-shadow: 0 0 25px #00ccff, 0 0 50px #00ccff, 0 0 75px #00ccff; } } @keyframes twinkle { 0% { opacity: 0.4; transform: scale(0.9); } 100% { opacity: 1; transform: scale(1.1); } } @keyframes fall { 0% { transform: translateY(-100px) translateX(0) rotate(0deg); opacity: 0; } 10% { opacity: 1; } 90% { opacity: 1; } 100% { transform: translateY(100vh) translateX(30px) rotate(360deg); opacity: 0; } } @keyframes spin { 0% { transform: translateX(-50%) rotate(0deg); } 100% { transform: translateX(-50%) rotate(360deg); } } @keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-20px); } } .float { animation: float 6s ease-in-out infinite; } @media (max-width: 768px) { h1 { font-size: 2.8rem; } .subtitle { font-size: 1.6rem; } .content { flex-direction: column; align-items: center; } .greeting-box, .tree-container { width: 100%; } .tree { transform: scale(0.9); } } </style> </head> <body> <div class="snow-container" id="snow"></div> <div class="container"> <header> <h1>С Наступающим 2026 Годом!</h1> <div class="subtitle">Пусть новый год принесёт счастье, здоровье и успех!</div> <div class="counter" id="counter">До 2026 года осталось: <br><span id="countdown"></span></div> </header> <div class="content"> <div class="greeting-box float"> <h2><i class="fas fa-star"></i> Дорогие друзья! <i class="fas fa-star"></i></h2> <div class="message"> <p>Скоро наступит волшебная ночь, когда часы пробьют двенадцать, и мы встретим новый 2026 год! Пусть этот год станет для вас временем новых начинаний, радостных событий и исполнения самых заветных желаний.</p> <p>Желаем, чтобы в вашем доме всегда царили уют, тепло и взаимопонимание. Пусть работа приносит удовольствие и достойное вознаграждение, а в личной жизни будет много счастливых моментов и приятных сюрпризов.</p> <p>Пусть здоровье будет крепким, энергии — через край, а настроение — всегда праздничным. Пусть Новый 2026 год станет годом больших побед, ярких открытий и незабываемых впечатлений!</p> </div> <div class="signature">С любовью и наилучшими пожеланиями!</div> </div> <div class="tree-container"> <div class="tree float" id="tree"> <div class="tree-part tree-part-1"></div> <div class="tree-part tree-part-2"></div> <div class="tree-part tree-part-3"></div> <div class="trunk"></div> <div class="star"><i class="fas fa-star"></i></div> <!-- Огоньки будут добавлены через JS --> </div> </div> </div> <button class="fireworks-btn" id="fireworksBtn"> <i class="fas fa-fire"></i> ЗАПУСТИТЬ ФЕЙЕРВЕРК! <i class="fas fa-fire"></i> </button> <div class="gifts-container" id="giftsContainer"> <!-- Подарки будут добавлены через JS --> </div> <footer> <p>© 2025 - 2026 С Новым Годом! Создано с ❤️ и праздничным настроением</p> <p>Кликайте на огоньки и подарки для сюрпризов!</p> </footer> </div>
<script> $(document).ready(function() { // Создаем реалистичный снег function createSnow() { const snowContainer = $('#snow'); for (let i = 0; i < 200; i++) { const snowflake = $('<div class="snowflake"></div>'); const size = Math.random() * 8 + 4; const opacity = Math.random() * 0.7 + 0.3; const duration = Math.random() * 8 + 7; const delay = Math.random() * 10; snowflake.css({ width: size + 'px', height: size + 'px', left: Math.random() * 100 + 'vw', top: '-50px', opacity: opacity, animation: `fall ${duration}s linear infinite`, animationDelay: delay + 's' }); snowContainer.append(snowflake); } } // Добавляем огоньки на ёлку function addLightsToTree() { const tree = $('#tree'); const colors = [ '#ff0000', '#00ff00', '#ffff00', '#ff00ff', '#00ffff', '#ff9900', '#ff33cc', '#33ff33', '#3366ff' ]; // Координаты для огоньков на ёлке const lightsPositions = [ // Нижний ярус {left: '50%', bottom: '30px'}, {left: '30%', bottom: '50px'}, {left: '70%', bottom: '50px'}, {left: '40%', bottom: '70px'}, {left: '60%', bottom: '70px'}, {left: '20%', bottom: '90px'}, {left: '80%', bottom: '90px'}, {left: '45%', bottom: '110px'}, {left: '55%', bottom: '110px'}, // Средний ярус {left: '50%', bottom: '150px'}, {left: '35%', bottom: '170px'}, {left: '65%', bottom: '170px'}, {left: '25%', bottom: '190px'}, {left: '75%', bottom: '190px'}, {left: '40%', bottom: '210px'}, {left: '60%', bottom: '210px'}, // Верхний ярус {left: '50%', bottom: '250px'}, {left: '40%', bottom: '270px'}, {left: '60%', bottom: '270px'}, {left: '30%', bottom: '290px'}, {left: '70%', bottom: '290px'}, // Верхушка {left: '50%', bottom: '320px'}, {left: '45%', bottom: '340px'}, {left: '55%', bottom: '340px'} ]; lightsPositions.forEach((pos, index) => { const light = $('<div class="light"></div>'); const color = colors[Math.floor(Math.random() * colors.length)]; const delay = Math.random() * 2; light.css({ left: pos.left, bottom: pos.bottom, backgroundColor: color, boxShadow: `0 0 15px ${color}, 0 0 30px ${color}`, animationDelay: delay + 's' }); // При клике на огонёк меняем его цвет light.click(function() { const newColor = colors[Math.floor(Math.random() * colors.length)]; $(this).css({ backgroundColor: newColor, boxShadow: `0 0 15px ${newColor}, 0 0 30px ${newColor}` }); // Создаем эффект искры createSparkle( $(this).offset().left + 8, $(this).offset().top + 8, newColor ); }); tree.append(light); }); } // Создаем искры при клике на огонёк function createSparkle(x, y, color) { for (let i = 0; i < 8; i++) { const spark = $('<div class="spark"></div>'); const angle = (Math.PI * 2 * i) / 8; const distance = 30; spark.css({ position: 'fixed', width: '4px', height: '4px', backgroundColor: color, borderRadius: '50%', left: x + 'px', top: y + 'px', boxShadow: `0 0 10px ${color}`, zIndex: 1001 }); $('body').append(spark); // Анимация искры const vx = Math.cos(angle) * distance; const vy = Math.sin(angle) * distance; spark.animate({ left: '+=' + vx + 'px', top: '+=' + vy + 'px', opacity: 0 }, 500, function() { spark.remove(); }); } } // Создаем подарки function createGifts() { const giftsContainer = $('#giftsContainer'); const giftColors = [ {box: '#ff3366', ribbon: '#ffcc00', bow: '#ff0000'}, {box: '#3366ff', ribbon: '#ffcc00', bow: '#ff0066'}, {box: '#33cc66', ribbon: '#ff0066', bow: '#ff3366'}, {box: '#ff9933', ribbon: '#3366ff', bow: '#ff9900'}, {box: '#cc33ff', ribbon: '#33cc33', bow: '#cc00ff'} ]; giftColors.forEach((color, index) => { const gift = $('<div class="gift"></div>'); const giftBox = $('<div class="gift-box"></div>'); const giftLid = $('<div class="gift-lid"></div>'); const giftRibbon = $('<div class="gift-ribbon"></div>'); const giftRibbonHorizontal = $('<div class="gift-ribbon-horizontal"></div>'); const giftBow = $('<div class="gift-bow"></div>'); giftBox.css({ background: `linear-gradient(135deg, ${color.box} 0%, ${darkenColor(color.box, 20)} 100%)` }); giftLid.css({ background: `linear-gradient(135deg, ${lightenColor(color.box, 10)} 0%, ${color.box} 100%)` }); gift.append(giftLid); gift.append(giftBox); gift.append(giftRibbon); gift.append(giftRibbonHorizontal); gift.append(giftBow); // При клике на подарок gift.click(function() { const messages = [ "Желаем счастья и удачи в Новом году!", "Пусть здоровье будет крепким как никогда!", "Желаем интересных путешествий и открытий!", "Пусть любовь и гармония наполнят ваш дом!", "Исполнения всех самых заветных желаний!" ]; // Анимация открытия подарка $(this).css('transform', 'translateY(-30px) rotateY(180deg) scale(1.2)'); // Показываем сообщение setTimeout(() => { alert(messages[index]); $(this).css('transform', 'translateY(-15px) rotateY(15deg)'); }, 500); // Создаем эффект конфетти createConfetti($(this).offset().left + 60, $(this).offset().top + 60); }); giftsContainer.append(gift); }); } // Создаем конфетти function createConfetti(x, y) { const colors = ['#ff0000', '#00ff00', '#ffff00', '#ff00ff', '#00ffff', '#ff9900']; for (let i = 0; i < 50; i++) { const confetti = $('<div class="confetti"></div>'); const color = colors[Math.floor(Math.random() * colors.length)]; const size = Math.random() * 10 + 5; const angle = Math.random() * Math.PI * 2; const distance = Math.random() * 100 + 50; const duration = Math.random() * 1000 + 500; confetti.css({ position: 'fixed', width: size + 'px', height: size + 'px', backgroundColor: color, borderRadius: '50%', left: x + 'px', top: y + 'px', zIndex: 1001, boxShadow: `0 0 5px ${color}` }); $('body').append(confetti); // Анимация конфетти const vx = Math.cos(angle) * distance; const vy = Math.sin(angle) * distance; confetti.animate({ left: '+=' + vx + 'px', top: '+=' + vy + 'px', opacity: 0 }, duration, function() { confetti.remove(); }); } } // Вспомогательные функции для цветов function darkenColor(color, percent) { const num = parseInt(color.slice(1), 16); const amt = Math.round(2.55 * percent); const R = (num >> 16) - amt; const G = (num >> 8 & 0x00FF) - amt; const B = (num & 0x0000FF) - amt; return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 + (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 + (B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1); } function lightenColor(color, percent) { const num = parseInt(color.slice(1), 16); const amt = Math.round(2.55 * percent); const R = (num >> 16) + amt; const G = (num >> 8 & 0x00FF) + amt; const B = (num & 0x0000FF) + amt; return "#" + (0x1000000 + (R > 255 ? 255 : R) * 0x10000 + (G > 255 ? 255 : G) * 0x100 + (B > 255 ? 255 : B)).toString(16).slice(1); } // Таймер до Нового года function updateCountdown() { const now = new Date(); const newYear = new Date(2026, 0, 1, 0, 0, 0); const diff = newYear - now; // Если уже 2026 год if (diff <= 0) { $('#countdown').html('🎉 С Новым 2026 Годом! 🎉'); $('#counter').addClass('float'); return; } const days = Math.floor(diff / (1000 * 60 * 60 * 24)); const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((diff % (1000 * 60)) / 1000); $('#countdown').html(`${days} дней ${hours} часов ${minutes} минут ${seconds} секунд`); } // Фейерверк function createFirework(x, y) { const colors = ['#ff0000', '#00ff00', '#ffff00', '#ff00ff', '#00ffff', '#ff9900']; const particles = 40; for (let i = 0; i < particles; i++) { const particle = $('<div class="firework"></div>'); const color = colors[Math.floor(Math.random() * colors.length)]; const angle = (Math.PI * 2 * i) / particles; const velocity = 3 + Math.random() * 3; const vx = Math.cos(angle) * velocity; const vy = Math.sin(angle) * velocity; const size = Math.random() * 8 + 4; particle.css({ left: x, top: y, width: size + 'px', height: size + 'px', backgroundColor: color, boxShadow: `0 0 10px ${color}, 0 0 20px ${color}` }); $('body').append(particle); // Анимация частицы let posX = x; let posY = y; const gravity = 0.08; let vyCurrent = vy; const moveParticle = setInterval(() => { posX += vx; posY += vyCurrent; vyCurrent += gravity; particle.css({ left: posX + 'px', top: posY + 'px', opacity: parseFloat(particle.css('opacity')) - 0.015 }); if (parseFloat(particle.css('opacity')) <= 0) { clearInterval(moveParticle); particle.remove(); } }, 30); } // Звуковой эффект (имитация) const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.frequency.setValueAtTime(800, audioContext.currentTime); oscillator.frequency.exponentialRampToValueAtTime(200, audioContext.currentTime + 0.5); gainNode.gain.setValueAtTime(0.1, audioContext.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5); oscillator.start(audioContext.currentTime); oscillator.stop(audioContext.currentTime + 0.5); } // Запуск фейерверков function startFireworks(count = 15) { for (let i = 0; i < count; i++) { setTimeout(() => { const x = Math.random() * (window.innerWidth - 100) + 50; const y = Math.random() * (window.innerHeight / 2 - 100) + 50; createFirework(x, y); }, i * 200); } } // Инициализация createSnow(); addLightsToTree(); createGifts(); updateCountdown(); setInterval(updateCountdown, 1000); // Обработчики событий $('#fireworksBtn').click(function() { startFireworks(20); }); // Случайные фейерверки каждые 8 секунд setInterval(() => { if (Math.random() > 0.5) { createFirework( Math.random() * (window.innerWidth - 100) + 50, Math.random() * (window.innerHeight / 2 - 100) + 50 ); } }, 8000); // Фейерверк при клике в любое место $(document).click(function(e) { if (e.target.id !== 'fireworksBtn' && !$(e.target).hasClass('light') && !$(e.target).hasClass('gift') && !$(e.target).closest('.gift').length) { createFirework(e.pageX, e.pageY); } }); }); </script> </body> </html> Добавлено (2025-12-30, 14:08) --------------------------------------------- <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>С Новым 2026 Годом!</title> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background: radial-gradient(circle at center, #0a1929 0%, #05111f 70%, #020a15 100%); color: #fff; min-height: 100vh; overflow-x: hidden; position: relative; } .snow-container { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; } .container { max-width: 1200px; margin: 0 auto; padding: 20px; position: relative; z-index: 2; } header { text-align: center; padding: 30px 0; position: relative; } h1 { font-size: 4.2rem; text-shadow: 0 0 20px #ff0066, 0 0 40px #ff0066, 0 0 60px #ff0066; margin-bottom: 10px; background: linear-gradient(to right, #ff3366, #ff9933, #ffff33, #33ff66, #3366ff); -webkit-background-clip: text; background-clip: text; color: transparent; animation: glow 3s ease-in-out infinite alternate; letter-spacing: 2px; padding: 10px 0; } .subtitle { font-size: 2rem; margin-bottom: 20px; color: #ffdd44; text-shadow: 0 0 15px #ffaa00, 0 0 25px #ffaa00; font-weight: 300; } .content { display: flex; flex-wrap: wrap; justify-content: space-around; align-items: flex-start; gap: 40px; margin-top: 30px; } .greeting-box { flex: 1; min-width: 300px; background: rgba(0, 30, 50, 0.8); border-radius: 20px; padding: 35px; box-shadow: 0 0 40px rgba(0, 200, 255, 0.4), inset 0 0 20px rgba(0, 200, 255, 0.1); backdrop-filter: blur(10px); border: 1px solid rgba(0, 200, 255, 0.3); transform-style: preserve-3d; perspective: 1000px; transition: transform 0.5s; } .greeting-box:hover { transform: translateY(-10px) rotateX(5deg); } .tree-container { flex: 1; min-width: 300px; display: flex; justify-content: center; align-items: center; position: relative; perspective: 1000px; min-height: 500px; } .tree { position: relative; width: 320px; height: 450px; transform-style: preserve-3d; } /* Исправленная ёлка с правильно расположенными ярусами */ .tree-part { position: absolute; border-left: 100px solid transparent; border-right: 100px solid transparent; border-bottom: 140px solid #0d6b0d; filter: drop-shadow(0 10px 15px rgba(0, 0, 0, 0.5)); transform-style: preserve-3d; } .tree-part::before { content: ''; position: absolute; top: 5px; left: -95px; border-left: 95px solid transparent; border-right: 95px solid transparent; border-bottom: 135px solid #0e7a0e; z-index: -1; } /* Нижний ярус (самый большой) */ .tree-part-1 { bottom: 70px; /* Оставляем место для ствола */ left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 150px solid transparent; border-right: 150px solid transparent; border-bottom: 180px solid #0b5c0b; z-index: 3; } /* Средний ярус (меньше и выше) */ .tree-part-2 { bottom: 200px; /* Над первым ярусом */ left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 120px solid transparent; border-right: 120px solid transparent; border-bottom: 150px solid #0d6b0d; z-index: 2; } /* Верхний ярус (самый маленький и высокий) */ .tree-part-3 { bottom: 320px; /* Над вторым ярусом */ left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 90px solid transparent; border-right: 90px solid transparent; border-bottom: 120px solid #0e7a0e; z-index: 1; } /* Ствол ёлки - теперь ПОД ветками */ .trunk { position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 50px; height: 70px; background: linear-gradient(to right, #8B4513, #A0522D, #8B4513); border-radius: 0 0 10px 10px; box-shadow: inset 0 -10px 20px rgba(0, 0, 0, 0.5), 0 5px 15px rgba(0, 0, 0, 0.7); z-index: 4; } .star { position: absolute; top: -20px; left: 50%; transform: translateX(-50%); color: gold; font-size: 3.5rem; text-shadow: 0 0 20px gold, 0 0 40px orange; z-index: 5; animation: spin 4s linear infinite; } .light { position: absolute; width: 16px; height: 16px; border-radius: 50%; animation: twinkle 1.5s infinite alternate; cursor: pointer; filter: drop-shadow(0 0 8px currentColor); transition: transform 0.3s, filter 0.3s; z-index: 10; } .light:hover { transform: scale(1.3); filter: drop-shadow(0 0 15px currentColor); } .gifts-container { display: flex; justify-content: center; gap: 30px; margin-top: 50px; flex-wrap: wrap; } .gift { width: 120px; height: 120px; position: relative; cursor: pointer; transition: transform 0.5s; transform-style: preserve-3d; } .gift:hover { transform: translateY(-15px) rotateY(15deg); } .gift-box { width: 100%; height: 80px; position: absolute; bottom: 0; border-radius: 10px; box-shadow: 0 10px 20px rgba(0, 0, 0, 0.5), inset 0 -5px 10px rgba(0, 0, 0, 0.3); transform-style: preserve-3d; } .gift-lid { width: 110%; height: 30px; position: absolute; top: 0; left: -5%; border-radius: 10px 10px 0 0; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); } .gift-ribbon { position: absolute; background: linear-gradient(45deg, #ff0000, #ff6666, #ff0000); width: 25px; height: 100%; left: 50%; transform: translateX(-50%); border-radius: 5px; box-shadow: 0 0 10px rgba(255, 0, 0, 0.5); } .gift-ribbon-horizontal { position: absolute; background: linear-gradient(45deg, #ff0000, #ff6666, #ff0000); width: 100%; height: 25px; top: 50%; transform: translateY(-50%); border-radius: 5px; box-shadow: 0 0 10px rgba(255, 0, 0, 0.5); } .gift-bow { position: absolute; width: 40px; height: 40px; top: -15px; left: 50%; transform: translateX(-50%); background: linear-gradient(45deg, #ff0000, #ff6666); border-radius: 50%; z-index: 2; box-shadow: 0 0 15px rgba(255, 0, 0, 0.7); } .gift-bow::before, .gift-bow::after { content: ''; position: absolute; width: 50px; height: 30px; background: linear-gradient(45deg, #ff0000, #ff6666); border-radius: 50%; top: 5px; } .gift-bow::before { left: -30px; transform: rotate(-45deg); } .gift-bow::after { right: -30px; transform: rotate(45deg); } /* Окно сообщения для подарков */ .gift-message-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); display: flex; justify-content: center; align-items: center; z-index: 2000; opacity: 0; visibility: hidden; transition: opacity 0.5s, visibility 0.5s; backdrop-filter: blur(5px); } .gift-message-overlay.active { opacity: 1; visibility: visible; } .gift-message { background: linear-gradient(135deg, rgba(0, 40, 80, 0.95), rgba(0, 60, 100, 0.95)); width: 90%; max-width: 500px; padding: 40px; border-radius: 20px; text-align: center; box-shadow: 0 0 50px rgba(0, 200, 255, 0.5), inset 0 0 30px rgba(0, 200, 255, 0.2); border: 2px solid rgba(0, 200, 255, 0.3); transform: scale(0.8); transition: transform 0.5s; position: relative; } .gift-message-overlay.active .gift-message { transform: scale(1); } .gift-message h3 { font-size: 2.2rem; color: #ffdd44; margin-bottom: 20px; text-shadow: 0 0 10px #ffaa00; } .gift-message p { font-size: 1.3rem; line-height: 1.6; margin-bottom: 30px; } .close-gift-message { position: absolute; top: 15px; right: 15px; background: rgba(255, 0, 0, 0.3); border: none; color: white; width: 35px; height: 35px; border-radius: 50%; font-size: 1.2rem; cursor: pointer; transition: background 0.3s; } .close-gift-message:hover { background: rgba(255, 0, 0, 0.6); } .message-icon { font-size: 4rem; color: #ffdd44; margin-bottom: 20px; text-shadow: 0 0 15px #ffaa00; } .message { font-size: 1.25rem; line-height: 1.7; margin-bottom: 25px; text-align: justify; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); } .signature { text-align: right; font-style: italic; font-size: 1.6rem; color: #ffdd44; margin-top: 25px; text-shadow: 0 0 10px #ffaa00; } .counter { text-align: center; font-size: 2rem; margin: 30px 0; color: #ffdd44; text-shadow: 0 0 15px #ffaa00; background: rgba(0, 20, 40, 0.6); padding: 20px; border-radius: 15px; display: inline-block; box-shadow: 0 0 20px rgba(255, 170, 0, 0.3); border: 1px solid rgba(255, 200, 0, 0.2); } .fireworks-btn { display: block; margin: 40px auto; padding: 18px 45px; background: linear-gradient(45deg, #ff3366, #ff9933, #ff3366); border: none; border-radius: 50px; color: white; font-size: 1.3rem; font-weight: bold; cursor: pointer; transition: all 0.4s; box-shadow: 0 0 25px rgba(255, 102, 0, 0.7), 0 5px 15px rgba(0, 0, 0, 0.3); text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); position: relative; overflow: hidden; z-index: 2; } .fireworks-btn:hover { transform: translateY(-5px) scale(1.05); box-shadow: 0 0 35px rgba(255, 102, 0, 0.9), 0 10px 25px rgba(0, 0, 0, 0.4); } .fireworks-btn:before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: 0.5s; z-index: -1; } .fireworks-btn:hover:before { left: 100%; } footer { text-align: center; padding: 40px 0; margin-top: 70px; border-top: 1px solid rgba(255, 255, 255, 0.1); color: #bbddff; font-size: 1.1rem; } .firework { position: fixed; width: 8px; height: 8px; border-radius: 50%; pointer-events: none; z-index: 1000; } .snowflake { position: absolute; background: linear-gradient(transparent, white); border-radius: 50%; opacity: 0.8; filter: blur(0.5px); box-shadow: 0 0 10px white, 0 0 20px rgba(255, 255, 255, 0.5); } .snowflake:nth-child(3n) { background: linear-gradient(transparent, #e6f2ff); } .snowflake:nth-child(5n) { background: linear-gradient(transparent, #cce6ff); } @keyframes glow { 0% { text-shadow: 0 0 20px #ff0066, 0 0 40px #ff0066, 0 0 60px #ff0066; } 100% { text-shadow: 0 0 25px #00ccff, 0 0 50px #00ccff, 0 0 75px #00ccff; } } @keyframes twinkle { 0% { opacity: 0.4; transform: scale(0.9); } 100% { opacity: 1; transform: scale(1.1); } } @keyframes fall { 0% { transform: translateY(-100px) translateX(0) rotate(0deg); opacity: 0; } 10% { opacity: 1; } 90% { opacity: 1; } 100% { transform: translateY(100vh) translateX(30px) rotate(360deg); opacity: 0; } } @keyframes spin { 0% { transform: translateX(-50%) rotate(0deg); } 100% { transform: translateX(-50%) rotate(360deg); } } @keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-20px); } } .float { animation: float 6s ease-in-out infinite; } @media (max-width: 768px) { h1 { font-size: 2.8rem; } .subtitle { font-size: 1.6rem; } .content { flex-direction: column; align-items: center; } .greeting-box, .tree-container { width: 100%; } .tree { transform: scale(0.9); } } </style> </head> <body> <div class="snow-container" id="snow"></div> <!-- Окно для сообщений от подарков --> <div class="gift-message-overlay" id="giftMessageOverlay"> <div class="gift-message"> <button class="close-gift-message" id="closeGiftMessage">×</button> <div class="message-icon" id="giftIcon"></div> <h3 id="giftTitle"></h3> <p id="giftText"></p> </div> </div> <div class="container"> <header> <h1>С Наступающим 2026 Годом!</h1> <div class="subtitle">Пусть новый год принесёт счастье, здоровье и успех!</div> <div class="counter" id="counter">До 2026 года осталось: <br><span id="countdown"></span></div> </header> <div class="content"> <div class="greeting-box float"> <h2><i class="fas fa-star"></i> Дорогие друзья! <i class="fas fa-star"></i></h2> <div class="message"> <p>Скоро наступит волшебная ночь, когда часы пробьют двенадцать, и мы встретим новый 2026 год! Пусть этот год станет для вас временем новых начинаний, радостных событий и исполнения самых заветных желаний.</p> <p>Желаем, чтобы в вашем доме всегда царили уют, тепло и взаимопонимание. Пусть работа приносит удовольствие и достойное вознаграждение, а в личной жизни будет много счастливых моментов и приятных сюрпризов.</p> <p>Пусть здоровье будет крепким, энергии — через край, а настроение — всегда праздничным. Пусть Новый 2026 год станет годом больших побед, ярких открытий и незабываемых впечатлений!</p> </div> <div class="signature">С любовью и наилучшими пожеланиями!</div> </div> <div class="tree-container"> <div class="tree float" id="tree"> <div class="tree-part tree-part-1"></div> <div class="tree-part tree-part-2"></div> <div class="tree-part tree-part-3"></div> <div class="trunk"></div> <div class="star"><i class="fas fa-star"></i></div> <!-- Огоньки будут добавлены через JS --> </div> </div> </div> <button class="fireworks-btn" id="fireworksBtn"> <i class="fas fa-fire"></i> ЗАПУСТИТЬ ФЕЙЕРВЕРК! <i class="fas fa-fire"></i> </button> <div class="gifts-container" id="giftsContainer"> <!-- Подарки будут добавлены через JS --> </div> <footer> <p>© 2025 - 2026 С Новым Годом! Создано с ❤️ и праздничным настроением</p> <p>Кликайте на огоньки и подарки для сюрпризов!</p> </footer> </div>
<script> $(document).ready(function() { // Создаем реалистичный снег function createSnow() { const snowContainer = $('#snow'); for (let i = 0; i < 200; i++) { const snowflake = $('<div class="snowflake"></div>'); const size = Math.random() * 8 + 4; const opacity = Math.random() * 0.7 + 0.3; const duration = Math.random() * 8 + 7; const delay = Math.random() * 10; snowflake.css({ width: size + 'px', height: size + 'px', left: Math.random() * 100 + 'vw', top: '-50px', opacity: opacity, animation: `fall ${duration}s linear infinite`, animationDelay: delay + 's' }); snowContainer.append(snowflake); } } // Добавляем огоньки на ёлку function addLightsToTree() { const tree = $('#tree'); const colors = [ '#ff0000', '#00ff00', '#ffff00', '#ff00ff', '#00ffff', '#ff9900', '#ff33cc', '#33ff33', '#3366ff' ]; // Координаты для огоньков на ёлке const lightsPositions = [ // Нижний ярус {left: '50%', bottom: '100px'}, {left: '30%', bottom: '120px'}, {left: '70%', bottom: '120px'}, {left: '40%', bottom: '140px'}, {left: '60%', bottom: '140px'}, {left: '20%', bottom: '160px'}, {left: '80%', bottom: '160px'}, {left: '45%', bottom: '180px'}, {left: '55%', bottom: '180px'}, // Средний ярус {left: '50%', bottom: '220px'}, {left: '35%', bottom: '240px'}, {left: '65%', bottom: '240px'}, {left: '25%', bottom: '260px'}, {left: '75%', bottom: '260px'}, {left: '40%', bottom: '280px'}, {left: '60%', bottom: '280px'}, // Верхний ярус {left: '50%', bottom: '340px'}, {left: '40%', bottom: '360px'}, {left: '60%', bottom: '360px'}, {left: '30%', bottom: '380px'}, {left: '70%', bottom: '380px'}, // Верхушка {left: '50%', bottom: '410px'}, {left: '45%', bottom: '420px'}, {left: '55%', bottom: '420px'} ]; lightsPositions.forEach((pos, index) => { const light = $('<div class="light"></div>'); const color = colors[Math.floor(Math.random() * colors.length)]; const delay = Math.random() * 2; light.css({ left: pos.left, bottom: pos.bottom, backgroundColor: color, boxShadow: `0 0 15px ${color}, 0 0 30px ${color}`, animationDelay: delay + 's' }); // При клике на огонёк меняем его цвет light.click(function() { const newColor = colors[Math.floor(Math.random() * colors.length)]; $(this).css({ backgroundColor: newColor, boxShadow: `0 0 15px ${newColor}, 0 0 30px ${newColor}` }); // Создаем эффект искры createSparkle( $(this).offset().left + 8, $(this).offset().top + 8, newColor ); }); tree.append(light); }); } // Создаем искры при клике на огонёк function createSparkle(x, y, color) { for (let i = 0; i < 8; i++) { const spark = $('<div class="spark"></div>'); const angle = (Math.PI * 2 * i) / 8; const distance = 30; spark.css({ position: 'fixed', width: '4px', height: '4px', backgroundColor: color, borderRadius: '50%', left: x + 'px', top: y + 'px', boxShadow: `0 0 10px ${color}`, zIndex: 1001 }); $('body').append(spark); // Анимация искры const vx = Math.cos(angle) * distance; const vy = Math.sin(angle) * distance; spark.animate({ left: '+=' + vx + 'px', top: '+=' + vy + 'px', opacity: 0 }, 500, function() { spark.remove(); }); } } // Создаем подарки function createGifts() { const giftsContainer = $('#giftsContainer'); const gifts = [ { box: '#ff3366', ribbon: '#ffcc00', bow: '#ff0000', icon: 'fa-gem', title: 'Дорогой подарок!', message: 'Желаем финансового благополучия и процветания в Новом году! Пусть все ваши проекты будут успешными, а доходы только растут!' }, { box: '#3366ff', ribbon: '#ffcc00', bow: '#ff0066', icon: 'fa-heart', title: 'Подарок любви!', message: 'Желаем крепкого здоровья, счастливых отношений и взаимопонимания с близкими! Пусть ваше сердце всегда будет наполнено любовью!' }, { box: '#33cc66', ribbon: '#ff0066', bow: '#ff3366', icon: 'fa-globe-americas', title: 'Подарок путешествий!', message: 'Желаем интересных поездок, новых открытий и незабываемых впечатлений! Пусть 2026 год будет полон ярких событий и удивительных мест!' }, { box: '#ff9933', ribbon: '#3366ff', bow: '#ff9900', icon: 'fa-graduation-cap', title: 'Подарок знаний!', message: 'Желаем профессионального роста, новых навыков и интересных проектов! Пусть работа приносит радость и достойное вознаграждение!' }, { box: '#cc33ff', ribbon: '#33cc33', bow: '#cc00ff', icon: 'fa-magic', title: 'Волшебный подарок!', message: 'Желаем, чтобы все ваши мечты сбылись в Новом году! Пусть каждый день будет наполнен волшебством, улыбками и приятными сюрпризами!' } ]; gifts.forEach((giftData, index) => { const gift = $('<div class="gift"></div>'); const giftBox = $('<div class="gift-box"></div>'); const giftLid = $('<div class="gift-lid"></div>'); const giftRibbon = $('<div class="gift-ribbon"></div>'); const giftRibbonHorizontal = $('<div class="gift-ribbon-horizontal"></div>'); const giftBow = $('<div class="gift-bow"></div>'); giftBox.css({ background: `linear-gradient(135deg, ${giftData.box} 0%, ${darkenColor(giftData.box, 20)} 100%)` }); giftLid.css({ background: `linear-gradient(135deg, ${lightenColor(giftData.box, 10)} 0%, ${giftData.box} 100%)` }); gift.append(giftLid); gift.append(giftBox); gift.append(giftRibbon); gift.append(giftRibbonHorizontal); gift.append(giftBow); // Сохраняем данные подарка в элементе gift.data('giftData', giftData); // При клике на подарок gift.click(function() { const giftData = $(this).data('giftData'); // Анимация открытия подарка $(this).css('transform', 'translateY(-30px) rotateY(180deg) scale(1.2)'); // Показываем сообщение через 500мс setTimeout(() => { showGiftMessage(giftData); $(this).css('transform', 'translateY(-15px) rotateY(15deg)'); }, 500); // Создаем эффект конфетти createConfetti($(this).offset().left + 60, $(this).offset().top + 60); }); giftsContainer.append(gift); }); } // Показать сообщение от подарка function showGiftMessage(giftData) { $('#giftIcon').html(`<i class="fas ${giftData.icon}"></i>`); $('#giftTitle').text(giftData.title); $('#giftText').text(giftData.message); $('#giftMessageOverlay').addClass('active'); } // Закрыть сообщение от подарка $('#closeGiftMessage').click(function() { $('#giftMessageOverlay').removeClass('active'); }); // Закрыть сообщение при клике вне его $('#giftMessageOverlay').click(function(e) { if (e.target === this) { $(this).removeClass('active'); } }); // Создаем конфетти function createConfetti(x, y) { const colors = ['#ff0000', '#00ff00', '#ffff00', '#ff00ff', '#00ffff', '#ff9900']; for (let i = 0; i < 50; i++) { const confetti = $('<div class="confetti"></div>'); const color = colors[Math.floor(Math.random() * colors.length)]; const size = Math.random() * 10 + 5; const angle = Math.random() * Math.PI * 2; const distance = Math.random() * 100 + 50; const duration = Math.random() * 1000 + 500; confetti.css({ position: 'fixed', width: size + 'px', height: size + 'px', backgroundColor: color, borderRadius: '50%', left: x + 'px', top: y + 'px', zIndex: 1001, boxShadow: `0 0 5px ${color}` }); $('body').append(confetti); // Анимация конфетти const vx = Math.cos(angle) * distance; const vy = Math.sin(angle) * distance; confetti.animate({ left: '+=' + vx + 'px', top: '+=' + vy + 'px', opacity: 0 }, duration, function() { confetti.remove(); }); } } // Вспомогательные функции для цветов function darkenColor(color, percent) { const num = parseInt(color.slice(1), 16); const amt = Math.round(2.55 * percent); const R = (num >> 16) - amt; const G = (num >> 8 & 0x00FF) - amt; const B = (num & 0x0000FF) - amt; return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 + (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 + (B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1); } function lightenColor(color, percent) { const num = parseInt(color.slice(1), 16); const amt = Math.round(2.55 * percent); const R = (num >> 16) + amt; const G = (num >> 8 & 0x00FF) + amt; const B = (num & 0x0000FF) + amt; return "#" + (0x1000000 + (R > 255 ? 255 : R) * 0x10000 + (G > 255 ? 255 : G) * 0x100 + (B > 255 ? 255 : B)).toString(16).slice(1); } // Таймер до Нового года function updateCountdown() { const now = new Date(); const newYear = new Date(2026, 0, 1, 0, 0, 0); const diff = newYear - now; // Если уже 2026 год if (diff <= 0) { $('#countdown').html('🎉 С Новым 2026 Годом! 🎉'); $('#counter').addClass('float'); return; } const days = Math.floor(diff / (1000 * 60 * 60 * 24)); const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((diff % (1000 * 60)) / 1000); $('#countdown').html(`${days} дней ${hours} часов ${minutes} минут ${seconds} секунд`); } // Фейерверк function createFirework(x, y) { const colors = ['#ff0000', '#00ff00', '#ffff00', '#ff00ff', '#00ffff', '#ff9900']; const particles = 40; for (let i = 0; i < particles; i++) { const particle = $('<div class="firework"></div>'); const color = colors[Math.floor(Math.random() * colors.length)]; const angle = (Math.PI * 2 * i) / particles; const velocity = 3 + Math.random() * 3; const vx = Math.cos(angle) * velocity; const vy = Math.sin(angle) * velocity; const size = Math.random() * 8 + 4; particle.css({ left: x, top: y, width: size + 'px', height: size + 'px', backgroundColor: color, boxShadow: `0 0 10px ${color}, 0 0 20px ${color}` }); $('body').append(particle); // Анимация частицы let posX = x; let posY = y; const gravity = 0.08; let vyCurrent = vy; const moveParticle = setInterval(() => { posX += vx; posY += vyCurrent; vyCurrent += gravity; particle.css({ left: posX + 'px', top: posY + 'px', opacity: parseFloat(particle.css('opacity')) - 0.015 }); if (parseFloat(particle.css('opacity')) <= 0) { clearInterval(moveParticle); particle.remove(); } }, 30); } } // Запуск фейерверков function startFireworks(count = 15) { for (let i = 0; i < count; i++) { setTimeout(() => { const x = Math.random() * (window.innerWidth - 100) + 50; const y = Math.random() * (window.innerHeight / 2 - 100) + 50; createFirework(x, y); }, i * 200); } } // Инициализация createSnow(); addLightsToTree(); createGifts(); updateCountdown(); setInterval(updateCountdown, 1000); // Обработчики событий $('#fireworksBtn').click(function() { startFireworks(20); }); // Случайные фейерверки каждые 8 секунд setInterval(() => { if (Math.random() > 0.5) { createFirework( Math.random() * (window.innerWidth - 100) + 50, Math.random() * (window.innerHeight / 2 - 100) + 50 ); } }, 8000); // Фейерверк при клике в любое место $(document).click(function(e) { if (e.target.id !== 'fireworksBtn' && !$(e.target).hasClass('light') && !$(e.target).hasClass('gift') && !$(e.target).closest('.gift').length && e.target.id !== 'closeGiftMessage' && !$(e.target).closest('.gift-message').length) { createFirework(e.pageX, e.pageY); } }); }); </script> </body> </html>
|