Отзывы и предложения к софту от AleXStam
  • Страница 32 из 32
  • «
  • 1
  • 2
  • 30
  • 31
  • 32
Поговорим о...
--- Проверка изображения ---
petrography/:2650 Src: http://localhost:1313/hugo/docs/manuals/laboratory/petrography/images/cat.png
petrography/:2651 Natural размер: 1280 × 280
petrography/:2652 Displayed размер: 791 × 173
petrography/:2653 Complete: true
petrography/:2672 Минимум: 400×300
petrography/:2673 Достаточно большой: false
petrography/:2677 ❌ Отфильтровано: оригинал слишком маленький для увеличения
<!-- Lightbox для изображений в статьях -->
<script>
class ContentLightbox {
constructor() {
this.modal = null;
this.modalImage = null;
this.caption = null;
this.images = [];
this.currentIndex = 0;
this.debug = true;
this.initialized = false;
this.init();
}

init() {
this.createModal();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => this.setup(), 500);
});
} else {
setTimeout(() => this.setup(), 500);
}
}

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.innerHTML = '×';
closeBtn.setAttribute('aria-label', 'Закрыть');
closeBtn.style.cssText = `
position: absolute;
top: -45px;
right: 0;
background: rgba(255, 255, 255, 0.15);
border: none;
color: white;
font-size: 32px;
cursor: pointer;
line-height: 1;
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);
`;
closeBtn.addEventListener('mouseenter', () => {
closeBtn.style.background = 'rgba(255, 255, 255, 0.25)';
closeBtn.style.transform = 'scale(1.1)';
});
closeBtn.addEventListener('mouseleave', () => {
closeBtn.style.background = 'rgba(255, 255, 255, 0.15)';
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: 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) return;
this.initialized = true;

const allLightboxImages = document.querySelectorAll('img[data-lightbox="true"]');

if (this.debug) {
console.log('=== DEBUG LIGHTBOX ===');
console.log('Всего изображений с data-lightbox="true":', allLightboxImages.length);
}

const imagePromises = Array.from(allLightboxImages).map(img => {
return new Promise((resolve) => {
if (img.complete && img.naturalWidth > 0) {
resolve(img);
} else if (img.complete && img.naturalWidth === 0) {
if (this.debug) console.log('⚠️ Изображение не загружено:', img.src);
resolve(null);
} else {
img.onload = () => resolve(img);
img.onerror = () => {
if (this.debug) console.log('❌ Ошибка загрузки изображения:', img.src);
resolve(null);
};
setTimeout(() => {
if (img.naturalWidth === 0) {
if (this.debug) console.log('⚠️ Таймаут загрузки изображения:', img.src);
}
resolve(img);
}, 1000);
}
});
});

Promise.all(imagePromises).then(loadedImages => {
const validImages = loadedImages.filter(img => img !== null);

if (this.debug) {
console.log('Загруженных изображений:', validImages.length);
console.log('Не загруженных:', loadedImages.length - validImages.length);
}

this.images = validImages.filter(img => {
const naturalWidth = img.naturalWidth || 0;
const naturalHeight = img.naturalHeight || 0;
const displayedWidth = img.offsetWidth || img.width || 0;
const displayedHeight = img.offsetHeight || img.height || 0;

if (this.debug) {
console.log('--- Проверка изображения ---');
console.log('Src:', img.src.substring(0, 100));
console.log('Natural размер:', naturalWidth, '×', naturalHeight);
console.log('Displayed размер:', displayedWidth, '×', displayedHeight);
console.log('Complete:', img.complete);
console.log('Сжатие:', naturalWidth > 0 ? Math.round((displayedWidth / naturalWidth) * 100) + '%' : 'N/A');
}

if (naturalWidth === 0 && naturalHeight === 0) {
if (this.debug) console.log('❌ Пропущено: размеры 0×0 (еще не загружено)');
return false;
}

const isNotIcon = !this.isIcon(img);
if (!isNotIcon) {
if (this.debug) console.log('❌ Отфильтровано: иконка');
return false;
}

// УЛУЧШЕННАЯ ЛОГИКА: учитываем и оригинальные, и отображаемые размеры
const MIN_WIDTH = 400;
const MIN_HEIGHT = 200;
const MIN_DISPLAYED_WIDTH = 300; // Минимальная отображаемая ширина
const MIN_DISPLAYED_HEIGHT = 150; // Минимальная отображаемая высота

// Проверяем пропорции оригинала
const naturalAspectRatio = naturalWidth / naturalHeight;
const isNaturalHorizontal = naturalAspectRatio > 3;
const isNaturalVeryHorizontal = naturalAspectRatio > 5;

// Проверяем пропорции отображаемого
const displayedAspectRatio = displayedWidth / displayedHeight;
const isDisplayedHorizontal = displayedAspectRatio > 3;
const isDisplayedVeryHorizontal = displayedAspectRatio > 5;

// Коэффициент сжатия (насколько изображение уменьшилось для отображения)
const compressionRatio = naturalWidth > 0 ? displayedWidth / naturalWidth : 1;
const isCompressed = compressionRatio < 0.9; // Сжато более чем на 10%

if (this.debug) {
console.log(`Пропорции оригинала: ${naturalAspectRatio.toFixed(2)}:1`);
console.log(`Пропорции отображаемого: ${displayedAspectRatio.toFixed(2)}:1`);
console.log(`Сжатие: ${Math.round(compressionRatio * 100)}% (${isCompressed ? 'сильно сжато' : 'почти оригинал'})`);
}

// Проверка №1: Достаточно ли большой оригинал?
let isNaturalLargeEnough;

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;
}

// Проверка №2: Достаточно ли большой отображаемый размер?
let isDisplayedLargeEnough;

if (isDisplayedVeryHorizontal) {
// Для очень горизонтальных отображаемых
isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.2 && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.8;
} else if (isDisplayedHorizontal) {
// Для горизонтальных отображаемых
isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.9;
} else {
// Для обычных отображаемых
isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT;
}

// Итоговая проверка: изображение можно увеличивать если:
// 1. Оригинал достаточно большой ИЛИ
// 2. Отображаемый размер достаточно большой (особенно если изображение сжато)
const isLargeEnough = isNaturalLargeEnough || (isCompressed && isDisplayedLargeEnough);

if (this.debug) {
console.log(`Оригинал достаточно большой: ${isNaturalLargeEnough}`);
console.log(`Отображаемый достаточно большой: ${isDisplayedLargeEnough}`);
console.log(`Сжатое изображение: ${isCompressed}`);
console.log(`Итог: достаточно большой = ${isLargeEnough}`);
}

if (!isLargeEnough) {
if (this.debug) console.log('❌ Отфильтровано: слишком маленькое для увеличения');
return false;
}

// Проверка для документации
const currentPath = window.location.pathname.toLowerCase();
const isDocsPage = currentPath.includes('/manuals/') ||
currentPath.includes('/docs/') ||
currentPath.includes('/laboratory/');

if (isDocsPage) {
if (this.debug) console.log('✅ Страница документации - разрешаем увеличение');
return true;
}

// Для обычных страниц - проверяем, полностью ли видно
const isNotFullyVisible = !this.isFullyVisible(img, displayedWidth, displayedHeight);

if (!isNotFullyVisible) {
if (this.debug) console.log('❌ Отфильтровано: полностью видно на обычной странице');
return false;
}

if (this.debug) console.log('✅ Изображение доступно для увеличения');
return true;
});

if (this.debug) {
console.log('=== ИТОГО ===');
console.log('Доступно для увеличения:', this.images.length, 'из', validImages.length);
console.log('Процент:', Math.round((this.images.length / validImages.length) * 100) + '%');
}

allLightboxImages.forEach((img) => {
const isZoomable = this.images.includes(img);

if (isZoomable) {
img.style.cursor = 'zoom-in';
img.classList.add('is-zoomable');

img.addEventListener('mouseenter', () => {
img.style.opacity = '0.92';
img.style.transform = 'translateY(-3px)';
});

img.addEventListener('mouseleave', () => {
img.style.opacity = '1';
img.style.transform = 'translateY(0)';
});

img.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const filteredIndex = this.images.indexOf(img);
if (filteredIndex !== -1) {
this.currentIndex = filteredIndex;
this.open(img.dataset.originalSrc || img.src, img.alt || img.title || '');
}
});

if (this.debug) {
console.log(`✅ ${img.src.substring(0, 80)}... - МОЖНО увеличивать`);
}
} else {
img.style.cursor = 'default';
img.classList.remove('content-image--zoomable');
img.classList.add('not-zoomable');

img.onclick = null;

if (this.debug && img.naturalWidth > 0) {
console.log(`❌ ${img.src.substring(0, 80)}... - НЕЛЬЗЯ увеличивать`);
}
}
});

setTimeout(() => {
this.checkLateLoadingImages(allLightboxImages);
}, 3000);

}).catch(error => {
console.error('Ошибка при загрузке изображений:', error);
});
}

checkLateLoadingImages(allImages) {
const lateImages = Array.from(allImages).filter(img => {
const naturalWidth = img.naturalWidth || 0;
const naturalHeight = img.naturalHeight || 0;
const displayedWidth = img.offsetWidth || img.width || 0;
const displayedHeight = img.offsetHeight || img.height || 0;

if (!this.images.includes(img) &&
naturalWidth > 0 &&
naturalHeight > 0 &&
!this.isIcon(img)) {

// Та же улучшенная логика проверки
const MIN_WIDTH = 400;
const MIN_HEIGHT = 200;
const MIN_DISPLAYED_WIDTH = 300;
const MIN_DISPLAYED_HEIGHT = 150;

const naturalAspectRatio = naturalWidth / naturalHeight;
const isNaturalHorizontal = naturalAspectRatio > 3;
const isNaturalVeryHorizontal = naturalAspectRatio > 5;

const displayedAspectRatio = displayedWidth / displayedHeight;
const isDisplayedHorizontal = displayedAspectRatio > 3;
const isDisplayedVeryHorizontal = displayedAspectRatio > 5;

const compressionRatio = naturalWidth > 0 ? displayedWidth / naturalWidth : 1;
const isCompressed = compressionRatio < 0.9;

let isNaturalLargeEnough;
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 (isDisplayedVeryHorizontal) {
isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.2 && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.8;
} else if (isDisplayedHorizontal) {
isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.9;
} else {
isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT;
}

return isNaturalLargeEnough || (isCompressed && isDisplayedLargeEnough);
}
return false;
});

if (lateImages.length > 0 && this.debug) {
console.log('=== ПОЗДНЕЕ ОБНАРУЖЕНИЕ ===');
console.log('Найдено поздно загруженных изображений:', lateImages.length);
}

lateImages.forEach(img => {
if (!this.images.includes(img)) {
this.images.push(img);

img.style.cursor = 'zoom-in';
img.classList.add('is-zoomable');
img.classList.remove('not-zoomable');

img.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const filteredIndex = this.images.indexOf(img);
if (filteredIndex !== -1) {
this.currentIndex = filteredIndex;
this.open(img.dataset.originalSrc || img.src, img.alt || img.title || '');
}
});

if (this.debug) {
console.log(`✅ Позднее обнаружение: ${img.src.substring(0, 80)}...`);
}
}
});
}

isIcon(img) {
const src = (img.src || '').toLowerCase();
const alt = (img.alt || '').toLowerCase();
const classes = (img.className || '').toLowerCase();

const iconKeywords = [
'icon', 'logo', 'avatar', 'favicon', 'sprite',
'emoji', 'svg', 'ico', 'png-16', 'png-32', 'png-64',
'icon-', '-icon'
];

const naturalWidth = img.naturalWidth || img.offsetWidth || 0;
const naturalHeight = img.naturalHeight || img.offsetHeight || 0;

const isVerySmall = naturalWidth < 50 && naturalHeight < 50;
const isSquareAndSmall = Math.abs(naturalWidth - naturalHeight) < 10 && naturalWidth < 100;

const hasIconKeyword = iconKeywords.some(keyword =>
src.includes(keyword) || alt.includes(keyword) || classes.includes(keyword)
);

return isVerySmall || (isSquareAndSmall && hasIconKeyword);
}

isFullyVisible(img, displayedWidth = null, displayedHeight = null) {
const currentPath = window.location.pathname.toLowerCase();
const isDocsPage = currentPath.includes('/manuals/') ||
currentPath.includes('/docs/') ||
currentPath.includes('/laboratory/');

if (isDocsPage) {
return false;
}

const imgWidth = displayedWidth || img.offsetWidth || img.naturalWidth;
const container = img.parentElement;
if (!container) return false;

const containerWidth = container.clientWidth;
return imgWidth < containerWidth * 0.90;
}

open(src, caption) {
if (this.debug) {
console.log('Открываем lightbox:', src.substring(0, 100));
}

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();
}, 100);

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.25s ease;
}

.content-image.is-zoomable {
cursor: zoom-in;
}

.content-image.not-zoomable {
cursor: default;
}

.content-image--shadow.is-zoomable {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.content-image:not(.is-zoomable),
.content-image.not-zoomable {
box-shadow: none !important;
}

.content-image.is-zoomable:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
opacity: 0.95;
}

.content-image.not-zoomable:hover {
transform: none !important;
opacity: 1 !important;
box-shadow: none !important;
}

.content-image__caption {
text-align: center;
font-size: 0.875rem;
color: #666;
margin-top: 0.5rem;
line-height: 1.4;
font-style: italic;
}

.content-image__wrapper {
margin: 1.5rem 0;
text-align: center;
}

@media (max-width: 768px) {
.content-image {
margin: 1rem auto;
border-radius: 3px;
}

.content-image--shadow.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: 15px;
right: 15px;
width: 32px;
height: 32px;
font-size: 28px;
}
}

