Files
2025-08-02 22:43:51 +03:00

247 lines
8.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const { useState, useEffect } = React;
// Функция для получения настроек сайта из HTML
function getSiteConfigFromHTML() {
const configElement = document.querySelector('#site-config site-header');
if (!configElement) {
return {
bgImage: 'https://picsum.photos/1920/1080?random=1',
title: 'Станция Сосновая',
subtitle: 'База отдыха'
};
}
return {
bgImage: configElement.getAttribute('data-bg-image') || 'https://picsum.photos/1920/1080?random=1',
title: configElement.getAttribute('data-title') || 'Станция Сосновая',
subtitle: configElement.getAttribute('data-subtitle') || 'База отдыха'
};
}
// Функция для получения данных карточек из HTML
function getCardDataFromHTML() {
const cardElements = document.querySelectorAll('#cards-data card-data');
return Array.from(cardElements).map(element => {
const images = element.getAttribute('data-images');
return {
title: element.getAttribute('data-title'),
short: element.getAttribute('data-short'),
images: images ? images.split(',').map(img => img.trim()) : [],
long: element.getAttribute('data-long')
};
});
}
// Функция для установки фонового изображения
function setHeaderBackground(bgImage) {
useEffect(() => {
const headerBg = document.querySelector('.header-bg');
if (headerBg && bgImage) {
headerBg.style.backgroundImage = `url('${bgImage}')`;
}
}, [bgImage]);
}
function Header() {
const [siteConfig, setSiteConfig] = useState({
bgImage: '',
title: 'Станция Сосновая',
subtitle: 'База отдыха'
});
useEffect(() => {
const config = getSiteConfigFromHTML();
setSiteConfig(config);
}, []);
// Устанавливаем фоновое изображение
useEffect(() => {
const headerBg = document.querySelector('.header-bg');
if (headerBg && siteConfig.bgImage) {
headerBg.style.backgroundImage = `url('${siteConfig.bgImage}')`;
}
}, [siteConfig.bgImage]);
return (
<header className="header">
<div className="header-bg"></div>
<div className="header-overlay"></div>
<div className="header-content">
<h1>{siteConfig.title}</h1>
<h2>{siteConfig.subtitle}</h2>
</div>
</header>
);
}
function SafeImage({ src, alt, className, fallback = '/images/placeholder.jpg' }) {
const [imgSrc, setImgSrc] = useState(src);
const handleError = () => {
console.warn(`Не удалось загрузить изображение: ${src}`);
setImgSrc(fallback);
};
return (
<img
src={imgSrc}
alt={alt}
className={className}
onError={handleError}
/>
);
}
function Card({ card, onCardClick }) {
return (
<div className="card" onClick={() => onCardClick(card)}>
<SafeImage
src={card.images[0]}
alt={card.title}
className="card-image"
/>
<div className="card-content">
<h3 className="card-title">{card.title}</h3>
<p className="card-description">{card.short}</p>
</div>
</div>
);
}
function Modal({ card, isOpen, onClose }) {
const [currentImageIndex, setCurrentImageIndex] = useState(0);
useEffect(() => {
if (isOpen) {
setCurrentImageIndex(0);
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
return () => (document.body.style.overflow = '');
}, [isOpen, card]);
if (!isOpen || !card) return null;
const next = (e) => {
e.stopPropagation();
setCurrentImageIndex((i) => (i + 1) % card.images.length);
};
const prev = (e) => {
e.stopPropagation();
setCurrentImageIndex((i) => (i - 1 + card.images.length) % card.images.length);
};
const modalContent = (
<div className="modal" onClick={(e) => e.target === e.currentTarget && onClose()}>
<div className="modal-content">
<div className="modal-header">
<h2 className="modal-title">{card.title}</h2>
<button className="close-button" onClick={onClose}>×</button>
</div>
<div className="carousel-container">
<SafeImage
src={card.images[currentImageIndex]}
className="carousel-image"
alt={card.title}
/>
{card.images.length > 1 && (
<>
<button className="carousel-button prev" onClick={prev}></button>
<button className="carousel-button next" onClick={next}></button>
</>
)}
</div>
<div className="modal-body">
<p className="modal-description">{card.long}</p>
</div>
</div>
</div>
);
return ReactDOM.createPortal(modalContent, document.body);
}
function CardsSection() {
const [selected, setSelected] = useState(null);
const [cardData, setCardData] = useState([]);
const isOpen = Boolean(selected);
// Загружаем данные карточек из HTML при монтировании компонента
useEffect(() => {
const data = getCardDataFromHTML();
setCardData(data);
}, []);
return (
<section className="cards-section">
<div className="container">
<h2 className="section-title">Что есть на участке</h2>
<div className="cards-grid">
{cardData.map((c, i) => (
<Card key={i} card={c} onCardClick={setSelected} />
))}
</div>
</div>
<Modal card={selected} isOpen={isOpen} onClose={() => setSelected(null)} />
</section>
);
}
function Footer() {
const ContactLink = ({ href, children }) => (
<a href={href} target="_blank" rel="noopener noreferrer" className="contact-item">
{children}
</a>
);
const TelegramIcon = (
<svg className="contact-icon" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69a.2.2 0 0 0-.05-.18c-.06-.05-.14-.03-.21-.02-.09.02-1.49.95-4.22 2.79-.4.27-.76.41-1.08.4-.36-.01-1.04-.2-1.55-.37-.63-.2-1.12-.31-1.08-.66.02-.18.27-.36.74-.55 2.92-1.27 4.86-2.11 5.83-2.51 2.78-1.16 3.35-1.36 3.73-1.36.08 0 .27.02.39.12.1.08.13.19.14.27-.01.06.01.24 0 .38z"/>
</svg>
);
const WhatsAppIcon = (
<svg className="contact-icon" viewBox="0 0 24 24">
<path d="M12.04 2c-5.46 0-9.91 4.45-9.91 9.91 0 1.75.46 3.45 1.32 4.95L2.05 22l5.25-1.38c1.45.79 3.08 1.21 4.74 1.21 5.46 0 9.91-4.45 9.91-9.91S17.5 2 12.04 2zm4.52 7.01c.2-.07.4-.02.4.24 0 .41-.12 2.28-.17 2.64-.05.36-.3.42-.6.26-.32-.18-1.37-.6-1.37-.6s-.72-.45-.94-.77c-.24-.32-.39-.66-.44-.87-.04-.2.07-.35.18-.46.1-.1.23-.15.23-.15s.18-.12.81.19c.47.23 1.17.8 1.17.8s.72.35.73.46c.01.11-.07.24-.07.24z"/>
</svg>
);
const PhoneIcon = (
<svg className="contact-icon" viewBox="0 0 24 24">
<path d="M20.01 15.38c-1.23 0-2.42-.2-3.53-.56-.35-.12-.74-.03-1.01.24l-1.57 1.97c-2.83-1.35-5.48-3.9-6.89-6.83l1.95-1.66c.27-.28.35-.67.24-1.02-.37-1.11-.56-2.3-.56-3.53 0-.54-.45-.99-.99-.99H4.19C3.65 3 3 3.24 3 3.99 3 13.28 10.73 21 20.01 21c.71 0 .99-.63.99-1.18v-3.45c0-.54-.45-.99-.99-.99z"/>
</svg>
);
return (
<footer className="footer">
<div className="container">
<div className="footer-content">
<ContactLink href="tel:+79123456789">
{PhoneIcon} +7 (912) 345-67-89
</ContactLink>
<ContactLink href="https://t.me/stanciya_sosnovaya">
{TelegramIcon} Telegram
</ContactLink>
<ContactLink href="https://wa.me/79123456789">
{WhatsAppIcon} WhatsApp
</ContactLink>
</div>
</div>
</footer>
);
}
function App() {
return (
<>
<Header />
<CardsSection />
<Footer />
</>
);
}
ReactDOM.render(<App />, document.getElementById('root'));