/
melissaaaz
/
MemoryOffice
Обзор
Документация
Войти
/
melissaaaz
/
MemoryOffice
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
front/src/components/VideoModal.jsx
280 строк
13 KB
melissaaaz
upload files Frontend
19 дек 2025, 21:55
19 дек 2025, 21:55
bb001d3
Код
Авторство
О чём код?
// components/VideoModal.jsx import React, { useEffect, useRef } from 'react' import { X, Play, Film, Globe, MapPin, Star, Building, Castle } from 'lucide-react' const VideoModal = ({ isOpen, onClose, city = 'kaliningrad' }) => { const modalRef = useRef(null) const videoRef = useRef(null) // Видео для разных городов const cityVideos = { kaliningrad: { videoPath: '/videos/kaliningrad.mp4', poster: '/images/kaliningrad-video-poster.jpg', title: 'Янтарные сны Калининграда', description: 'Путешествие по самому западному городу России', landmarks: [ { name: 'Кафедральный собор', icon: <Building className="h-4 w-4" /> }, { name: 'Рыбная деревня', icon: <Castle className="h-4 w-4" /> }, { name: 'Музей янтаря', icon: <Star className="h-4 w-4" /> }, { name: 'Форт №5', icon: <Castle className="h-4 w-4" /> }, { name: 'Бранденбургские ворота', icon: <MapPin className="h-4 w-4" /> }, { name: 'Остров Канта', icon: <Globe className="h-4 w-4" /> } ], colorTheme: 'amber', bgGradient: 'from-amber-50 to-white', borderColor: 'border-amber-200', textColor: 'text-amber-900', textLight: 'text-amber-700', accentBg: 'bg-amber-100', accentText: 'text-amber-800', buttonGradient: 'from-amber-500 to-amber-600 hover:from-amber-600 hover:to-amber-700' }, moscow: { videoPath: '/videos/moscow.mp4', poster: '/images/moscow-video-poster.jpg', title: 'Москва: имперский размах', description: 'От Кремля до Москва-Сити — экскурсия по столице', landmarks: [ { name: 'Красная площадь', icon: <Building className="h-4 w-4" /> }, { name: 'Москва-Сити', icon: <Building className="h-4 w-4" /> }, { name: 'Арбат', icon: <MapPin className="h-4 w-4" /> }, { name: 'ВДНХ', icon: <Star className="h-4 w-4" /> }, { name: 'Храм Христа Спасителя', icon: <Building className="h-4 w-4" /> }, { name: 'Третьяковская галерея', icon: <Star className="h-4 w-4" /> } ], colorTheme: 'red', bgGradient: 'from-red-50 to-white', borderColor: 'border-red-200', textColor: 'text-red-900', textLight: 'text-red-700', accentBg: 'bg-red-100', accentText: 'text-red-800', buttonGradient: 'from-red-500 to-red-600 hover:from-red-600 hover:to-red-700' } } const videoConfig = cityVideos[city] || cityVideos.kaliningrad // Закрытие по ESC useEffect(() => { const handleEsc = (e) => { if (e.key === 'Escape') onClose() } if (isOpen) { document.addEventListener('keydown', handleEsc) document.body.style.overflow = 'hidden' } return () => { document.removeEventListener('keydown', handleEsc) document.body.style.overflow = 'unset' } }, [isOpen, onClose]) // Автовоспроизведение при открытии useEffect(() => { if (isOpen && videoRef.current) { videoRef.current.play().catch(e => console.log('Автовоспроизведение заблокировано:', e)) } }, [isOpen]) // Клик вне модального окна useEffect(() => { const handleClickOutside = (e) => { if (modalRef.current && !modalRef.current.contains(e.target)) { onClose() } } if (isOpen) { document.addEventListener('mousedown', handleClickOutside) } return () => { document.removeEventListener('mousedown', handleClickOutside) } }, [isOpen, onClose]) if (!isOpen) return null return ( <> {/* Затемнение фона с анимацией */} <div className="fixed inset-0 bg-white/90 backdrop-blur-sm z-[100] transition-all duration-300 animate-fade-in" /> {/* Модальное окно */} <div className="fixed inset-0 z-[101] flex items-center justify-center p-4 md:p-6"> <div ref={modalRef} className={`relative w-full max-w-5xl bg-gradient-to-br ${videoConfig.bgGradient} rounded-2xl overflow-hidden shadow-2xl border-2 ${videoConfig.borderColor} animate-scale-in`} > {/* Шапка модального окна */} <div className={`flex items-center justify-between p-4 md:p-6 border-b ${videoConfig.borderColor} bg-white/80 backdrop-blur-sm`}> <div className="flex items-center gap-3 md:gap-4"> <div className={`p-2 md:p-3 rounded-xl ${videoConfig.accentBg}`}> <Film className={`h-5 w-5 md:h-6 md:w-6 ${videoConfig.accentText}`} /> </div> <div className="flex-1"> <div className="flex items-center gap-2 flex-wrap"> <h2 className={`text-lg md:text-2xl font-bold ${videoConfig.textColor}`}> {videoConfig.title} </h2> <span className={`px-3 py-1 rounded-full text-xs font-medium ${videoConfig.accentBg} ${videoConfig.accentText}`}> {city === 'moscow' ? 'Москва' : 'Калининград'} </span> </div> <p className={`text-sm md:text-base ${videoConfig.textLight} mt-1`}> {videoConfig.description} </p> </div> </div> <button onClick={onClose} className="p-2 hover:bg-gray-100/80 rounded-full transition-all duration-200 group" aria-label="Закрыть видео" > <X className="h-5 w-5 md:h-6 md:w-6 text-gray-500 group-hover:text-gray-700 group-hover:rotate-90 transition-all" /> </button> </div> {/* Основной контент - видео и информация */} <div className="grid grid-cols-1 lg:grid-cols-3 bg-white/50"> {/* Видео контейнер */} <div className="lg:col-span-2"> <div className="relative aspect-[16/8] md:aspect-[16/14]"> <video ref={videoRef} src={videoConfig.videoPath} poster={videoConfig.poster} className="absolute inset-0 w-full h-full object-cover" controls controlsList="nodownload" playsInline preload="metadata" > <source src={videoConfig.videoPath} type="video/mp4" /> Ваш браузер не поддерживает видео тег. </video> {/* Тонкий градиент сверху */} <div className="absolute inset-0 bg-gradient-to-t from-white/5 via-transparent to-transparent pointer-events-none" /> </div> </div> {/* Информация справа от видео */} <div className={`p-4 md:p-6 border-l ${videoConfig.borderColor} bg-white/70 backdrop-blur-sm`}> <div className="space-y-6"> {/* Достопримечательности */} <div> <div className="flex items-center gap-2 mb-4"> <div className={`p-2 rounded-lg ${videoConfig.accentBg}`}> <MapPin className={`h-4 w-4 ${videoConfig.accentText}`} /> </div> <h3 className={`text-lg font-semibold ${videoConfig.textColor}`}> Вы увидите в видео: </h3> </div> <div className="grid grid-cols-2 gap-3"> {videoConfig.landmarks.map((landmark, index) => ( <div key={index} className={`flex items-center gap-3 p-3 rounded-xl border ${videoConfig.borderColor} bg-white hover:shadow-md transition-all duration-200 group hover:-translate-y-0.5`} > <div className={`p-2 rounded-lg ${videoConfig.accentBg} group-hover:scale-110 transition-transform`}> {landmark.icon} </div> <span className={`text-sm font-medium ${videoConfig.textColor} flex-1`}> {landmark.name} </span> </div> ))} </div> </div> {/* Информация о городе */} <div className="pt-4 border-t border-gray-200/50"> <div className="flex items-center gap-2 mb-3"> <div className={`p-2 rounded-lg ${videoConfig.accentBg}`}> <Globe className={`h-4 w-4 ${videoConfig.accentText}`} /> </div> <h3 className={`text-lg font-semibold ${videoConfig.textColor}`}> О городе </h3> </div> <div className="p-4 rounded-xl border border-gray-200/50 bg-white/50"> <p className={`text-sm leading-relaxed ${videoConfig.textLight}`}> {city === 'moscow' ? 'Москва — столица России, город с многовековой историей, где традиции встречаются с современностью. От древних стен Кремля до футуристических небоскребов Москва-Сити.' : 'Калининград — самый западный город России, сохранивший дух Восточной Пруссии. Город янтаря, фортов и удивительной архитектуры.' } </p> </div> </div> {/* Интересные факты */} <div className="grid grid-cols-2 gap-3 pt-4 border-t border-gray-200/50"> <div className={`text-center p-3 rounded-xl border ${videoConfig.borderColor} bg-white/80 hover:shadow-sm transition-shadow`}> <div className={`text-lg md:text-xl font-bold mb-1 ${videoConfig.textColor}`}> {city === 'moscow' ? '12.7 млн' : '0.5 млн'} </div> <div className={`text-xs ${videoConfig.textLight}`}>Население</div> </div> <div className={`text-center p-3 rounded-xl border ${videoConfig.borderColor} bg-white/80 hover:shadow-sm transition-shadow`}> <div className={`text-lg md:text-xl font-bold mb-1 ${videoConfig.textColor}`}> {city === 'moscow' ? '870 лет' : '770 лет'} </div> <div className={`text-xs ${videoConfig.textLight}`}>Возраст города</div> </div> </div> </div> </div> </div> {/* Подвал модального окна */} <div className={`p-4 md:p-6 border-t ${videoConfig.borderColor} bg-white/80 backdrop-blur-sm`}> <div className="flex flex-col md:flex-row items-center justify-between gap-4"> <div className={`flex items-center gap-3 p-3 rounded-xl ${videoConfig.accentBg} ${videoConfig.accentText} text-sm`}> <Star className="h-4 w-4" /> <span>Рекомендуем смотреть в полноэкранном режиме</span> </div> <div className="flex gap-3"> <button onClick={() => { if (videoRef.current) { if (videoRef.current.paused) { videoRef.current.play() } else { videoRef.current.pause() } } }} className="px-4 py-2 rounded-lg font-medium transition-all hover:scale-105 border border-gray-300 bg-white text-gray-700 hover:bg-gray-50" > <Play className="inline h-4 w-4 mr-2" /> Пауза/Играть </button> <button onClick={() => { if (videoRef.current) { videoRef.current.requestFullscreen() } }} className={`px-4 py-2 rounded-lg font-medium transition-all hover:scale-105 text-white ${videoConfig.buttonGradient}`} > <Play className="inline h-4 w-4 mr-2" /> Полный экран </button> </div> </div> </div> </div> </div> </> ) } export default VideoModal