@media (hover: none) {
.content-image:hover {
transform: none !important;
}
}
`;
document.head.appendChild(style);
});

let scrollCheckTimeout = null;
window.addEventListener('scroll', () => {
if (scrollCheckTimeout) clearTimeout(scrollCheckTimeout);
scrollCheckTimeout = setTimeout(() => {
if (window.contentLightbox && window.contentLightbox.initialized) {
const visibleImages = document.querySelectorAll('img[data-lightbox="true"]:not(.is-zoomable):not(.not-zoomable)');
if (visibleImages.length > 0) {
window.contentLightbox.checkLateLoadingImages(visibleImages);
}
}
}, 500);
});

document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
setTimeout(() => {
if (window.contentLightbox) {
window.contentLightbox.setup();
}
}, 1000);
}
});
</script>

Добавлено (2025-12-19, 11:31)
---------------------------------------------
<!-- Lightbox для изображений в статьях -->
<script>
class ContentLightbox {
constructor() {
this.modal = null;
this.modalImage = null;
this.caption = null;
this.images = [];
this.currentIndex = 0;
this.debug = true;
this.initialized = false;
this.observer = null;
this.pendingImages = new Set(); // Изображения ожидающие загрузки
this.init();
}

init() {
this.createModal();
this.setupIntersectionObserver();

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => this.setup(), 500);
});
} else {
setTimeout(() => this.setup(), 500);
}
}

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.innerHTML = '×';
closeBtn.setAttribute('aria-label', 'Закрыть');
closeBtn.style.cssText = `
position: absolute;
top: -45px;
right: 0;
background: rgba(255, 255, 255, 0.15);
border: none;
color: white;
font-size: 32px;
cursor: pointer;
line-height: 1;
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);
`;
closeBtn.addEventListener('mouseenter', () => {
closeBtn.style.background = 'rgba(255, 255, 255, 0.25)';
closeBtn.style.transform = 'scale(1.1)';
});
closeBtn.addEventListener('mouseleave', () => {
closeBtn.style.background = 'rgba(255, 255, 255, 0.15)';
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: 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();
}
});
}

setupIntersectionObserver() {
if ('IntersectionObserver' in window) {
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
this.observer.unobserve(img);

if (img.dataset.originalSrc) {
// Для lazy loading изображений
if (!img.src || img.src !== img.dataset.originalSrc) {
img.src = img.dataset.originalSrc;
}
}

// Проверяем изображение после появления в viewport
this.checkSingleImage(img);
}
});
}, {
rootMargin: '200px', // Начинаем загружать за 200px до появления
threshold: 0.1
});
}
}

setup() {
if (this.initialized) return;
this.initialized = true;

const allLightboxImages = document.querySelectorAll('img[data-lightbox="true"]');

if (this.debug) {
console.log('=== DEBUG LIGHTBOX ===');
console.log('Всего изображений с data-lightbox="true":', allLightboxImages.length);
}

// Разделяем изображения на видимые и невидимые
const visibleImages = [];
const invisibleImages = [];

allLightboxImages.forEach(img => {
const rect = img.getBoundingClientRect();
const isVisible = (
rect.top < window.innerHeight &&
rect.bottom > 0 &&
rect.left < window.innerWidth &&
rect.right > 0
);

if (isVisible) {
visibleImages.push(img);
} else {
invisibleImages.push(img);
}
});

if (this.debug) {
console.log('Видимых изображений:', visibleImages.length);
console.log('Невидимых изображений:', invisibleImages.length);
}

// Обрабатываем видимые изображения сразу
this.processImageBatch(visibleImages, 'visible');

// Начинаем отслеживать невидимые изображения
invisibleImages.forEach(img => {
if (this.observer) {
this.observer.observe(img);
this.pendingImages.add(img);
} else {
// Fallback для старых браузеров
this.processImageBatch([img], 'invisible-fallback');
}
});

// Также запускаем проверку всех изображений через 3 секунды
setTimeout(() => {
this.checkAllImages(allLightboxImages);
}, 3000);

// И через 10 секунд на всякий случай
setTimeout(() => {
this.finalCheck(allLightboxImages);
}, 10000);
}

processImageBatch(images, batchType = '') {
if (this.debug && batchType) {
console.log(`--- Обработка ${batchType} изображений (${images.length}) ---`);
}

images.forEach(img => {
if (this.pendingImages.has(img)) {
this.pendingImages.delete(img);
}

this.checkSingleImage(img);
});
}

checkSingleImage(img) {
return new Promise((resolve) => {
const checkImage = () => {
const naturalWidth = img.naturalWidth || 0;
const naturalHeight = img.naturalHeight || 0;
const displayedWidth = img.offsetWidth || img.width || 0;
const displayedHeight = img.offsetHeight || img.height || 0;

if (this.debug) {
console.log('--- Проверка одного изображения ---');
console.log('Src:', img.src.substring(0, 100));
console.log('Natural размер:', naturalWidth, '×', naturalHeight);
console.log('Displayed размер:', displayedWidth, '×', displayedHeight);
console.log('Complete:', img.complete);
}

// Если изображение еще не загружено, ждем
if (!img.complete || (naturalWidth === 0 && displayedWidth === 0)) {
if (this.debug) console.log('🕐 Изображение еще не загружено, ждем...');

if (!img.complete) {
img.addEventListener('load', () => {
setTimeout(() => checkImage(), 100);
});
img.addEventListener('error', () => {
if (this.debug) console.log('❌ Ошибка загрузки изображения');
resolve(false);
});
} else {
setTimeout(() => checkImage(), 500);
}
return;
}

if (naturalWidth === 0 && naturalHeight === 0 && displayedWidth === 0 && displayedHeight === 0) {
if (this.debug) console.log('❌ Размеры 0×0, пропускаем');
resolve(false);
return;
}

const isNotIcon = !this.isIcon(img);
if (!isNotIcon) {
if (this.debug) console.log('❌ Отфильтровано: иконка');
this.markAsNotZoomable(img);
resolve(false);
return;
}

// Логика проверки размеров (та же что и раньше)
const MIN_WIDTH = 400;
const MIN_HEIGHT = 200;
const MIN_DISPLAYED_WIDTH = 300;
const MIN_DISPLAYED_HEIGHT = 150;

const naturalAspectRatio = naturalWidth / naturalHeight;
const isNaturalHorizontal = naturalAspectRatio > 3;
const isNaturalVeryHorizontal = naturalAspectRatio > 5;

const displayedAspectRatio = displayedWidth / displayedHeight;
const isDisplayedHorizontal = displayedAspectRatio > 3;
const isDisplayedVeryHorizontal = displayedAspectRatio > 5;

const compressionRatio = naturalWidth > 0 ? displayedWidth / naturalWidth : 1;
const isCompressed = compressionRatio < 0.9;

let isNaturalLargeEnough;
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 (isDisplayedVeryHorizontal) {
isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.2 && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.8;
} else if (isDisplayedHorizontal) {
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);

if (!isLargeEnough) {
if (this.debug) console.log('❌ Отфильтровано: слишком маленькое');
this.markAsNotZoomable(img);
resolve(false);
return;
}

const currentPath = window.location.pathname.toLowerCase();
const isDocsPage = currentPath.includes('/manuals/') ||
currentPath.includes('/docs/') ||
currentPath.includes('/laboratory/');

if (!isDocsPage) {
const isNotFullyVisible = !this.isFullyVisible(img, displayedWidth, displayedHeight);
if (!isNotFullyVisible) {
if (this.debug) console.log('❌ Отфильтровано: полностью видно на обычной странице');
this.markAsNotZoomable(img);
resolve(false);
return;
}
}

// Изображение прошло все проверки
if (!this.images.includes(img)) {
this.images.push(img);
}

this.markAsZoomable(img);

if (this.debug) {
console.log('✅ Изображение доступно для увеличения');
}

resolve(true);
};

checkImage();
});
}

markAsZoomable(img) {
img.style.cursor = 'zoom-in';
img.classList.add('is-zoomable');
img.classList.remove('not-zoomable');

// Удаляем старые обработчики если есть
const newImg = img.cloneNode(true);
img.parentNode.replaceChild(newImg, img);

// Добавляем новые обработчики
newImg.addEventListener('mouseenter', () => {
newImg.style.opacity = '0.92';
newImg.style.transform = 'translateY(-3px)';
});

newImg.addEventListener('mouseleave', () => {
newImg.style.opacity = '1';
newImg.style.transform = 'translateY(0)';
});

newImg.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const filteredIndex = this.images.indexOf(img);
if (filteredIndex !== -1) {
this.currentIndex = filteredIndex;
this.open(img.dataset.originalSrc || img.src, img.alt || img.title || '');
}
});

// Обновляем data-lightbox чтобы он остался
newImg.setAttribute('data-lightbox', 'true');
if (img.dataset.originalSrc) {
newImg.setAttribute('data-original-src', img.dataset.originalSrc);
}

return newImg;
}

markAsNotZoomable(img) {
img.style.cursor = 'default';
img.classList.remove('content-image--zoomable');
img.classList.add('not-zoomable');
img.onclick = null;
}

checkAllImages(allImages) {
if (this.debug) {
console.log('=== ПРОВЕРКА ВСЕХ ИЗОБРАЖЕНИЙ ЧЕРЕЗ 3С ===');
}

const uncheckedImages = Array.from(allImages).filter(img =>
!this.images.includes(img) &&
!img.classList.contains('not-zoomable')
);

if (uncheckedImages.length > 0) {
this.processImageBatch(uncheckedImages, 'late-batch-3s');
}
}

finalCheck(allImages) {
if (this.debug) {
console.log('=== ФИНАЛЬНАЯ ПРОВЕРКА ЧЕРЕЗ 10С ===');
console.log('Осталось в ожидании:', this.pendingImages.size);
}

// Принудительно проверяем все оставшиеся изображения
Array.from(allImages).forEach(img => {
if (!this.images.includes(img) && !img.classList.contains('not-zoomable')) {
this.checkSingleImage(img);
}
});

// Очищаем pending
this.pendingImages.clear();
}

isIcon(img) {
const src = (img.src || '').toLowerCase();
const alt = (img.alt || '').toLowerCase();
const classes = (img.className || '').toLowerCase();

const iconKeywords = [
'icon', 'logo', 'avatar', 'favicon', 'sprite',
'emoji', 'svg', 'ico', 'png-16', 'png-32', 'png-64',
'icon-', '-icon'
];

const naturalWidth = img.naturalWidth || img.offsetWidth || 0;
const naturalHeight = img.naturalHeight || img.offsetHeight || 0;

const isVerySmall = naturalWidth < 50 && naturalHeight < 50;
const isSquareAndSmall = Math.abs(naturalWidth - naturalHeight) < 10 && naturalWidth < 100;

const hasIconKeyword = iconKeywords.some(keyword =>
src.includes(keyword) || alt.includes(keyword) || classes.includes(keyword)
);

return isVerySmall || (isSquareAndSmall && hasIconKeyword);
}

isFullyVisible(img, displayedWidth = null, displayedHeight = null) {
const currentPath = window.location.pathname.toLowerCase();
const isDocsPage = currentPath.includes('/manuals/') ||
currentPath.includes('/docs/') ||
currentPath.includes('/laboratory/');

if (isDocsPage) {
return false;
}

const imgWidth = displayedWidth || img.offsetWidth || img.naturalWidth;
const container = img.parentElement;
if (!container) return false;

const containerWidth = container.clientWidth;
return imgWidth < containerWidth * 0.90;
}

open(src, caption) {
if (this.debug) {
console.log('Открываем lightbox:', src.substring(0, 100));
}

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();
}, 100);

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.25s ease;
}

.content-image.is-zoomable {
cursor: zoom-in;
}

.content-image.not-zoomable {
cursor: default;
}

.content-image--shadow.is-zoomable {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.content-image:not(.is-zoomable),
.content-image.not-zoomable {
box-shadow: none !important;
}

.content-image.is-zoomable:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
opacity: 0.95;
}

.content-image.not-zoomable:hover {
transform: none !important;
opacity: 1 !important;
box-shadow: none !important;
}

.content-image__caption {
text-align: center;
font-size: 0.875rem;
color: #666;
margin-top: 0.5rem;
line-height: 1.4;
font-style: italic;
}

.content-image__wrapper {
margin: 1.5rem 0;
text-align: center;
}

/* Стили для lazy loading */
.content-image[loading="lazy"] {
opacity: 0;
transition: opacity 0.3s ease;
}

.content-image[loading="lazy"].loaded {
opacity: 1;
}

@media (max-width: 768px) {
.content-image {
margin: 1rem auto;
border-radius: 3px;
}

.content-image--shadow.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: 15px;
right: 15px;
width: 32px;
height: 32px;
font-size: 28px;
}
}

@media (hover: none) {
.content-image:hover {
transform: none !important;
}
}
`;
document.head.appendChild(style);
});

// Автоматическая загрузка lazy images при скролле
let lazyLoadTimeout;
window.addEventListener('scroll', () => {
if (lazyLoadTimeout) clearTimeout(lazyLoadTimeout);
lazyLoadTimeout = setTimeout(() => {
// Находим все lazy изображения рядом с viewport
const lazyImages = document.querySelectorAll('img[data-lightbox="true"][loading="lazy"]');
lazyImages.forEach(img => {
const rect = img.getBoundingClientRect();
const isNearViewport = (
rect.top < window.innerHeight + 500 &&
rect.bottom > -500
);

if (isNearViewport && !img.classList.contains('loaded')) {
img.classList.add('loaded');
if (window.contentLightbox) {
window.contentLightbox.checkSingleImage(img);
}
}
});
}, 250);
});

// Принудительная проверка при изменении размера окна
window.addEventListener('resize', () => {
setTimeout(() => {
if (window.contentLightbox) {
const allImages = document.querySelectorAll('img[data-lightbox="true"]');
window.contentLightbox.checkAllImages(allImages);
}
}, 500);
});

// Проверка при возвращении на вкладку
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
setTimeout(() => {
if (window.contentLightbox) {
const allImages = document.querySelectorAll('img[data-lightbox="true"]');
window.contentLightbox.checkAllImages(allImages);
}
}, 1000);
}
});
</script>

Добавлено (2025-12-19, 11:37)
---------------------------------------------
<!-- Lightbox для изображений в статьях -->
<script>
class ContentLightbox {
constructor() {
this.modal = null;
this.modalImage = null;
this.caption = null;
this.images = [];
this.currentIndex = 0;
this.debug = true;
this.initialized = false;
this.init();
}

init() {
this.createModal();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => this.setup(), 100);
});
} else {
setTimeout(() => this.setup(), 100);
}
}

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;
display: flex;
flex-direction: column;
align-items: center;
`;

// ИСПРАВЛЕННАЯ КНОПКА ЗАКРЫТИЯ
const closeBtn = document.createElement('button');
closeBtn.className = 'content-lightbox__close';
closeBtn.innerHTML = '×';
closeBtn.setAttribute('aria-label', 'Закрыть');
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;
line-height: 36px;
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;
margin: 0;
`;
closeBtn.addEventListener('mouseenter', () => {
closeBtn.style.background = 'rgba(255, 255, 255, 0.25)';
closeBtn.style.transform = 'scale(1.1)';
});
closeBtn.addEventListener('mouseleave', () => {
closeBtn.style.background = 'rgba(255, 255, 255, 0.15)';
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: 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) return;
this.initialized = true;

const allLightboxImages = document.querySelectorAll('img[data-lightbox="true"]');

if (this.debug) {
console.log('=== DEBUG LIGHTBOX ===');
console.log('Всего изображений с data-lightbox="true":', allLightboxImages.length);
}

// Обрабатываем ВСЕ изображения сразу
this.processAllImages(allLightboxImages);

// Дополнительная проверка через 1 секунду для lazy images
setTimeout(() => {
this.checkLazyImages(allLightboxImages);
}, 1000);

// Еще одна проверка через 3 секунды
setTimeout(() => {
this.finalCheck(allLightboxImages);
}, 3000);
}

processAllImages(allImages) {
const imagePromises = Array.from(allImages).map(img => {
return new Promise((resolve) => {
// Если изображение уже загружено
if (img.complete && img.naturalWidth > 0) {
this.processImage(img);
resolve(true);
}
// Если изображение еще не загружено, но имеет src
else if (img.src || img.dataset.originalSrc) {
// Ждем загрузки
const onLoad = () => {
img.removeEventListener('load', onLoad);
img.removeEventListener('error', onError);
setTimeout(() => {
this.processImage(img);
resolve(true);
}, 100);
};

const onError = () => {
img.removeEventListener('load', onLoad);
img.removeEventListener('error', onError);
if (this.debug) console.log('❌ Ошибка загрузки:', img.src);
resolve(false);
};

img.addEventListener('load', onLoad);
img.addEventListener('error', onError);

// Если изображение lazy, загружаем его
if (img.loading === 'lazy' && img.dataset.originalSrc && !img.src) {
img.src = img.dataset.originalSrc;
}
} else {
resolve(false);
}
});
});

Promise.all(imagePromises).then(results => {
const successful = results.filter(r => r).length;
if (this.debug) {
console.log(`Обработано изображений: ${successful}/${allImages.length}`);
}
});
}

