Отзывы и предложения к софту от AleXStam
  • Страница 33 из 33
  • «
  • 1
  • 2
  • 31
  • 32
  • 33
Поговорим о...
переделать кнопку закрытия, крестик не по центру круглой кнопки, также саму круглую кнопку нужно поправить пусть чуть меньше будет и отображена корректно с плавной анимацией простой при наведении. Остальной код не трогай, логику отображения не переделывай. также немного уменьши радиус скругления изображения.
<!-- 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>

  • Страница 33 из 33
  • «
  • 1
  • 2
  • 31
  • 32
  • 33
Поиск:
Новый ответ
Имя:
Текст сообщения: