diff --git a/html/css/style.css b/html/css/style.css index cc23f71..f0aa496 100644 --- a/html/css/style.css +++ b/html/css/style.css @@ -335,6 +335,154 @@ button { font-family: inherit; cursor: pointer; border: none; background: none; .work-info p { font-size: 14px; color: var(--color-text-muted); margin-bottom: 16px; } .work-meta { display: flex; gap: 16px; font-size: 13px; color: var(--color-text-muted); padding-top: 16px; border-top: 1px solid var(--color-border); } +/* ===== PORTFOLIO VIDEO ===== */ +.work-media { position: relative; overflow: hidden; } +.work-video { + position: relative; + display: block; + width: 100%; + height: 100%; + padding: 0; + border: 0; + background: transparent; + color: #fff; + cursor: pointer; + text-align: left; +} +.work-video:focus-visible { outline: 3px solid var(--color-accent); outline-offset: -3px; } +.work-video-preview { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + background: #1f1f1f; + transition: transform 0.4s ease; +} +.work-card:hover .work-video-preview { transform: scale(1.04); } +.work-video::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(to top, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0) 45%); + pointer-events: none; +} +.work-play { + position: absolute; + top: 50%; left: 50%; + width: 64px; height: 64px; + margin: -32px 0 0 -32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: rgba(255,255,255,0.92); + color: var(--color-accent); + box-shadow: 0 6px 24px rgba(0,0,0,0.3); + transition: transform 0.25s ease, background 0.25s ease; + z-index: 1; +} +.work-play svg { margin-left: 3px; } +.work-video:hover .work-play { transform: scale(1.1); background: #fff; } +.work-video-label { + position: absolute; + left: 16px; bottom: 12px; + font-size: 13px; + font-weight: 600; + text-shadow: 0 1px 4px rgba(0,0,0,0.6); + z-index: 1; +} + +/* ===== VIDEO MODAL ===== */ +.video-modal { + position: fixed; + inset: 0; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease, visibility 0.3s ease; +} +.video-modal.open { opacity: 1; visibility: visible; } +.video-modal-backdrop { + position: absolute; + inset: 0; + background: rgba(0,0,0,0.92); + -webkit-backdrop-filter: blur(6px); + backdrop-filter: blur(6px); +} +.video-modal-dialog { + position: relative; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + transform: scale(0.96); + transition: transform 0.3s ease; +} +.video-modal.open .video-modal-dialog { transform: scale(1); } +.video-modal-bar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 14px 20px; + color: #fff; +} +.video-modal-title { + font-size: 16px; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.video-modal-actions { display: flex; gap: 8px; flex-shrink: 0; } +.video-modal-btn { + width: 44px; height: 44px; + display: flex; + align-items: center; + justify-content: center; + border: 0; + border-radius: 50%; + background: rgba(255,255,255,0.12); + color: #fff; + cursor: pointer; + transition: background 0.2s ease, transform 0.2s ease; +} +.video-modal-btn:hover { background: rgba(255,255,255,0.25); transform: scale(1.06); } +.video-modal-btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; } +.video-modal-stage { + flex: 1; + min-height: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 0 20px 20px; +} +.video-modal-stage video { + display: block; + max-width: 100%; + max-height: 100%; + width: auto; + height: 100%; + object-fit: contain; + border-radius: 12px; + background: #000; + box-shadow: 0 20px 60px rgba(0,0,0,0.6); + outline: none; +} +.video-modal-stage video:fullscreen { border-radius: 0; } +body.video-modal-open { overflow: hidden; } + +@media (max-width: 768px) { + .work-play { width: 56px; height: 56px; margin: -28px 0 0 -28px; } + .video-modal-bar { padding: 10px 12px; } + .video-modal-title { font-size: 14px; } + .video-modal-stage { padding: 0 8px 12px; } + .video-modal-stage video { border-radius: 8px; } +} + /* ===== REVIEWS ===== */ .reviews { background: var(--color-bg-alt); } .reviews-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 24px; margin-top: 48px; } diff --git a/html/js/main.js b/html/js/main.js index 6032a77..3b09ef9 100644 --- a/html/js/main.js +++ b/html/js/main.js @@ -69,4 +69,107 @@ document.querySelectorAll('a[href^="#"]').forEach(anchor => { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); -}); \ No newline at end of file +}); + +// ===== Portfolio Video Lightbox ===== +(function() { + const modal = document.getElementById('videoModal'); + const player = document.getElementById('videoModalPlayer'); + const title = document.getElementById('videoModalTitle'); + const fsBtn = document.getElementById('videoModalFullscreen'); + const triggers = document.querySelectorAll('.work-video[data-video]'); + if (!modal || !player || !triggers.length) return; + + let lastFocus = null; + + function open(src, label) { + lastFocus = document.activeElement; + title.textContent = label || ''; + player.src = src; + modal.classList.add('open'); + modal.setAttribute('aria-hidden', 'false'); + document.body.classList.add('video-modal-open'); + const p = player.play(); + if (p && typeof p.catch === 'function') p.catch(() => {}); + player.focus({ preventScroll: true }); + } + + function close() { + if (document.fullscreenElement) { + document.exitFullscreen().catch(() => {}); + } + player.pause(); + modal.classList.remove('open'); + modal.setAttribute('aria-hidden', 'true'); + document.body.classList.remove('video-modal-open'); + setTimeout(() => { + if (!modal.classList.contains('open')) { + player.removeAttribute('src'); + player.load(); + } + }, 300); + if (lastFocus && typeof lastFocus.focus === 'function') lastFocus.focus({ preventScroll: true }); + } + + function toggleFullscreen() { + if (document.fullscreenElement) { + document.exitFullscreen().catch(() => {}); + return; + } + if (player.requestFullscreen) { + player.requestFullscreen().catch(() => {}); + } else if (player.webkitEnterFullscreen) { + player.webkitEnterFullscreen(); // iOS Safari + } + } + + triggers.forEach(btn => { + btn.addEventListener('click', function(e) { + e.preventDefault(); + open(btn.getAttribute('data-video'), btn.getAttribute('data-title')); + }); + }); + + // Превью: когда карточка попадает в зону видимости, гарантируем загрузку первого кадра + const previews = document.querySelectorAll('.work-video-preview'); + if ('IntersectionObserver' in window && previews.length) { + const io = new IntersectionObserver((entries, obs) => { + entries.forEach(entry => { + if (!entry.isIntersecting) return; + const v = entry.target; + obs.unobserve(v); + if (v.readyState < 2) { + v.preload = 'auto'; + v.load(); + } + v.addEventListener('loadeddata', () => { + if (v.currentTime === 0) { try { v.currentTime = 0.1; } catch (err) {} } + v.preload = 'metadata'; + }, { once: true }); + }); + }, { rootMargin: '200px 0px' }); + previews.forEach(v => io.observe(v)); + } + + modal.querySelectorAll('[data-video-close]').forEach(el => { + el.addEventListener('click', close); + }); + + // Клик по тёмной области вокруг плеера тоже закрывает окно + modal.querySelectorAll('.video-modal-dialog, .video-modal-stage, .video-modal-bar').forEach(el => { + el.addEventListener('click', function(e) { + if (e.target === el) close(); + }); + }); + + if (fsBtn) fsBtn.addEventListener('click', toggleFullscreen); + + player.addEventListener('dblclick', toggleFullscreen); + + document.addEventListener('keydown', function(e) { + if (!modal.classList.contains('open')) return; + if (e.key === 'Escape') { close(); return; } + if (e.key === ' ' && document.activeElement === player) return; // native handling + if (e.key === 'f' || e.key === 'F') toggleFullscreen(); + }); +})(); diff --git a/html/portfolio.html b/html/portfolio.html index fd4f17f..1f87149 100644 --- a/html/portfolio.html +++ b/html/portfolio.html @@ -74,7 +74,11 @@