processImage(img) {
const naturalWidth = img.naturalWidth || 0;
const naturalHeight = img.naturalHeight || 0;
const displayedWidth = img.offsetWidth || img.width || 0;
const displayedHeight = img.offsetHeight || img.height || 0;

if (this.debug) {
console.log('--- Обработка изображения ---');
console.log('Src:', img.src.substring(0, 100));
console.log('Natural:', naturalWidth, '×', naturalHeight);
console.log('Displayed:', displayedWidth, '×', displayedHeight);
}

// Проверяем минимальные размеры
if (naturalWidth === 0 && displayedWidth === 0) {
if (this.debug) console.log('🕐 Изображение еще не загрузилось, откладываем');
return false;
}

const isNotIcon = !this.isIcon(img);
if (!isNotIcon) {
this.markAsNotZoomable(img);
return false;
}

// УЛУЧШЕННАЯ ПРОВЕРКА РАЗМЕРОВ
const MIN_WIDTH = 400;
const MIN_HEIGHT = 200;
const MIN_DISPLAYED_WIDTH = 300;
const MIN_DISPLAYED_HEIGHT = 150;

const naturalAspectRatio = naturalWidth / naturalHeight;
const isNaturalHorizontal = naturalAspectRatio > 3;
const isNaturalVeryHorizontal = naturalAspectRatio > 5;

const displayedAspectRatio = displayedWidth / displayedHeight;
const isDisplayedHorizontal = displayedAspectRatio > 3;
const isDisplayedVeryHorizontal = displayedAspectRatio > 5;

const compressionRatio = naturalWidth > 0 ? displayedWidth / naturalWidth : 1;
const isCompressed = compressionRatio < 0.9;

let isNaturalLargeEnough;
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 (isDisplayedVeryHorizontal) {
isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.2 && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.8;
} else if (isDisplayedHorizontal) {
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);

if (!isLargeEnough) {
this.markAsNotZoomable(img);
return false;
}

const currentPath = window.location.pathname.toLowerCase();
const isDocsPage = currentPath.includes('/manuals/') ||
currentPath.includes('/docs/') ||
currentPath.includes('/laboratory/');

if (!isDocsPage) {
const isNotFullyVisible = !this.isFullyVisible(img, displayedWidth, displayedHeight);
if (!isNotFullyVisible) {
this.markAsNotZoomable(img);
return false;
}
}

// Изображение прошло все проверки
if (!this.images.includes(img)) {
this.images.push(img);
}

this.markAsZoomable(img);

if (this.debug) {
console.log('✅ Доступно для увеличения');
}

return true;
}

markAsZoomable(img) {
img.style.cursor = 'zoom-in';
img.classList.add('is-zoomable');
img.classList.remove('not-zoomable');

// Удаляем старые обработчики
const oldOnClick = img.onclick;
img.onclick = null;

// Добавляем новые обработчики
img.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const filteredIndex = this.images.indexOf(img);
if (filteredIndex !== -1) {
this.currentIndex = filteredIndex;
this.open(img.dataset.originalSrc || img.src, img.alt || img.title || '');
}
});

// Эффекты при наведении
img.addEventListener('mouseenter', () => {
img.style.opacity = '0.92';
img.style.transform = 'translateY(-3px)';
});

img.addEventListener('mouseleave', () => {
img.style.opacity = '1';
img.style.transform = 'translateY(0)';
});
}

markAsNotZoomable(img) {
img.style.cursor = 'default';
img.classList.remove('content-image--zoomable');
img.classList.add('not-zoomable');
img.onclick = null;
}

checkLazyImages(allImages) {
const lazyImages = Array.from(allImages).filter(img =>
(img.loading === 'lazy' || !img.complete) &&
!this.images.includes(img) &&
!img.classList.contains('not-zoomable')
);

if (lazyImages.length > 0 && this.debug) {
console.log('Проверка lazy images:', lazyImages.length);
}

lazyImages.forEach(img => {
this.processImage(img);
});
}

finalCheck(allImages) {
const unprocessedImages = Array.from(allImages).filter(img =>
!this.images.includes(img) &&
!img.classList.contains('not-zoomable')
);

if (unprocessedImages.length > 0 && this.debug) {
console.log('Финальная проверка:', unprocessedImages.length, 'изображений');
}

unprocessedImages.forEach(img => {
this.processImage(img);
});

if (this.debug) {
console.log('=== ИТОГО ===');
console.log('Доступно для увеличения:', this.images.length, 'из', allImages.length);
}
}

isIcon(img) {
const src = (img.src || '').toLowerCase();
const alt = (img.alt || '').toLowerCase();
const classes = (img.className || '').toLowerCase();

const iconKeywords = [
'icon', 'logo', 'avatar', 'favicon', 'sprite',
'emoji', 'svg', 'ico', 'png-16', 'png-32', 'png-64',
'icon-', '-icon'
];

const naturalWidth = img.naturalWidth || img.offsetWidth || 0;
const naturalHeight = img.naturalHeight || img.offsetHeight || 0;

const isVerySmall = naturalWidth < 50 && naturalHeight < 50;
const isSquareAndSmall = Math.abs(naturalWidth - naturalHeight) < 10 && naturalWidth < 100;

const hasIconKeyword = iconKeywords.some(keyword =>
src.includes(keyword) || alt.includes(keyword) || classes.includes(keyword)
);

return isVerySmall || (isSquareAndSmall && hasIconKeyword);
}

isFullyVisible(img, displayedWidth = null, displayedHeight = null) {
const currentPath = window.location.pathname.toLowerCase();
const isDocsPage = currentPath.includes('/manuals/') ||
currentPath.includes('/docs/') ||
currentPath.includes('/laboratory/');

if (isDocsPage) {
return false;
}

const imgWidth = displayedWidth || img.offsetWidth || img.naturalWidth;
const container = img.parentElement;
if (!container) return false;

const containerWidth = container.clientWidth;
return imgWidth < containerWidth * 0.90;
}

open(src, caption) {
if (this.debug) {
console.log('Открываем lightbox:', src.substring(0, 100));
}

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();
}, 50);

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.25s ease;
}

.content-image.is-zoomable {
cursor: zoom-in;
}

.content-image.not-zoomable {
cursor: default;
}

.content-image--shadow.is-zoomable {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.content-image:not(.is-zoomable),
.content-image.not-zoomable {
box-shadow: none !important;
}

.content-image.is-zoomable:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
opacity: 0.95;
}

.content-image.not-zoomable:hover {
transform: none !important;
opacity: 1 !important;
box-shadow: none !important;
}

.content-image__caption {
text-align: center;
font-size: 0.875rem;
color: #666;
margin-top: 0.5rem;
line-height: 1.4;
font-style: italic;
}

.content-image__wrapper {
margin: 1.5rem 0;
text-align: center;
}

/* Стили для lazy loading */
.content-image[loading="lazy"] {
min-height: 100px;
background: #f5f5f5;
}

@media (max-width: 768px) {
.content-image {
margin: 1rem auto;
border-radius: 3px;
}

.content-image--shadow.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: 10px;
right: 10px;
width: 36px;
height: 36px;
font-size: 30px;
}
}

@media (hover: none) {
.content-image:hover {
transform: none !important;
}
}
`;
document.head.appendChild(style);
});

// Проверка при скролле для lazy images
window.addEventListener('scroll', () => {
setTimeout(() => {
if (window.contentLightbox && window.contentLightbox.initialized) {
const allImages = document.querySelectorAll('img[data-lightbox="true"]');
window.contentLightbox.checkLazyImages(allImages);
}
}, 500);
});

// Проверка при изменении размера окна
window.addEventListener('resize', () => {
setTimeout(() => {
if (window.contentLightbox) {
const allImages = document.querySelectorAll('img[data-lightbox="true"]');
window.contentLightbox.finalCheck(allImages);
}
}, 300);
});
</script>

Добавлено (2025-12-19, 11:45)
---------------------------------------------
<!-- Lightbox для изображений в статьях -->
<script>
class ContentLightbox {
constructor() {
// ... весь JavaScript код остается без изменений ...
// Просто вставьте предыдущий JavaScript код полностью
}
}

// Инициализация
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
window.contentLightbox = new ContentLightbox();
}, 50);

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.25s ease;
}

/* Стили для изображений, которые можно увеличивать */
.content-image.is-zoomable {
cursor: zoom-in;
}

/* Для изображений, которые нельзя увеличивать */
.content-image.not-zoomable {
cursor: default;
}

/* Тень только для zoomable изображений */
.content-image--shadow.is-zoomable {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

/* Маленькие/не-zoomable изображения - без тени */
.content-image:not(.is-zoomable),
.content-image.not-zoomable {
box-shadow: none !important;
}

/* Эффект при наведении на zoomable изображения */
.content-image.is-zoomable:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
opacity: 0.95;
}

/* Отключаем hover эффекты для не-zoomable изображений */
.content-image.not-zoomable:hover {
transform: none !important;
opacity: 1 !important;
box-shadow: none !important;
}

/* Подпись к изображениям */
.content-image__caption {
text-align: center;
font-size: 0.875rem;
color: #666;
margin-top: 0.5rem;
line-height: 1.4;
font-style: italic;
}

/* Обертка для изображений с подписью */
.content-image__wrapper {
margin: 1.5rem 0;
text-align: center;
}

/* УБИРАЕМ min-height для lazy loading - это вызывало растягивание */
/* .content-image[loading="lazy"] {
min-height: 100px;
background: #f5f5f5;
} */

/* Вместо этого делаем плавную загрузку */
.content-image {
opacity: 1;
}

.content-image[loading="lazy"] {
opacity: 0;
transition: opacity 0.3s ease;
}

.content-image[loading="lazy"].loaded {
opacity: 1;
}

/* Стили для мобильных устройств */
@media (max-width: 768px) {
.content-image {
margin: 1rem auto;
border-radius: 3px;
}

.content-image--shadow.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: 10px;
right: 10px;
width: 36px;
height: 36px;
font-size: 30px;
}
}

/* Отключение hover на тач-устройствах */
@media (hover: none) {
.content-image:hover {
transform: none !important;
}
}
`;
document.head.appendChild(style);
});

// Обновляем JavaScript для добавления класса loaded
window.addEventListener('load', () => {
const lazyImages = document.querySelectorAll('img[data-lightbox="true"][loading="lazy"]');
lazyImages.forEach(img => {
if (img.complete) {
img.classList.add('loaded');
} else {
img.addEventListener('load', () => {
img.classList.add('loaded');
});
}
});
});

// Проверка при скролле для lazy images
window.addEventListener('scroll', () => {
setTimeout(() => {
if (window.contentLightbox && window.contentLightbox.initialized) {
const allImages = document.querySelectorAll('img[data-lightbox="true"]');
window.contentLightbox.checkLazyImages(allImages);
}
}, 500);
});

// Проверка при изменении размера окна
window.addEventListener('resize', () => {
setTimeout(() => {
if (window.contentLightbox) {
const allImages = document.querySelectorAll('img[data-lightbox="true"]');
window.contentLightbox.finalCheck(allImages);
}
}, 300);
});
</script>

--- Обработка изображения ---
petrography/:2687 Src: http://localhost:1313/hugo/docs/manuals/laboratory/petrography/images/cat_add.png
petrography/:2688 Natural: 1009 × 97
petrography/:2689 Displayed: 791 × 76
<!-- Lightbox для изображений в статьях -->
<script>
class ContentLightbox {
constructor() {
this.modal = null;
this.modalImage = null;
this.caption = null;
this.images = [];
this.currentIndex = 0;
this.debug = true;
this.initialized = false;
this.init();
}

init() {
this.createModal();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => this.setup(), 100);
});
} else {
setTimeout(() => this.setup(), 100);
}
}

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.innerHTML = '×';
closeBtn.setAttribute('aria-label', 'Закрыть');
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;
margin: 0;
line-height: 40px;
`;
closeBtn.addEventListener('mouseenter', () => {
closeBtn.style.background = 'rgba(255, 255, 255, 0.25)';
closeBtn.style.transform = 'scale(1.1)';
});
closeBtn.addEventListener('mouseleave', () => {
closeBtn.style.background = 'rgba(255, 255, 255, 0.15)';
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: 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) return;
this.initialized = true;

const allLightboxImages = document.querySelectorAll('img[data-lightbox="true"]');

if (this.debug) {
console.log('=== DEBUG LIGHTBOX ===');
console.log('Всего изображений с data-lightbox="true":', allLightboxImages.length);
}

// Обрабатываем ВСЕ изображения сразу
this.processAllImages(allLightboxImages);

// Дополнительная проверка через 1 секунду для lazy images
setTimeout(() => {
this.checkLazyImages(allLightboxImages);
}, 1000);

// Еще одна проверка через 3 секунды
setTimeout(() => {
this.finalCheck(allLightboxImages);
}, 3000);
}

processAllImages(allImages) {
const imagePromises = Array.from(allImages).map(img => {
return new Promise((resolve) => {
// Если изображение уже загружено
if (img.complete && img.naturalWidth > 0) {
this.processImage(img);
resolve(true);
}
// Если изображение еще не загружено, но имеет src
else if (img.src || img.dataset.originalSrc) {
// Ждем загрузки
const onLoad = () => {
img.removeEventListener('load', onLoad);
img.removeEventListener('error', onError);
setTimeout(() => {
this.processImage(img);
resolve(true);
}, 100);
};

const onError = () => {
img.removeEventListener('load', onLoad);
img.removeEventListener('error', onError);
if (this.debug) console.log('❌ Ошибка загрузки:', img.src);
resolve(false);
};

img.addEventListener('load', onLoad);
img.addEventListener('error', onError);

// Если изображение lazy, загружаем его
if (img.loading === 'lazy' && img.dataset.originalSrc && !img.src) {
img.src = img.dataset.originalSrc;
}
} else {
resolve(false);
}
});
});

Promise.all(imagePromises).then(results => {
const successful = results.filter(r => r).length;
if (this.debug) {
console.log(`Обработано изображений: ${successful}/${allImages.length}`);
}
});
}

processImage(img) {
const naturalWidth = img.naturalWidth || 0;
const naturalHeight = img.naturalHeight || 0;
const displayedWidth = img.offsetWidth || img.width || 0;
const displayedHeight = img.offsetHeight || img.height || 0;

if (this.debug) {
console.log('--- Обработка изображения ---');
console.log('Src:', img.src.substring(0, 100));
console.log('Natural:', naturalWidth, '×', naturalHeight);
console.log('Displayed:', displayedWidth, '×', displayedHeight);
}

// Проверяем минимальные размеры
if (naturalWidth === 0 && displayedWidth === 0) {
if (this.debug) console.log('🕐 Изображение еще не загрузилось, откладываем');
return false;
}

const isNotIcon = !this.isIcon(img);
if (!isNotIcon) {
this.markAsNotZoomable(img);
return false;
}

// УЛУЧШЕННАЯ ПРОВЕРКА РАЗМЕРОВ - ОСЛАБЛЯЕМ ТРЕБОВАНИЯ К ВЫСОТЕ ДЛЯ ГОРИЗОНТАЛЬНЫХ
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 isDisplayedHorizontal = displayedAspectRatio > 3;
const isDisplayedVeryHorizontal = displayedAspectRatio > 5;
const isDisplayedExtremelyHorizontal = displayedAspectRatio > 10;

const compressionRatio = naturalWidth > 0 ? displayedWidth / naturalWidth : 1;
const isCompressed = compressionRatio < 0.9;

let isNaturalLargeEnough;

if (isNaturalExtremelyHorizontal) {
// Для ОЧЕНЬ горизонтальных изображений (ширина > 10× высоты)
// Требуем только ширину, высота почти не важна
isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.2 && naturalHeight >= 50;
} else if (isNaturalVeryHorizontal) {
// Для очень горизонтальных (ширина > 5× высоты)
isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.5 && naturalHeight >= MIN_HEIGHT * 0.6;
} else if (isNaturalHorizontal) {
// Для горизонтальных (ширина > 3× высоты)
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 (isDisplayedVeryHorizontal) {
// Для очень горизонтальных отображаемых
isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.2 && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.8;
} else if (isDisplayedHorizontal) {
// Для горизонтальных отображаемых
isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.9;
} else {
// Для обычных отображаемых
isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH && displayedHeight >= MIN_DISPLAYED_HEIGHT;
}

// Итоговая проверка: изображение можно увеличивать если:
// 1. Оригинал достаточно большой ИЛИ
// 2. Отображаемый размер достаточно большой (особенно если изображение сжато)
const isLargeEnough = isNaturalLargeEnough || (isCompressed && isDisplayedLargeEnough);

if (this.debug) {
console.log(`Пропорции: ${naturalAspectRatio.toFixed(2)}:1`);
console.log(`Тип: ${isNaturalExtremelyHorizontal ? 'очень-очень горизонтальное' :
isNaturalVeryHorizontal ? 'очень горизонтальное' :
isNaturalHorizontal ? 'горизонтальное' : 'обычное'}`);
console.log(`Сжатие: ${Math.round(compressionRatio * 100)}%`);
console.log(`Оригинал достаточно большой: ${isNaturalLargeEnough}`);
console.log(`Отображаемый достаточно большой: ${isDisplayedLargeEnough}`);
console.log(`Итог: достаточно большой = ${isLargeEnough}`);
}

if (!isLargeEnough) {
this.markAsNotZoomable(img);
return false;
}

const currentPath = window.location.pathname.toLowerCase();
const isDocsPage = currentPath.includes('/manuals/') ||
currentPath.includes('/docs/') ||
currentPath.includes('/laboratory/');

if (!isDocsPage) {
const isNotFullyVisible = !this.isFullyVisible(img, displayedWidth, displayedHeight);
if (!isNotFullyVisible) {
this.markAsNotZoomable(img);
return false;
}
}

// Изображение прошло все проверки
if (!this.images.includes(img)) {
this.images.push(img);
}

this.markAsZoomable(img);

if (this.debug) {
console.log('✅ Доступно для увеличения');
}

return true;
}

markAsZoomable(img) {
img.style.cursor = 'zoom-in';
img.classList.add('is-zoomable');
img.classList.remove('not-zoomable');

// Удаляем старые обработчики
const oldOnClick = img.onclick;
img.onclick = null;

// Добавляем новые обработчики
img.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const filteredIndex = this.images.indexOf(img);
if (filteredIndex !== -1) {
this.currentIndex = filteredIndex;
this.open(img.dataset.originalSrc || img.src, img.alt || img.title || '');
}
});

// Эффекты при наведении
img.addEventListener('mouseenter', () => {
img.style.opacity = '0.92';
img.style.transform = 'translateY(-3px)';
});

img.addEventListener('mouseleave', () => {
img.style.opacity = '1';
img.style.transform = 'translateY(0)';
});
}

markAsNotZoomable(img) {
img.style.cursor = 'default';
img.classList.remove('content-image--zoomable');
img.classList.add('not-zoomable');
img.onclick = null;
}

checkLazyImages(allImages) {
const lazyImages = Array.from(allImages).filter(img =>
(img.loading === 'lazy' || !img.complete) &&
!this.images.includes(img) &&
!img.classList.contains('not-zoomable')
);

if (lazyImages.length > 0 && this.debug) {
console.log('Проверка lazy images:', lazyImages.length);
}

lazyImages.forEach(img => {
this.processImage(img);
});
}

finalCheck(allImages) {
const unprocessedImages = Array.from(allImages).filter(img =>
!this.images.includes(img) &&
!img.classList.contains('not-zoomable')
);

if (unprocessedImages.length > 0 && this.debug) {
console.log('Финальная проверка:', unprocessedImages.length, 'изображений');
}

unprocessedImages.forEach(img => {
this.processImage(img);
});

if (this.debug) {
console.log('=== ИТОГО ===');
console.log('Доступно для увеличения:', this.images.length, 'из', allImages.length);
}
}

isIcon(img) {
const src = (img.src || '').toLowerCase();
const alt = (img.alt || '').toLowerCase();
const classes = (img.className || '').toLowerCase();

const iconKeywords = [
'icon', 'logo', 'avatar', 'favicon', 'sprite',
'emoji', 'svg', 'ico', 'png-16', 'png-32', 'png-64',
'icon-', '-icon'
];

const naturalWidth = img.naturalWidth || img.offsetWidth || 0;
const naturalHeight = img.naturalHeight || img.offsetHeight || 0;

const isVerySmall = naturalWidth < 50 && naturalHeight < 50;
const isSquareAndSmall = Math.abs(naturalWidth - naturalHeight) < 10 && naturalWidth < 100;

const hasIconKeyword = iconKeywords.some(keyword =>
src.includes(keyword) || alt.includes(keyword) || classes.includes(keyword)
);

return isVerySmall || (isSquareAndSmall && hasIconKeyword);
}

isFullyVisible(img, displayedWidth = null, displayedHeight = null) {
const currentPath = window.location.pathname.toLowerCase();
const isDocsPage = currentPath.includes('/manuals/') ||
currentPath.includes('/docs/') ||
currentPath.includes('/laboratory/');

if (isDocsPage) {
return false;
}

const imgWidth = displayedWidth || img.offsetWidth || img.naturalWidth;
const container = img.parentElement;
if (!container) return false;

const containerWidth = container.clientWidth;
return imgWidth < containerWidth * 0.90;
}

open(src, caption) {
if (this.debug) {
console.log('Открываем lightbox:', src.substring(0, 100));
}

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();
}, 50);

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.25s ease;
}

/* Стили для изображений, которые можно увеличивать */
.content-image.is-zoomable {
cursor: zoom-in;
}

/* Для изображений, которые нельзя увеличивать */
.content-image.not-zoomable {
cursor: default;
}

/* Тень только для zoomable изображений */
.content-image--shadow.is-zoomable {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

/* Маленькие/не-zoomable изображения - без тени */
.content-image:not(.is-zoomable),
.content-image.not-zoomable {
box-shadow: none !important;
}

/* Эффект при наведении на zoomable изображения */
.content-image.is-zoomable:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
opacity: 0.95;
}

/* Отключаем hover эффекты для не-zoomable изображений */
.content-image.not-zoomable:hover {
transform: none !important;
opacity: 1 !important;
box-shadow: none !important;
}

/* Подпись к изображениям */
.content-image__caption {
text-align: center;
font-size: 0.875rem;
color: #666;
margin-top: 0.5rem;
line-height: 1.4;
font-style: italic;
}

/* Обертка для изображений с подписью */
.content-image__wrapper {
margin: 1.5rem 0;
text-align: center;
}

/* Стили для lazy loading */
.content-image {
opacity: 1;
}

.content-image[loading="lazy"] {
opacity: 0;
transition: opacity 0.3s ease;
}

.content-image[loading="lazy"].loaded {
opacity: 1;
}

/* Стили для мобильных устройств */
@media (max-width: 768px) {
.content-image {
margin: 1rem auto;
border-radius: 3px;
}

.content-image--shadow.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: 10px;
right: 10px;
width: 36px;
height: 36px;
font-size: 30px;
line-height: 36px;
}
}

/* Отключение hover на тач-устройствах */
@media (hover: none) {
.content-image:hover {
transform: none !important;
}
}
`;
document.head.appendChild(style);
});

// Добавляем класс loaded для lazy images
window.addEventListener('load', () => {
const lazyImages = document.querySelectorAll('img[data-lightbox="true"][loading="lazy"]');
lazyImages.forEach(img => {
if (img.complete) {
img.classList.add('loaded');
} else {
img.addEventListener('load', () => {
img.classList.add('loaded');
});
}
});
});

// Проверка при скролле для lazy images
window.addEventListener('scroll', () => {
setTimeout(() => {
if (window.contentLightbox && window.contentLightbox.initialized) {
const allImages = document.querySelectorAll('img[data-lightbox="true"]');
window.contentLightbox.checkLazyImages(allImages);
}
}, 500);
});

// Проверка при изменении размера окна
window.addEventListener('resize', () => {
setTimeout(() => {
if (window.contentLightbox) {
const allImages = document.querySelectorAll('img[data-lightbox="true"]');
window.contentLightbox.finalCheck(allImages);
}
}, 300);
});
</script>

Добавлено (2025-12-19, 12:24)
---------------------------------------------
Проблема в том, что на некоторых страницах изображения не получают data-lightbox="true" из-за условий в render-image.html. Давайте упростим логику - сделаем так, чтобы ВСЕ изображения в статьях (кроме главной и разделов) получали lightbox, а фильтрацию оставим на JavaScript.

1. Сначала обновите render-image.html:

```html
{{- $alt := .PlainText | safeHTML -}}
{{- $lazyLoading := .Page.Site.Params.enableImageLazyLoading | default true -}}
{{- $dest := .Destination -}}

{{- $isLocal := not (urls.Parse $dest).Scheme -}}
{{- $isPage := and (eq .Page.Kind "page") (not .Page.BundleType) -}}
{{- $startsWithSlash := hasPrefix $dest "/" -}}
{{- $startsWithRelative := hasPrefix $dest "../" -}}

{{- if and $dest $isLocal -}}
{{- if $startsWithSlash -}}
{{- $dest = (relURL (strings.TrimPrefix "/" $dest)) -}}
{{- else if and $isPage (not $startsWithRelative) -}}
{{ $dest = (printf "../%s" $dest) }}
{{- end -}}
{{- end -}}

{{/* ПРОСТАЯ ЛОГИКА: ВСЕГДА добавляем lightbox для страниц, кроме home и section */}}
{{- $addLightbox := true -}}
{{- $addShadow := true -}}

{{/* ИСКЛЮЧАЕМ ТОЛЬКО главную и разделы */}}
{{- if or (eq .Page.Kind "home") (eq .Page.Kind "section") -}}
{{- $addLightbox = false -}}
{{- $addShadow = false -}}
{{- end -}}

{{/* ВСЕ остальные страницы (page, taxonomy, term) получают lightbox */}}

{{- $imageClass := "content-image" -}}
{{- if $addLightbox -}}
{{- $imageClass = printf "%s %s" $imageClass "content-image--zoomable" -}}
{{- end -}}
{{- if $addShadow -}}
{{- $imageClass = printf "%s %s" $imageClass "content-image--shadow" -}}
{{- end -}}

{{- with .Title -}}
<figure class="content-image__wrapper">
<img src="{{ $dest | safeURL }}"
title="{{ . }}"
alt="{{ $alt }}"
{{ if $lazyLoading }}loading="lazy"{{ end }}
class="{{ $imageClass }}"
data-lightbox="{{ $addLightbox }}"
data-original-src="{{ $dest | safeURL }}" />
<figcaption class="content-image__caption">{{ . }}</figcaption>
</figure>
{{- else -}}
<img src="{{ $dest | safeURL }}"
alt="{{ $alt }}"
{{ if $lazyLoading }}loading="lazy"{{ end }}
class="{{ $imageClass }}"
data-lightbox="{{ $addLightbox }}"
data-original-src="{{ $dest | safeURL }}" />
{{- end -}}
```

2. Теперь обновите JavaScript - добавьте fallback для изображений без data-lightbox:

Добавьте этот код в начало setup() метода (после if (this.initialized) return;):

```javascript
setup() {
if (this.initialized) return;
this.initialized = true;

// Сначала находим все изображения с data-lightbox="true"
let allLightboxImages = document.querySelectorAll('img[data-lightbox="true"]');

// FALLBACK: Если не нашли изображений с data-lightbox="true",
// ищем все изображения в основном контенте
if (allLightboxImages.length === 0) {
if (this.debug) {
console.log('Не найдено изображений с data-lightbox="true", ищем в контенте...');
}

// Находим основной контент статьи
const contentSelectors = [
'main',
'article',
'.content',
'.post-content',
'.article-content',
'.entry-content',
'.prose',
'[role="main"]'
];

let contentArea = null;
for (const selector of contentSelectors) {
const element = document.querySelector(selector);
if (element) {
contentArea = element;
break;
}
}

if (contentArea) {
// Находим все изображения в контенте (кроме иконок в шапке/футере)
const allImagesInContent = contentArea.querySelectorAll('img');
allImagesInContent.forEach(img => {
// Проверяем, что это не иконка в навигации/шапке
const isInHeader = img.closest('header, .header, nav, .nav, .navbar');
const isInFooter = img.closest('footer, .footer');
const isInSidebar = img.closest('aside, .sidebar, .aside');

if (!isInHeader && !isInFooter && !isInSidebar) {
// Добавляем data-lightbox если его нет
if (!img.hasAttribute('data-lightbox')) {
img.setAttribute('data-lightbox', 'true');
if (img.src && !img.hasAttribute('data-original-src')) {
img.setAttribute('data-original-src', img.src);
}
// Добавляем класс для стилей
img.classList.add('content-image', 'content-image--zoomable', 'content-image--shadow');
}
}
});

// Обновляем список
allLightboxImages = document.querySelectorAll('img[data-lightbox="true"]');

if (this.debug) {
console.log('Добавлено изображений через fallback:', allLightboxImages.length);
}
}
}

if (this.debug) {
console.log('=== DEBUG LIGHTBOX ===');
console.log('Всего изображений для обработки:', allLightboxImages.length);
console.log('Текущая страница:', window.location.pathname);
console.log('Page Kind (если доступно):', document.body.dataset.pageKind || 'не определен');
}

// Обрабатываем ВСЕ изображения
this.processAllImages(allLightboxImages);

// Дополнительная проверка через 1 секунду для lazy images
setTimeout(() => {
this.checkLazyImages(allLightboxImages);
}, 1000);

// Еще одна проверка через 3 секунды
setTimeout(() => {
this.finalCheck(allLightboxImages);
}, 3000);
}
```

3. Полный исправленный JavaScript код (с fallback):

Если нужно весь код, вот ключевая часть с fallback. Полный код слишком длинный, но вот что важно добавить в существующий код:

```javascript
// В начале setup() метода, после проверки initialized
setup() {
if (this.initialized) return;
this.initialized = true;

// === НАЧАЛО FALLBACK ЛОГИКИ ===
let allLightboxImages = document.querySelectorAll('img[data-lightbox="true"]');

// Если на странице вообще нет изображений с data-lightbox="true"
// Проверяем, может быть это страница, где render-image.html не сработал
if (allLightboxImages.length === 0) {
console.warn('⚠️ Lightbox: Не найдено изображений с data-lightbox="true"');
console.warn('⚠️ Проверяем, может быть это страница типа:', window.location.pathname);

// Определяем тип страницы по URL или структуре
const path = window.location.pathname.toLowerCase();
const isPossibleArticlePage = (
path.includes('/posts/') ||
path.includes('/blog/') ||
path.includes('/articles/') ||
path.includes('/manuals/') ||
path.includes('/docs/') ||
path.includes('/laboratory/') ||
path.includes('/page/') ||
/\/\d{4}\/\d{2}\/\d{2}\//.test(path) // Дата в URL (например /2023/12/01/)
);

if (isPossibleArticlePage) {
console.log('ℹ️ Это похоже на страницу статьи, добавляем lightbox ко всем изображениям в контенте');

// Находим контент статьи
const contentSelectors = [
'main article',
'article .content',
'.post-content',
'.article-content',
'.entry-content',
'.prose',
'main .content',
'article',
'main'
];

for (const selector of contentSelectors) {
const content = document.querySelector(selector);
if (content) {
// Добавляем data-lightbox ко всем изображениям в контенте
const images = content.querySelectorAll('img');
images.forEach(img => {
// Пропускаем совсем маленькие (иконки)
if (img.width > 100 || img.height > 100) {
img.setAttribute('data-lightbox', 'true');
if (!img.hasAttribute('data-original-src') && img.src) {
img.setAttribute('data-original-src', img.src);
}
// Добавляем базовые классы
if (!img.classList.contains('content-image')) {
img.classList.add('content-image');
}
}
});
break;
}
}

// Обновляем список
allLightboxImages = document.querySelectorAll('img[data-lightbox="true"]');
console.log(`✅ Добавлено ${allLightboxImages.length} изображений через fallback`);
}
}
// === КОНЕЦ FALLBACK ЛОГИКИ ===

// Дальше ваш существующий код продолжается...
if (this.debug) {
console.log('=== DEBUG LIGHTBOX ===');
console.log('Всего изображений для обработки:', allLightboxImages.length);
}

// ... остальной код setup()
}
```

4. Альтернативный простой вариант - обработка ВСЕХ изображений:

Если хотите совсем простой вариант, замените начало setup() на:

```javascript
setup() {
if (this.initialized) return;
this.initialized = true;

// ПРОСТОЙ ВАРИАНТ: обрабатываем ВСЕ изображения в основном контенте

// 1. Находим основной контент
const content = document.querySelector('main, article, .content, .post-content') || document.body;

// 2. Находим все изображения (кроме явных иконок)
const allImages = Array.from(content.querySelectorAll('img')).filter(img => {
// Пропускаем иконки
if (this.isIcon(img)) return false;

// Пропускаем изображения в шапке/футере
const isInHeader = img.closest('header, .header, nav, .navbar');
const isInFooter = img.closest('footer, .footer');
return !isInHeader && !isInFooter;
});

// 3. Добавляем необходимые атрибуты
allImages.forEach(img => {
if (!img.hasAttribute('data-lightbox')) {
img.setAttribute('data-lightbox', 'true');
}
if (!img.hasAttribute('data-original-src') && img.src) {
img.setAttribute('data-original-src', img.src);
}
if (!img.classList.contains('content-image')) {
img.classList.add('content-image');
}
});

// 4. Получаем окончательный список
const allLightboxImages = document.querySelectorAll('img[data-lightbox="true"]');

// ... дальше ваш существующий код
}
```

Что делать:

1. Сначала обновите render-image.html с упрощенной логикой
2. Добавьте fallback логику в JavaScript (первый вариант выше)
3. Проверьте проблемные страницы - теперь изображения должны обрабатываться

Проблема в том, что на некоторых страницах .Page.Kind или другие условия в render-image.html не срабатывают как ожидалось. Fallback в JavaScript решает эту проблему, обрабатывая изображения даже если они не получили data-lightbox="true" из шаблона.

Добавлено (2025-12-19, 12:30)
---------------------------------------------
<!-- Lightbox для изображений в статьях -->
<script>
class ContentLightbox {
constructor() {
this.modal = null;
this.modalImage = null;
this.caption = null;
this.images = [];
this.currentIndex = 0;
this.debug = true;
this.initialized = false;
this.init();
}

init() {
this.createModal();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => this.setup(), 100);
});
} else {
setTimeout(() => this.setup(), 100);
}
}

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.innerHTML = '×';
closeBtn.setAttribute('aria-label', 'Закрыть');
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;
margin: 0;
line-height: 40px;
`;
closeBtn.addEventListener('mouseenter', () => {
closeBtn.style.background = 'rgba(255, 255, 255, 0.25)';
closeBtn.style.transform = 'scale(1.1)';
});
closeBtn.addEventListener('mouseleave', () => {
closeBtn.style.background = 'rgba(255, 255, 255, 0.15)';
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: 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) return;
this.initialized = true;

// === FALLBACK ЛОГИКА: обрабатываем все изображения в контенте ===
let allLightboxImages = document.querySelectorAll('img[data-lightbox="true"]');

// Если нет изображений с data-lightbox="true", пробуем найти в контенте
if (allLightboxImages.length === 0) {
if (this.debug) {
console.log('⚠️ Не найдено изображений с data-lightbox="true", ищем в контенте...');
}

// Определяем, является ли это страницей статьи
const path = window.location.pathname.toLowerCase();
const isArticleLikePage = (
!path.endsWith('/') || // Не главная страница
path.includes('/posts/') ||
path.includes('/blog/') ||
path.includes('/articles/') ||
path.includes('/manuals/') ||
path.includes('/docs/') ||
path.includes('/laboratory/') ||
path.includes('/page/') ||
/\/\d{4}\/\d{2}\/\d{2}\//.test(path) // Дата в URL
);

if (isArticleLikePage) {
// Находим основной контент
const contentSelectors = [
'main',
'article',
'.content',
'.post-content',
'.article-content',
'.entry-content',
'.prose',
'[role="main"]'
];

let contentArea = null;
for (const selector of contentSelectors) {
const element = document.querySelector(selector);
if (element) {
contentArea = element;
break;
}
}

if (contentArea) {
// Находим все изображения в контенте
const imagesInContent = contentArea.querySelectorAll('img');

imagesInContent.forEach(img => {
// Пропускаем явные иконки
if (this.isIcon(img)) return;

// Пропускаем изображения в шапке/футере
const isInHeader = img.closest('header, .header, nav, .nav, .navbar, .menu');
const isInFooter = img.closest('footer, .footer');
const isInSidebar = img.closest('aside, .sidebar, .aside');

if (!isInHeader && !isInFooter && !isInSidebar) {
// Добавляем необходимые атрибуты
img.setAttribute('data-lightbox', 'true');
if (img.src && !img.hasAttribute('data-original-src')) {
img.setAttribute('data-original-src', img.src);
}
// Добавляем базовые классы
if (!img.classList.contains('content-image')) {
img.classList.add('content-image', 'content-image--zoomable', 'content-image--shadow');
}
}
});

// Обновляем список
allLightboxImages = document.querySelectorAll('img[data-lightbox="true"]');

if (this.debug) {
console.log(`✅ Добавлено ${allLightboxImages.length} изображений через fallback`);
}
}
}
}

if (this.debug) {
console.log('=== DEBUG LIGHTBOX ===');
console.log('Всего изображений для обработки:', allLightboxImages.length);
console.log('URL:', window.location.pathname);
}

// Обрабатываем ВСЕ изображения
this.processAllImages(allLightboxImages);

// Дополнительная проверка через 1 секунду для lazy images
setTimeout(() => {
this.checkLazyImages(allLightboxImages);
}, 1000);

// Еще одна проверка через 3 секунды
setTimeout(() => {
this.finalCheck(allLightboxImages);
}, 3000);
}

processAllImages(allImages) {
const imagePromises = Array.from(allImages).map(img => {
return new Promise((resolve) => {
// Если изображение уже загружено
if (img.complete && img.naturalWidth > 0) {
this.processImage(img);
resolve(true);
}
// Если изображение еще не загружено, но имеет src
else if (img.src || img.dataset.originalSrc) {
// Ждем загрузки
const onLoad = () => {
img.removeEventListener('load', onLoad);
img.removeEventListener('error', onError);
setTimeout(() => {
this.processImage(img);
resolve(true);
}, 100);
};

const onError = () => {
img.removeEventListener('load', onLoad);
img.removeEventListener('error', onError);
if (this.debug) console.log('❌ Ошибка загрузки:', img.src);
resolve(false);
};

img.addEventListener('load', onLoad);
img.addEventListener('error', onError);

// Если изображение lazy, загружаем его
if (img.loading === 'lazy' && img.dataset.originalSrc && !img.src) {
img.src = img.dataset.originalSrc;
}
} else {
resolve(false);
}
});
});

Promise.all(imagePromises).then(results => {
const successful = results.filter(r => r).length;
if (this.debug) {
console.log(`Обработано изображений: ${successful}/${allImages.length}`);
}
});
}

processImage(img) {
const naturalWidth = img.naturalWidth || 0;
const naturalHeight = img.naturalHeight || 0;
const displayedWidth = img.offsetWidth || img.width || 0;
const displayedHeight = img.offsetHeight || img.height || 0;

if (this.debug) {
console.log('--- Обработка изображения ---');
console.log('Src:', img.src.substring(0, 100));
console.log('Natural:', naturalWidth, '×', naturalHeight);
console.log('Displayed:', displayedWidth, '×', displayedHeight);
}

// Проверяем минимальные размеры
if (naturalWidth === 0 && displayedWidth === 0) {
if (this.debug) console.log('🕐 Изображение еще не загрузилось, откладываем');
return false;
}

const isNotIcon = !this.isIcon(img);
if (!isNotIcon) {
this.markAsNotZoomable(img);
return false;
}

// УЛУЧШЕННАЯ ПРОВЕРКА РАЗМЕРОВ
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 isDisplayedHorizontal = displayedAspectRatio > 3;
const isDisplayedVeryHorizontal = displayedAspectRatio > 5;
const isDisplayedExtremelyHorizontal = displayedAspectRatio > 10;

const compressionRatio = naturalWidth > 0 ? displayedWidth / naturalWidth : 1;
const isCompressed = compressionRatio < 0.9;

let isNaturalLargeEnough;

if (isNaturalExtremelyHorizontal) {
// Для ОЧЕНЬ горизонтальных изображений (ширина > 10× высоты)
isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.2 && naturalHeight >= 50;
} else if (isNaturalVeryHorizontal) {
// Для очень горизонтальных (ширина > 5× высоты)
isNaturalLargeEnough = naturalWidth >= MIN_WIDTH * 1.5 && naturalHeight >= MIN_HEIGHT * 0.6;
} else if (isNaturalHorizontal) {
// Для горизонтальных (ширина > 3× высоты)
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 (isDisplayedVeryHorizontal) {
// Для очень горизонтальных отображаемых
isDisplayedLargeEnough = displayedWidth >= MIN_DISPLAYED_WIDTH * 1.2 && displayedHeight >= MIN_DISPLAYED_HEIGHT * 0.8;
} else if (isDisplayedHorizontal) {
// Для горизонтальных отображаемых
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);

if (this.debug) {
console.log(`Пропорции: ${naturalAspectRatio.toFixed(2)}:1`);
console.log(`Тип: ${isNaturalExtremelyHorizontal ? 'очень-очень горизонтальное' :
isNaturalVeryHorizontal ? 'очень горизонтальное' :
isNaturalHorizontal ? 'горизонтальное' : 'обычное'}`);
console.log(`Сжатие: ${Math.round(compressionRatio * 100)}%`);
console.log(`Итог: достаточно большой = ${isLargeEnough}`);
}

if (!isLargeEnough) {
this.markAsNotZoomable(img);
return false;
}

const currentPath = window.location.pathname.toLowerCase();
const isDocsPage = currentPath.includes('/manuals/') ||
currentPath.includes('/docs/') ||
currentPath.includes('/laboratory/');

if (!isDocsPage) {
const isNotFullyVisible = !this.isFullyVisible(img, displayedWidth, displayedHeight);
if (!isNotFullyVisible) {
this.markAsNotZoomable(img);
return false;
}
}

// Изображение прошло все проверки
if (!this.images.includes(img)) {
this.images.push(img);
}

this.markAsZoomable(img);

if (this.debug) {
console.log('✅ Доступно для увеличения');
}

return true;
}

markAsZoomable(img) {
img.style.cursor = 'zoom-in';
img.classList.add('is-zoomable');
img.classList.remove('not-zoomable');

// Удаляем старые обработчики
img.onclick = null;

// Добавляем новые обработчики
img.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const filteredIndex = this.images.indexOf(img);
if (filteredIndex !== -1) {
this.currentIndex = filteredIndex;
this.open(img.dataset.originalSrc || img.src, img.alt || img.title || '');
}
});

// Эффекты при наведении
img.addEventListener('mouseenter', () => {
img.style.opacity = '0.92';
img.style.transform = 'translateY(-3px)';
});

img.addEventListener('mouseleave', () => {
img.style.opacity = '1';
img.style.transform = 'translateY(0)';
});
}

markAsNotZoomable(img) {
img.style.cursor = 'default';
img.classList.remove('content-image--zoomable');
img.classList.add('not-zoomable');
img.onclick = null;
}

checkLazyImages(allImages) {
const lazyImages = Array.from(allImages).filter(img =>
(img.loading === 'lazy' || !img.complete) &&
!this.images.includes(img) &&
!img.classList.contains('not-zoomable')
);

if (lazyImages.length > 0 && this.debug) {
console.log('Проверка lazy images:', lazyImages.length);
}

lazyImages.forEach(img => {
this.processImage(img);
});
}

finalCheck(allImages) {
const unprocessedImages = Array.from(allImages).filter(img =>
!this.images.includes(img) &&
!img.classList.contains('not-zoomable')
);

if (unprocessedImages.length > 0 && this.debug) {
console.log('Финальная проверка:', unprocessedImages.length, 'изображений');
}

unprocessedImages.forEach(img => {
this.processImage(img);
});

if (this.debug) {
console.log('=== ИТОГО ===');
console.log('Доступно для увеличения:', this.images.length, 'из', allImages.length);
}
}

isIcon(img) {
const src = (img.src || '').toLowerCase();
const alt = (img.alt || '').toLowerCase();
const classes = (img.className || '').toLowerCase();

const iconKeywords = [
'icon', 'logo', 'avatar', 'favicon', 'sprite',
'emoji', 'svg', 'ico', 'png-16', 'png-32', 'png-64',
'icon-', '-icon'
];

const naturalWidth = img.naturalWidth || img.offsetWidth || 0;
const naturalHeight = img.naturalHeight || img.offsetHeight || 0;

const isVerySmall = naturalWidth < 50 && naturalHeight < 50;
const isSquareAndSmall = Math.abs(naturalWidth - naturalHeight) < 10 && naturalWidth < 100;

const hasIconKeyword = iconKeywords.some(keyword =>
src.includes(keyword) || alt.includes(keyword) || classes.includes(keyword)
);

return isVerySmall || (isSquareAndSmall && hasIconKeyword);
}

isFullyVisible(img, displayedWidth = null, displayedHeight = null) {
const currentPath = window.location.pathname.toLowerCase();
const isDocsPage = currentPath.includes('/manuals/') ||
currentPath.includes('/docs/') ||
currentPath.includes('/laboratory/');

if (isDocsPage) {
return false;
}

const imgWidth = displayedWidth || img.offsetWidth || img.naturalWidth;
const container = img.parentElement;
if (!container) return false;

const containerWidth = container.clientWidth;
return imgWidth < containerWidth * 0.90;
}

open(src, caption) {
if (this.debug) {
console.log('Открываем lightbox:', src.substring(0, 100));
}

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();
}, 50);

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.25s ease;
}

/* Стили для изображений, которые можно увеличивать */
.content-image.is-zoomable {
cursor: zoom-in;
}

/* Для изображений, которые нельзя увеличивать */
.content-image.not-zoomable {
cursor: default;
}

/* Тень только для zoomable изображений */
.content-image--shadow.is-zoomable {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

/* Маленькие/не-zoomable изображения - без тени */
.content-image:not(.is-zoomable),
.content-image.not-zoomable {
box-shadow: none !important;
}

/* Эффект при наведении на zoomable изображения */
.content-image.is-zoomable:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
opacity: 0.95;
}

/* Отключаем hover эффекты для не-zoomable изображений */
.content-image.not-zoomable:hover {
transform: none !important;
opacity: 1 !important;
box-shadow: none !important;
}

/* Подпись к изображениям */
.content-image__caption {
text-align: center;
font-size: 0.875rem;
color: #666;
margin-top: 0.5rem;
line-height: 1.4;
font-style: italic;
}

/* Обертка для изображений с подписью */
.content-image__wrapper {
margin: 1.5rem 0;
text-align: center;
}

/* Стили для lazy loading */
.content-image {
opacity: 1;
}

.content-image[loading="lazy"] {
opacity: 0;
transition: opacity 0.3s ease;
}

.content-image[loading="lazy"].loaded {
opacity: 1;
}

/* Стили для мобильных устройств */
@media (max-width: 768px) {
.content-image {
margin: 1rem auto;
border-radius: 3px;
}

.content-image--shadow.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: 10px;
right: 10px;
width: 36px;
height: 36px;
font-size: 30px;
line-height: 36px;
}
}

/* Отключение hover на тач-устройствах */
@media (hover: none) {
.content-image:hover {
transform: none !important;
}
}
`;
document.head.appendChild(style);
});

// Добавляем класс loaded для lazy images
window.addEventListener('load', () => {
const lazyImages = document.querySelectorAll('img[data-lightbox="true"][loading="lazy"]');
lazyImages.forEach(img => {
if (img.complete) {
img.classList.add('loaded');
} else {
img.addEventListener('load', () => {
img.classList.add('loaded');
});
}
});
});

// Проверка при скролле для lazy images
window.addEventListener('scroll', () => {
setTimeout(() => {
if (window.contentLightbox && window.contentLightbox.initialized) {
const allImages = document.querySelectorAll('img[data-lightbox="true"]');
window.contentLightbox.checkLazyImages(allImages);
}
}, 500);
});

// Проверка при изменении размера окна
window.addEventListener('resize', () => {
setTimeout(() => {
if (window.contentLightbox) {
const allImages = document.querySelectorAll('img[data-lightbox="true"]');
window.contentLightbox.finalCheck(allImages);
}
}, 300);
});

// Дополнительная проверка для страниц, которые могли пропустить
setTimeout(() => {
if (window.contentLightbox && window.contentLightbox.initialized) {
// Проверяем, есть ли вообще изображения на странице
const anyImagesOnPage = document.querySelectorAll('img').length > 0;
const lightboxImages = document.querySelectorAll('img[data-lightbox="true"]').length;

if (anyImagesOnPage && lightboxImages === 0) {
console.log('🔄 Перезапускаем lightbox для страницы без обработанных изображений');
window.contentLightbox.setup();
}
}
}, 5000);
</script>

Добавлено (2025-12-19, 12:40)
---------------------------------------------
<!-- Lightbox для изображений в статьях -->
<script>
class ContentLightbox {
constructor() {
this.modal = null;
this.modalImage = null;
this.caption = null;
this.images = [];
this.currentIndex = 0;
this.debug = true;
this.initialized = false;
this.init();
}

init() {
this.createModal();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => this.setup(), 100);
});
} else {
setTimeout(() => this.setup(), 100);
}
}

createModal() {
// ... (код createModal остается без изменений) ...
}

setup() {
if (this.initialized) return;
this.initialized = true;

if (this.debug) {
console.log('=== DEBUG LIGHTBOX ===');
console.log('URL:', window.location.pathname);
}

// ВАЖНОЕ ИЗМЕНЕНИЕ: НЕ ИЩЕМ ПО data-lightbox="true"
// Вместо этого находим ВСЕ изображения в основном контенте

// 1. Находим основной контент
const contentSelectors = [
'main',
'article',
'.content',
'.post-content',
'.article-content',
'.entry-content',
'.prose',
'[role="main"]'
];

let contentArea = null;
for (const selector of contentSelectors) {
const element = document.querySelector(selector);
if (element) {
contentArea = element;
break;
}
}

// Если не нашли контент, используем body (но исключаем шапку/футер)
if (!contentArea) {
contentArea = document.body;
}

// 2. Находим ВСЕ изображения в контенте
const allImages = contentArea.querySelectorAll('img');

if (this.debug) {
console.log('Всего изображений на странице:', document.querySelectorAll('img').length);
console.log('Изображений в контенте:', allImages.length);
}

// 3. Фильтруем изображения для lightbox
const imagesForLightbox = Array.from(allImages).filter(img => {
// Пропускаем явные иконки
if (this.isIcon(img)) {
if (this.debug) console.log('Пропущено (иконка):', img.src.substring(0, 80));
return false;
}

// Пропускаем изображения в шапке/футере/навигации
const isInHeader = img.closest('header, .header, nav, .nav, .navbar, .menu, .navigation');
const isInFooter = img.closest('footer, .footer');
const isInSidebar = img.closest('aside, .sidebar, .aside');

if (isInHeader || isInFooter || isInSidebar) {
if (this.debug) console.log('Пропущено (шапка/футер):', img.src.substring(0, 80));
return false;
}

return true;
});

if (this.debug) {
console.log('Изображений для обработки lightbox:', imagesForLightbox.length);
}

// 4. Обрабатываем каждое изображение
imagesForLightbox.forEach(img => {
// Всегда добавляем базовые классы для стилей
if (!img.classList.contains('content-image')) {
img.classList.add('content-image');
}

// Добавляем атрибуты для lightbox (даже если в итоге не подойдет)
if (!img.hasAttribute('data-lightbox')) {
img.setAttribute('data-lightbox', 'pending'); // Вместо "true"
}
if (!img.hasAttribute('data-original-src') && img.src) {
img.setAttribute('data-original-src', img.src);
}

// Проверяем, подходит ли изображение для увеличения
this.checkImageForLightbox(img);
});

// 5. Дополнительные проверки
setTimeout(() => {
this.checkAllImages(imagesForLightbox);
}, 1000);

setTimeout(() => {
this.finalImageCheck(imagesForLightbox);
}, 3000);
}

checkImageForLightbox(img) {
// Ждем загрузки изображения
if (!img.complete) {
img.addEventListener('load', () => {
setTimeout(() => this.processImage(img), 100);
});
img.addEventListener('error', () => {
if (this.debug) console.log('❌ Ошибка загрузки:', img.src);
this.markAsNotZoomable(img);
});
return;
}

// Если изображение уже загружено
this.processImage(img);
}

processImage(img) {
const naturalWidth = img.naturalWidth || 0;
const naturalHeight = img.naturalHeight || 0;
const displayedWidth = img.offsetWidth || img.width || 0;
const displayedHeight = img.offsetHeight || img.height || 0;

if (this.debug) {
console.log('--- Проверка изображения ---');
console.log('Src:', img.src.substring(0, 100));
console.log('Natural:', naturalWidth, '×', naturalHeight);
console.log('Displayed:', displayedWidth, '×', displayedHeight);
}

// Если изображение не загрузилось
if (naturalWidth === 0 && displayedWidth === 0) {
this.markAsNotZoomable(img);
return false;
}

// Проверяем размеры для lightbox
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);

if (!isLargeEnough) {
if (this.debug) console.log('❌ Слишком маленькое для увеличения');
this.markAsNotZoomable(img);
return false;
}

const currentPath = window.location.pathname.toLowerCase();
const isDocsPage = currentPath.includes('/manuals/') ||
currentPath.includes('/docs/') ||
currentPath.includes('/laboratory/');

if (!isDocsPage) {
const isNotFullyVisible = !this.isFullyVisible(img, displayedWidth, displayedHeight);
if (!isNotFullyVisible) {
if (this.debug) console.log('❌ Полностью видно на странице');
this.markAsNotZoomable(img);
return false;
}
}

// Изображение подходит для lightbox
if (!this.images.includes(img)) {
this.images.push(img);
}

this.markAsZoomable(img);

if (this.debug) {
console.log('✅ Доступно для увеличения');
}

return true;
}

markAsZoomable(img) {
img.style.cursor = 'zoom-in';
img.classList.add('is-zoomable');
img.classList.remove('not-zoomable');

// Обновляем data-lightbox
img.setAttribute('data-lightbox', 'true');

// Удаляем старые обработчики
img.onclick = null;

// Добавляем обработчик клика
img.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const filteredIndex = this.images.indexOf(img);
if (filteredIndex !== -1) {
this.currentIndex = filteredIndex;
this.open(img.dataset.originalSrc || img.src, img.alt || img.title || '');
}
});

// Эффекты при наведении
img.addEventListener('mouseenter', () => {
img.style.opacity = '0.92';
img.style.transform = 'translateY(-3px)';
});

img.addEventListener('mouseleave', () => {
img.style.opacity = '1';
img.style.transform = 'translateY(0)';
});
}

markAsNotZoomable(img) {
img.style.cursor = 'default';
img.classList.add('not-zoomable');
img.classList.remove('is-zoomable');
img.onclick = null;

// Убираем data-lightbox или ставим false
img.setAttribute('data-lightbox', 'false');
}

checkAllImages(images) {
if (this.debug) {
console.log('Проверка всех изображений через 1с...');
}

images.forEach(img => {
if (!img.classList.contains('is-zoomable') && !img.classList.contains('not-zoomable')) {
this.checkImageForLightbox(img);
}
});
}

finalImageCheck(images) {
if (this.debug) {
console.log('Финальная проверка через 3с...');
const zoomable = images.filter(img => img.classList.contains('is-zoomable')).length;
const notZoomable = images.filter(img => img.classList.contains('not-zoomable')).length;
const unprocessed = images.length - zoomable - notZoomable;
console.log(`Итого: ${zoomable} можно увеличивать, ${notZoomable} нельзя, ${unprocessed} не обработано`);
}

// Обрабатываем все оставшиеся изображения
images.forEach(img => {
if (!img.classList.contains('is-zoomable') && !img.classList.contains('not-zoomable')) {
this.markAsNotZoomable(img); // Если не обработано - считаем не zoomable
}
});
}

isIcon(img) {
// ... (код isIcon без изменений) ...
}

isFullyVisible(img, displayedWidth = null, displayedHeight = null) {
// ... (код isFullyVisible без изменений) ...
}

open(src, caption) {
// ... (код open без изменений) ...
}

close() {
// ... (код close без изменений) ...
}

isOpen() {
// ... (код isOpen без изменений) ...
}

next() {
// ... (код next без изменений) ...
}

prev() {
// ... (код prev без изменений) ...
}
}

// Инициализация
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
window.contentLightbox = new ContentLightbox();
}, 100);

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.25s ease;
}

/* Стили ТОЛЬКО для изображений, которые можно увеличивать */
.content-image.is-zoomable {
cursor: zoom-in;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.content-image.is-zoomable:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
opacity: 0.95;
}

/* Для изображений, которые нельзя увеличивать */
.content-image.not-zoomable {
cursor: default;
box-shadow: none;
}

.content-image.not-zoomable:hover {
transform: none !important;
opacity: 1 !important;
box-shadow: none !important;
}

/* Подпись к изображениям */
.content-image__caption {
text-align: center;
font-size: 0.875rem;
color: #666;
margin-top: 0.5rem;
line-height: 1.4;
font-style: italic;
}

/* Обертка для изображений с подписью */
.content-image__wrapper {
margin: 1.5rem 0;
text-align: center;
}

/* Стили для lazy loading */
.content-image[loading="lazy"] {
opacity: 0;
transition: opacity 0.3s ease;
}

.content-image[loading="lazy"].loaded {
opacity: 1;
}

/* Стили для мобильных устройств */
@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);
}
}

/* Отключение hover на тач-устройствах */
@media (hover: none) {
.content-image:hover {
transform: none !important;
}
}
`;
document.head.appendChild(style);
});

// Загрузка lazy images
window.addEventListener('load', () => {
const lazyImages = document.querySelectorAll('.content-image[loading="lazy"]');
lazyImages.forEach(img => {
if (img.complete) {
img.classList.add('loaded');
} else {
img.addEventListener('load', () => {
img.classList.add('loaded');
// Проверяем после загрузки
if (window.contentLightbox) {
setTimeout(() => window.contentLightbox.checkImageForLightbox(img), 100);
}
});
}
});
});

// Проверка при скролле
window.addEventListener('scroll', () => {
setTimeout(() => {
if (window.contentLightbox) {
const images = document.querySelectorAll('.content-image:not(.is-zoomable):not(.not-zoomable)');
images.forEach(img => {
window.contentLightbox.checkImageForLightbox(img);
});
}
}, 500);
});
</script>

Добавлено (2025-12-19, 12:49)
---------------------------------------------
<!-- Lightbox для изображений в статьях -->
<script>
class ContentLightbox {
constructor() {
this.modal = null;
this.modalImage = null;
this.caption = null;
this.images = [];
this.currentIndex = 0;
this.debug = true;
this.initialized = false;
this.init();
}

init() {
this.createModal();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => this.setup(), 100);
});
} else {
setTimeout(() => this.setup(), 100);
}
}

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.innerHTML = '×';
closeBtn.setAttribute('aria-label', 'Закрыть');
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;
margin: 0;
line-height: 40px;
`;
closeBtn.addEventListener('mouseenter', () => {
closeBtn.style.background = 'rgba(255, 255, 255, 0.25)';
closeBtn.style.transform = 'scale(1.1)';
});
closeBtn.addEventListener('mouseleave', () => {
closeBtn.style.background = 'rgba(255, 255, 255, 0.15)';
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: 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) return;
this.initialized = true;

if (this.debug) {
console.log('=== DEBUG LIGHTBOX ===');
console.log('URL:', window.location.pathname);
}

const contentSelectors = [
'main',
'article',
'.content',
'.post-content',
'.article-content',
'.entry-content',
'.prose',
'[role="main"]'
];

let contentArea = null;
for (const selector of contentSelectors) {
const element = document.querySelector(selector);
if (element) {
contentArea = element;
break;
}
}

if (!contentArea) {
contentArea = document.body;
}

const allImages = contentArea.querySelectorAll('img');

if (this.debug) {
console.log('Всего изображений на странице:', document.querySelectorAll('img').length);
console.log('Изображений в контенте:', allImages.length);
}

const imagesForLightbox = Array.from(allImages).filter(img => {
if (this.isIcon(img)) {
if (this.debug) console.log('Пропущено (иконка):', img.src.substring(0, 80));
return false;
}

const isInHeader = img.closest('header, .header, nav, .nav, .navbar, .menu, .navigation');
const isInFooter = img.closest('footer, .footer');
const isInSidebar = img.closest('aside, .sidebar, .aside');

if (isInHeader || isInFooter || isInSidebar) {
if (this.debug) console.log('Пропущено (шапка/футер):', img.src.substring(0, 80));
return false;
}

return true;
});

if (this.debug) {
console.log('Изображений для обработки lightbox:', imagesForLightbox.length);
}

imagesForLightbox.forEach(img => {
if (!img.classList.contains('content-image')) {
img.classList.add('content-image');
}

if (!img.hasAttribute('data-lightbox')) {
img.setAttribute('data-lightbox', 'pending');
}
if (!img.hasAttribute('data-original-src') && img.src) {
img.setAttribute('data-original-src', img.src);
}

this.checkImageForLightbox(img);
});

setTimeout(() => {
this.checkAllImages(imagesForLightbox);
}, 1000);

setTimeout(() => {
this.finalImageCheck(imagesForLightbox);
}, 3000);
}

checkImageForLightbox(img) {
if (!img.complete) {
img.addEventListener('load', () => {
setTimeout(() => this.processImage(img), 100);
});
img.addEventListener('error', () => {
if (this.debug) console.log('❌ Ошибка загрузки:', img.src);
this.markAsNotZoomable(img);
});
return;
}

this.processImage(img);
}

processImage(img) {
const naturalWidth = img.naturalWidth || 0;
const naturalHeight = img.naturalHeight || 0;
const displayedWidth = img.offsetWidth || img.width || 0;
const displayedHeight = img.offsetHeight || img.height || 0;

if (this.debug) {
console.log('--- Проверка изображения ---');
console.log('Src:', img.src.substring(0, 100));
console.log('Natural:', naturalWidth, '×', naturalHeight);
console.log('Displayed:', displayedWidth, '×', displayedHeight);
}

if (naturalWidth === 0 && displayedWidth === 0) {
this.markAsNotZoomable(img);
return false;
}

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);

if (!isLargeEnough) {
if (this.debug) console.log('❌ Слишком маленькое для увеличения');
this.markAsNotZoomable(img);
return false;
}

const currentPath = window.location.pathname.toLowerCase();
const isDocsPage = currentPath.includes('/manuals/') ||
currentPath.includes('/docs/') ||
currentPath.includes('/laboratory/');

if (!isDocsPage) {
const isNotFullyVisible = !this.isFullyVisible(img, displayedWidth, displayedHeight);
if (!isNotFullyVisible) {
if (this.debug) console.log('❌ Полностью видно на странице');
this.markAsNotZoomable(img);
return false;
}
}

if (!this.images.includes(img)) {
this.images.push(img);
}

this.markAsZoomable(img);

if (this.debug) {
console.log('✅ Доступно для увеличения');
}

return true;
}

markAsZoomable(img) {
img.style.cursor = 'zoom-in';
img.classList.add('is-zoomable');
img.classList.remove('not-zoomable');

img.setAttribute('data-lightbox', 'true');

img.onclick = null;

img.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const filteredIndex = this.images.indexOf(img);
if (filteredIndex !== -1) {
this.currentIndex = filteredIndex;
this.open(img.dataset.originalSrc || img.src, img.alt || img.title || '');
}
});

img.addEventListener('mouseenter', () => {
img.style.opacity = '0.92';
img.style.transform = 'translateY(-3px)';
});

img.addEventListener('mouseleave', () => {
img.style.opacity = '1';
img.style.transform = 'translateY(0)';
});
}

markAsNotZoomable(img) {
img.style.cursor = 'default';
img.classList.add('not-zoomable');
img.classList.remove('is-zoomable');
img.onclick = null;

img.setAttribute('data-lightbox', 'false');
}

checkAllImages(images) {
if (this.debug) {
console.log('Проверка всех изображений через 1с...');
}

images.forEach(img => {
if (!img.classList.contains('is-zoomable') && !img.classList.contains('not-zoomable')) {
this.checkImageForLightbox(img);
}
});
}

finalImageCheck(images) {
if (this.debug) {
console.log('Финальная проверка через 3с...');
const zoomable = images.filter(img => img.classList.contains('is-zoomable')).length;
const notZoomable = images.filter(img => img.classList.contains('not-zoomable')).length;
const unprocessed = images.length - zoomable - notZoomable;
console.log(`Итого: ${zoomable} можно увеличивать, ${notZoomable} нельзя, ${unprocessed} не обработано`);
}

images.forEach(img => {
if (!img.classList.contains('is-zoomable') && !img.classList.contains('not-zoomable')) {
this.markAsNotZoomable(img);
}
});
}

isIcon(img) {
const src = (img.src || '').toLowerCase();
const alt = (img.alt || '').toLowerCase();
const classes = (img.className || '').toLowerCase();

const iconKeywords = [
'icon', 'logo', 'avatar', 'favicon', 'sprite',
'emoji', 'svg', 'ico', 'png-16', 'png-32', 'png-64',
'icon-', '-icon'
];

const naturalWidth = img.naturalWidth || img.offsetWidth || 0;
const naturalHeight = img.naturalHeight || img.offsetHeight || 0;

const isVerySmall = naturalWidth < 50 && naturalHeight < 50;
const isSquareAndSmall = Math.abs(naturalWidth - naturalHeight) < 10 && naturalWidth < 100;

const hasIconKeyword = iconKeywords.some(keyword =>
src.includes(keyword) || alt.includes(keyword) || classes.includes(keyword)
);

return isVerySmall || (isSquareAndSmall && hasIconKeyword);
}

isFullyVisible(img, displayedWidth = null, displayedHeight = null) {
const currentPath = window.location.pathname.toLowerCase();
const isDocsPage = currentPath.includes('/manuals/') ||
currentPath.includes('/docs/') ||
currentPath.includes('/laboratory/');

if (isDocsPage) {
return false;
}

const imgWidth = displayedWidth || img.offsetWidth || img.naturalWidth;
const container = img.parentElement;
if (!container) return false;

const containerWidth = container.clientWidth;
return imgWidth < containerWidth * 0.90;
}

open(src, caption) {
if (this.debug) {
console.log('Открываем lightbox:', src.substring(0, 100));
}

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();
}, 100);

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.25s ease;
}

.content-image.is-zoomable {
cursor: zoom-in;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.content-image.is-zoomable:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
opacity: 0.95;
}

.content-image.not-zoomable {
cursor: default;
box-shadow: none;
}

.content-image.not-zoomable:hover {
transform: none !important;
opacity: 1 !important;
box-shadow: none !important;
}

.content-image__caption {
text-align: center;
font-size: 0.875rem;
color: #666;
margin-top: 0.5rem;
line-height: 1.4;
font-style: italic;
}

.content-image__wrapper {
margin: 1.5rem 0;
text-align: center;
}

.content-image[loading="lazy"] {
opacity: 0;
transition: opacity 0.3s ease;
}

.content-image[loading="lazy"].loaded {
opacity: 1;
}

@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);
}
}

@media (hover: none) {
.content-image:hover {
transform: none !important;
}
}
`;
document.head.appendChild(style);
});

window.addEventListener('load', () => {
const lazyImages = document.querySelectorAll('.content-image[loading="lazy"]');
lazyImages.forEach(img => {
if (img.complete) {
img.classList.add('loaded');
} else {
img.addEventListener('load', () => {
img.classList.add('loaded');
if (window.contentLightbox) {
setTimeout(() => window.contentLightbox.checkImageForLightbox(img), 100);
}
});
}
});
});

window.addEventListener('scroll', () => {
setTimeout(() => {
if (window.contentLightbox) {
const images = document.querySelectorAll('.content-image:not(.is-zoomable):not(.not-zoomable)');
images.forEach(img => {
window.contentLightbox.checkImageForLightbox(img);
});
}
}, 500);
});
</script>

Добавлено (2025-12-19, 13:00)
---------------------------------------------
<!-- Lightbox для изображений в статьях -->
<script>
class ContentLightbox {
constructor() {
this.modal = null;
this.modalImage = null;
this.caption = null;
this.images = [];
this.currentIndex = 0;
this.debug = true;
this.initialized = false;
this.init();
}

init() {
this.createModal();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => this.setup(), 100);
});
} else {
setTimeout(() => this.setup(), 100);
}
}

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.innerHTML = '×';
closeBtn.setAttribute('aria-label', 'Закрыть');
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;
margin: 0;
line-height: 40px;
`;
closeBtn.addEventListener('mouseenter', () => {
closeBtn.style.background = 'rgba(255, 255, 255, 0.25)';
closeBtn.style.transform = 'scale(1.1)';
});
closeBtn.addEventListener('mouseleave', () => {
closeBtn.style.background = 'rgba(255, 255, 255, 0.15)';
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: 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();
}
});
}

shouldEnableLightbox() {
// Проверяем, нужно ли включать lightbox на этой странице
const path = window.location.pathname.toLowerCase();
const host = window.location.hostname;

// Главная страница (разные варианты)
const isHomePage = (
path === '/' ||
path === '/index.html' ||
path === '' ||
path === '/hugo/' ||
path.endsWith('/index.html') ||
(path.split('/').filter(p => p).length <= 1) // Корневой путь
);

// Страницы разделов (section pages)
const isSectionPage = (
path.includes('/categories/') ||
path.includes('/tags/') ||
path.includes('/archive/') ||
path.includes('/blog/') && !path.includes('/blog/') && path.split('/').length <= 3
);

// Проверяем по структуре DOM (дополнительная проверка)
const hasPostContent = document.querySelector('article, .post, .post-content, .article-content') !== null;
const isListPage = document.querySelector('.posts-list, .articles-list, .blog-list, .post-list') !== null;

if (this.debug) {
console.log('=== ПРОВЕРКА СТРАНИЦЫ ===');
console.log('Путь:', path);
console.log('Главная страница?', isHomePage);
console.log('Страница раздела?', isSectionPage);
console.log('Есть контент статьи?', hasPostContent);
console.log('Страница списка?', isListPage);
}

// ИСКЛЮЧАЕМ:
// 1. Главную страницу
// 2. Страницы разделов (без конкретной статьи)
// 3. Страницы списков
if (isHomePage || isSectionPage || (isListPage && !hasPostContent)) {
if (this.debug) console.log('❌ Lightbox отключен для этой страницы');
return false;
}

// ВКЛЮЧАЕМ:
// 1. Страницы статей (имеют article или .post-content)
// 2. Страницы документации (/manuals/, /docs/, /laboratory/)
// 3. Страницы с конкретным контентом
const isArticlePage = hasPostContent ||
path.includes('/manuals/') ||
path.includes('/docs/') ||
path.includes('/laboratory/') ||
path.includes('/posts/') ||
path.includes('/articles/');

if (this.debug) console.log('✅ Lightbox включен для этой страницы');
return isArticlePage;
}

setup() {
if (this.initialized) return;
this.initialized = true;

// Проверяем, нужно ли включать lightbox на этой странице
if (!this.shouldEnableLightbox()) {
if (this.debug) {
console.log('Lightbox отключен для этой страницы');
}
return; // Не инициализируем lightbox
}

if (this.debug) {
console.log('=== DEBUG LIGHTBOX ===');
console.log('URL:', window.location.pathname);
}

// Находим основной контент
const contentSelectors = [
'main',
'article',
'.content',
'.post-content',
'.article-content',
'.entry-content',
'.prose',
'[role="main"]'
];

let contentArea = null;
for (const selector of contentSelectors) {
const element = document.querySelector(selector);
if (element) {
contentArea = element;
break;
}
}

if (!contentArea) {
contentArea = document.body;
}

// Находим ВСЕ изображения в контенте
const allImages = contentArea.querySelectorAll('img');

if (this.debug) {
console.log('Всего изображений на странице:', document.querySelectorAll('img').length);
console.log('Изображений в контенте:', allImages.length);
}

// Фильтруем изображения для lightbox
const imagesForLightbox = Array.from(allImages).filter(img => {
// Пропускаем явные иконки
if (this.isIcon(img)) {
if (this.debug) console.log('Пропущено (иконка):', img.src.substring(0, 80));
return false;
}

// Пропускаем изображения в шапке/футере/навигации
const isInHeader = img.closest('header, .header, nav, .nav, .navbar, .menu, .navigation');
const isInFooter = img.closest('footer, .footer');
const isInSidebar = img.closest('aside, .sidebar, .aside');

if (isInHeader || isInFooter || isInSidebar) {
if (this.debug) console.log('Пропущено (шапка/футер):', img.src.substring(0, 80));
return false;
}

return true;
});

if (this.debug) {
console.log('Изображений для обработки lightbox:', imagesForLightbox.length);
}

// Обрабатываем каждое изображение
imagesForLightbox.forEach(img => {
// Всегда добавляем базовые классы для стилей
if (!img.classList.contains('content-image')) {
img.classList.add('content-image');
}

// Добавляем атрибуты для lightbox
if (!img.hasAttribute('data-lightbox')) {
img.setAttribute('data-lightbox', 'pending');
}
if (!img.hasAttribute('data-original-src') && img.src) {
img.setAttribute('data-original-src', img.src);
}

// Проверяем, подходит ли изображение для увеличения
this.checkImageForLightbox(img);
});

// Дополнительные проверки
setTimeout(() => {
this.checkAllImages(imagesForLightbox);
}, 1000);

setTimeout(() => {
this.finalImageCheck(imagesForLightbox);
}, 3000);
}

checkImageForLightbox(img) {
// Ждем загрузки изображения
if (!img.complete) {
img.addEventListener('load', () => {
setTimeout(() => this.processImage(img), 100);
});
img.addEventListener('error', () => {
if (this.debug) console.log('❌ Ошибка загрузки:', img.src);
this.markAsNotZoomable(img);
});
return;
}

// Если изображение уже загружено
this.processImage(img);
}

processImage(img) {
const naturalWidth = img.naturalWidth || 0;
const naturalHeight = img.naturalHeight || 0;
const displayedWidth = img.offsetWidth || img.width || 0;
const displayedHeight = img.offsetHeight || img.height || 0;

if (this.debug) {
console.log('--- Проверка изображения ---');
console.log('Src:', img.src.substring(0, 100));
console.log('Natural:', naturalWidth, '×', naturalHeight);
console.log('Displayed:', displayedWidth, '×', displayedHeight);
}

// Если изображение не загрузилось
if (naturalWidth === 0 && displayedWidth === 0) {
this.markAsNotZoomable(img);
return false;
}

// Проверяем размеры для lightbox
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);

if (!isLargeEnough) {
if (this.debug) console.log('❌ Слишком маленькое для увеличения');
this.markAsNotZoomable(img);
return false;
}

const currentPath = window.location.pathname.toLowerCase();
const isDocsPage = currentPath.includes('/manuals/') ||
currentPath.includes('/docs/') ||
currentPath.includes('/laboratory/');

if (!isDocsPage) {
const isNotFullyVisible = !this.isFullyVisible(img, displayedWidth, displayedHeight);
if (!isNotFullyVisible) {
if (this.debug) console.log('❌ Полностью видно на странице');
this.markAsNotZoomable(img);
return false;
}
}

// Изображение подходит для lightbox
if (!this.images.includes(img)) {
this.images.push(img);
}

this.markAsZoomable(img);

if (this.debug) {
console.log('✅ Доступно для увеличения');
}

return true;
}

markAsZoomable(img) {
img.style.cursor = 'zoom-in';
img.classList.add('is-zoomable');
img.classList.remove('not-zoomable');

// Обновляем data-lightbox
img.setAttribute('data-lightbox', 'true');

// Удаляем старые обработчики
img.onclick = null;

// Добавляем обработчик клика
img.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const filteredIndex = this.images.indexOf(img);
if (filteredIndex !== -1) {
this.currentIndex = filteredIndex;
this.open(img.dataset.originalSrc || img.src, img.alt || img.title || '');
}
});

// Эффекты при наведении
img.addEventListener('mouseenter', () => {
img.style.opacity = '0.92';
img.style.transform = 'translateY(-3px)';
});

img.addEventListener('mouseleave', () => {
img.style.opacity = '1';
img.style.transform = 'translateY(0)';
});
}

markAsNotZoomable(img) {
img.style.cursor = 'default';
img.classList.add('not-zoomable');
img.classList.remove('is-zoomable');
img.onclick = null;

// Убираем data-lightbox
img.setAttribute('data-lightbox', 'false');
}

checkAllImages(images) {
if (this.debug) {
console.log('Проверка всех изображений через 1с...');
}

images.forEach(img => {
if (!img.classList.contains('is-zoomable') && !img.classList.contains('not-zoomable')) {
this.checkImageForLightbox(img);
}
});
}

finalImageCheck(images) {
if (this.debug) {
console.log('Финальная проверка через 3с...');
const zoomable = images.filter(img => img.classList.contains('is-zoomable')).length;
const notZoomable = images.filter(img => img.classList.contains('not-zoomable')).length;
const unprocessed = images.length - zoomable - notZoomable;
console.log(`Итого: ${zoomable} можно увеличивать, ${notZoomable} нельзя, ${unprocessed} не обработано`);
}

// Обрабатываем все оставшиеся изображения
images.forEach(img => {
if (!img.classList.contains('is-zoomable') && !img.classList.contains('not-zoomable')) {
this.markAsNotZoomable(img);
}
});
}

isIcon(img) {
const src = (img.src || '').toLowerCase();
const alt = (img.alt || '').toLowerCase();
const classes = (img.className || '').toLowerCase();

const iconKeywords = [
'icon', 'logo', 'avatar', 'favicon', 'sprite',
'emoji', 'svg', 'ico', 'png-16', 'png-32', 'png-64',
'icon-', '-icon'
];

const naturalWidth = img.naturalWidth || img.offsetWidth || 0;
const naturalHeight = img.naturalHeight || img.offsetHeight || 0;

const isVerySmall = naturalWidth < 50 && naturalHeight < 50;
const isSquareAndSmall = Math.abs(naturalWidth - naturalHeight) < 10 && naturalWidth < 100;

const hasIconKeyword = iconKeywords.some(keyword =>
src.includes(keyword) || alt.includes(keyword) || classes.includes(keyword)
);

return isVerySmall || (isSquareAndSmall && hasIconKeyword);
}

isFullyVisible(img, displayedWidth = null, displayedHeight = null) {
const currentPath = window.location.pathname.toLowerCase();
const isDocsPage = currentPath.includes('/manuals/') ||
currentPath.includes('/docs/') ||
currentPath.includes('/laboratory/');

if (isDocsPage) {
return false;
}

const imgWidth = displayedWidth || img.offsetWidth || img.naturalWidth;
const container = img.parentElement;
if (!container) return false;

const containerWidth = container.clientWidth;
return imgWidth < containerWidth * 0.90;
}

open(src, caption) {
if (this.debug) {
console.log('Открываем lightbox:', src.substring(0, 100));
}

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();
}, 50);

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.25s ease;
}

/* Стили ТОЛЬКО для изображений, которые можно увеличивать */
.content-image.is-zoomable {
cursor: zoom-in;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.content-image.is-zoomable:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
opacity: 0.95;
}

/* Для изображений, которые нельзя увеличивать */
.content-image.not-zoomable {
cursor: default;
box-shadow: none;
}

.content-image.not-zoomable:hover {
transform: none !important;
opacity: 1 !important;
box-shadow: none !important;
}

/* Подпись к изображениям */
.content-image__caption {
text-align: center;
font-size: 0.875rem;
color: #666;
margin-top: 0.5rem;
line-height: 1.4;
font-style: italic;
}

/* Обертка для изображений с подписью */
.content-image__wrapper {
margin: 1.5rem 0;
text-align: center;
}

/* Стили для lazy loading */
.content-image[loading="lazy"] {
opacity: 0;
transition: opacity 0.3s ease;
}

.content-image[loading="lazy"].loaded {
opacity: 1;
}

/* Стили для мобильных устройств */
@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);
}
}

/* Отключение hover на тач-устройствах */
@media (hover: none) {
.content-image:hover {
transform: none !important;
}
}
`;
document.head.appendChild(style);
});

// Загрузка lazy images
window.addEventListener('load', () => {
const lazyImages = document.querySelectorAll('.content-image[loading="lazy"]');
lazyImages.forEach(img => {
if (img.complete) {
img.classList.add('loaded');
} else {
img.addEventListener('load', () => {
img.classList.add('loaded');
// Проверяем после загрузки
if (window.contentLightbox) {
setTimeout(() => window.contentLightbox.checkImageForLightbox(img), 100);
}
});
}
});
});

// Проверка при скролле
window.addEventListener('scroll', () => {
setTimeout(() => {
if (window.contentLightbox) {
const images = document.querySelectorAll('.content-image:not(.is-zoomable):not(.not-zoomable)');
images.forEach(img => {
window.contentLightbox.checkImageForLightbox(img);
});
}
}, 500);
});
</script>

Добавлено (2025-12-19, 13:09)
---------------------------------------------
<!-- Lightbox для изображений в статьях -->
<script>
class ContentLightbox {
constructor() {
this.modal = null;
this.modalImage = null;
this.caption = null;
this.images = [];
this.currentIndex = 0;
this.debug = true;
this.initialized = false;
this.init();
}

init() {
this.createModal();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => this.setup(), 100);
});
} else {
setTimeout(() => this.setup(), 100);
}
}

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.innerHTML = '×';
closeBtn.setAttribute('aria-label', 'Закрыть');
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;
margin: 0;
line-height: 40px;
`;
closeBtn.addEventListener('mouseenter', () => {
closeBtn.style.background = 'rgba(255, 255, 255, 0.25)';
closeBtn.style.transform = 'scale(1.1)';
});
closeBtn.addEventListener('mouseleave', () => {
closeBtn.style.background = 'rgba(255, 255, 255, 0.15)';
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: 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) return;
this.initialized = true;

// ПРОВЕРКА: Только для страниц в директории /hugo/docs/
const currentPath = window.location.pathname.toLowerCase();
const isDocsPage = currentPath.includes('/hugo/docs/');

if (this.debug) {
console.log('=== DEBUG LIGHTBOX ===');
console.log('URL:', currentPath);
console.log('В директории /hugo/docs/:', isDocsPage);
}

// Если не в нужной директории - ВЫХОДИМ
if (!isDocsPage) {
if (this.debug) console.log('❌ Не в директории /hugo/docs/, lightbox отключен');
return;
}

// Находим основной контент
const contentSelectors = [
'main',
'article',
'.content',
'.post-content',
'.article-content',
'.entry-content',
'.prose',
'[role="main"]'
];

let contentArea = null;
for (const selector of contentSelectors) {
const element = document.querySelector(selector);
if (element) {
contentArea = element;
break;
}
}

if (!contentArea) {
contentArea = document.body;
}

// Находим ВСЕ изображения в контенте
const allImages = contentArea.querySelectorAll('img');

if (this.debug) {
console.log('Всего изображений в контенте:', allImages.length);
}

// Фильтруем изображения
const imagesForProcessing = Array.from(allImages).filter(img => {
// Пропускаем явные иконки
if (this.isIcon(img)) {
if (this.debug) console.log('Пропущено (иконка):', img.src.substring(0, 80));
return false;
}

// Пропускаем изображения в шапке/футере/навигации
const isInHeader = img.closest('header, .header, nav, .nav, .navbar, .menu, .navigation');
const isInFooter = img.closest('footer, .footer');
const isInSidebar = img.closest('aside, .sidebar, .aside');

if (isInHeader || isInFooter || isInSidebar) {
if (this.debug) console.log('Пропущено (шапка/футер):', img.src.substring(0, 80));
return false;
}

return true;
});

if (this.debug) {
console.log('Изображений для обработки:', imagesForProcessing.length);
}

// Обрабатываем каждое изображение
imagesForProcessing.forEach(img => {
// Всегда добавляем базовые классы для стилей
if (!img.classList.contains('content-image')) {
img.classList.add('content-image');
}

// Добавляем атрибуты для lightbox
if (!img.hasAttribute('data-lightbox')) {
img.setAttribute('data-lightbox', 'pending');
}
if (!img.hasAttribute('data-original-src') && img.src) {
img.setAttribute('data-original-src', img.src);
}

// Проверяем изображение
this.checkImageForLightbox(img);
});

// Дополнительные проверки
setTimeout(() => {
this.checkAllImages(imagesForProcessing);
}, 1000);

setTimeout(() => {
this.finalImageCheck(imagesForProcessing);
}, 3000);
}

checkImageForLightbox(img) {
if (!img.complete) {
img.addEventListener('load', () => {
setTimeout(() => this.processImage(img), 100);
});
img.addEventListener('error', () => {
if (this.debug) console.log('❌ Ошибка загрузки:', img.src);
this.markAsNotZoomable(img);
});
return;
}

this.processImage(img);
}

processImage(img) {
const naturalWidth = img.naturalWidth || 0;
const naturalHeight = img.naturalHeight || 0;
const displayedWidth = img.offsetWidth || img.width || 0;
const displayedHeight = img.offsetHeight || img.height || 0;

if (this.debug) {
console.log('--- Проверка изображения ---');
console.log('Src:', img.src.substring(0, 100));
console.log('Natural:', naturalWidth, '×', naturalHeight);
console.log('Displayed:', displayedWidth, '×', displayedHeight);
}

if (naturalWidth === 0 && displayedWidth === 0) {
this.markAsNotZoomable(img);
return false;
}

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);

if (!isLargeEnough) {
if (this.debug) console.log('❌ Слишком маленькое для увеличения');
this.markAsNotZoomable(img);
return false;
}

// Для документации всегда разрешаем, если достаточно большое
const isNotFullyVisible = !this.isFullyVisible(img, displayedWidth, displayedHeight);
if (!isNotFullyVisible) {
if (this.debug) console.log('❌ Полностью видно на странице');
this.markAsNotZoomable(img);
return false;
}

// Изображение подходит для lightbox
if (!this.images.includes(img)) {
this.images.push(img);
}

this.markAsZoomable(img);

if (this.debug) {
console.log('✅ Доступно для увеличения');
}

return true;
}

markAsZoomable(img) {
img.style.cursor = 'zoom-in';
img.classList.add('is-zoomable');
img.classList.remove('not-zoomable');
img.setAttribute('data-lightbox', 'true');
img.onclick = null;

img.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const filteredIndex = this.images.indexOf(img);
if (filteredIndex !== -1) {
this.currentIndex = filteredIndex;
this.open(img.dataset.originalSrc || img.src, img.alt || img.title || '');
}
});

img.addEventListener('mouseenter', () => {
img.style.opacity = '0.92';
img.style.transform = 'translateY(-3px)';
});

img.addEventListener('mouseleave', () => {
img.style.opacity = '1';
img.style.transform = 'translateY(0)';
});
}

markAsNotZoomable(img) {
img.style.cursor = 'default';
img.classList.add('not-zoomable');
img.classList.remove('is-zoomable');
img.onclick = null;
img.setAttribute('data-lightbox', 'false');
}

checkAllImages(images) {
if (this.debug) {
console.log('Проверка всех изображений через 1с...');
}

images.forEach(img => {
if (!img.classList.contains('is-zoomable') && !img.classList.contains('not-zoomable')) {
this.checkImageForLightbox(img);
}
});
}

finalImageCheck(images) {
if (this.debug) {
const zoomable = images.filter(img => img.classList.contains('is-zoomable')).length;
const notZoomable = images.filter(img => img.classList.contains('not-zoomable')).length;
const unprocessed = images.length - zoomable - notZoomable;
console.log(`Итого: ${zoomable} можно увеличивать, ${notZoomable} нельзя, ${unprocessed} не обработано`);
}

images.forEach(img => {
if (!img.classList.contains('is-zoomable') && !img.classList.contains('not-zoomable')) {
this.markAsNotZoomable(img);
}
});
}

isIcon(img) {
const src = (img.src || '').toLowerCase();
const alt = (img.alt || '').toLowerCase();
const classes = (img.className || '').toLowerCase();

const iconKeywords = [
'icon', 'logo', 'avatar', 'favicon', 'sprite',
'emoji', 'svg', 'ico', 'png-16', 'png-32', 'png-64',
'icon-', '-icon'
];

const naturalWidth = img.naturalWidth || img.offsetWidth || 0;
const naturalHeight = img.naturalHeight || img.offsetHeight || 0;

const isVerySmall = naturalWidth < 50 && naturalHeight < 50;
const isSquareAndSmall = Math.abs(naturalWidth - naturalHeight) < 10 && naturalWidth < 100;

const hasIconKeyword = iconKeywords.some(keyword =>
src.includes(keyword) || alt.includes(keyword) || classes.includes(keyword)
);

return isVerySmall || (isSquareAndSmall && hasIconKeyword);
}

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;
return imgWidth < containerWidth * 0.90;
}

open(src, caption) {
if (this.debug) {
console.log('Открываем lightbox:', src.substring(0, 100));
}

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();
}, 100);

const style = document.createElement('style');
style.textContent = `
/* Базовые стили для изображений в контенте (только в /hugo/docs/) */
body:has([href*="/hugo/docs/"]) .content-image,
.content-image {
max-width: 100%;
height: auto;
display: block;
margin: 1.5rem auto;
border-radius: 4px;
transition: all 0.25s ease;
}

/* Стили ТОЛЬКО для изображений, которые можно увеличивать */
.content-image.is-zoomable {
cursor: zoom-in;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.content-image.is-zoomable:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
opacity: 0.95;
}

/* Для изображений, которые нельзя увеличивать */
.content-image.not-zoomable {
cursor: default;
box-shadow: none;
}

.content-image.not-zoomable:hover {
transform: none !important;
opacity: 1 !important;
box-shadow: none !important;
}

/* Подпись к изображениям */
.content-image__caption {
text-align: center;
font-size: 0.875rem;
color: #666;
margin-top: 0.5rem;
line-height: 1.4;
font-style: italic;
}

/* Обертка для изображений с подписью */
.content-image__wrapper {
margin: 1.5rem 0;
text-align: center;
}

/* Стили для lazy loading */
.content-image[loading="lazy"] {
opacity: 0;
transition: opacity 0.3s ease;
}

.content-image[loading="lazy"].loaded {
opacity: 1;
}

/* Стили для мобильных устройств */
@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);
}
}

/* Отключение hover на тач-устройствах */
@media (hover: none) {
.content-image:hover {
transform: none !important;
}
}
`;
document.head.appendChild(style);
});

// Загрузка lazy images
window.addEventListener('load', () => {
const lazyImages = document.querySelectorAll('.content-image[loading="lazy"]');
lazyImages.forEach(img => {
if (img.complete) {
img.classList.add('loaded');
} else {
img.addEventListener('load', () => {
img.classList.add('loaded');
if (window.contentLightbox) {
setTimeout(() => window.contentLightbox.checkImageForLightbox(img), 100);
}
});
}
});
});

// Проверка при скролле
window.addEventListener('scroll', () => {
setTimeout(() => {
if (window.contentLightbox) {
const images = document.querySelectorAll('.content-image:not(.is-zoomable):not(.not-zoomable)');
images.forEach(img => {
window.contentLightbox.checkImageForLightbox(img);
});
}
}, 500);
});
</script>

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