diff --git a/admin/admin.css b/admin/admin.css new file mode 100644 index 0000000..4d2b49b --- /dev/null +++ b/admin/admin.css @@ -0,0 +1,3 @@ +#clearSelection:hover { + color: #f14668; +} diff --git a/admin/index.html b/admin/index.html new file mode 100644 index 0000000..ece774f --- /dev/null +++ b/admin/index.html @@ -0,0 +1,246 @@ + + + + + + Admin Panel + + + + + + + + + + + + + + + + + + + diff --git a/gallery.css b/gallery.css new file mode 100644 index 0000000..025b3a6 --- /dev/null +++ b/gallery.css @@ -0,0 +1,425 @@ +.gallery-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 1.25rem; +} +.gallery-item { + position: relative; + border-radius: 12px; + overflow: hidden; + aspect-ratio: 4 / 5; + background: #f7f7f2; + box-shadow: 0 10px 22px rgba(0, 0, 0, 0.14), 0 1px 0 rgba(255, 255, 255, 0.5); + transition: transform 0.2s ease, box-shadow 0.2s ease; + opacity: 0; + transform: translateY(12px) scale(0.98); +} +.gallery-item:hover { + transform: translateY(-2px) scale(1.01); + box-shadow: 0 14px 30px rgba(0, 0, 0, 0.18), 0 2px 0 rgba(255, 255, 255, 0.6); +} +.gallery-item.is-visible { + opacity: 1; + transform: translateY(0) scale(1); + transition: transform 0.28s ease, box-shadow 0.28s ease, opacity 0.28s ease; +} +.gallery-photo, +.gallery-photo img { + width: 100%; + height: 100%; + display: block; +} +.gallery-photo img { + object-fit: cover; + cursor: pointer; +} +.gallery-overlay { + position: absolute; + inset: auto 0 0 0; + background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.65) 100%); + color: #fff; + display: flex; + flex-direction: column; + gap: 0.25rem; + opacity: 0; + padding: 0.75rem 0.8rem; + transition: opacity 0.18s ease; + pointer-events: none; +} +.gallery-item:hover .gallery-overlay, +.gallery-item.touch-active .gallery-overlay { + opacity: 1; +} +.overlay-title { + font-size: 1rem; + font-weight: 800; + line-height: 1.2; +} +.overlay-tags { + display: flex; + flex-wrap: wrap; + gap: 0.3rem; +} +.tag-chip { + display: inline-flex; + align-items: center; + gap: 0.3rem; + padding: 0.2rem 0.45rem; + border-radius: 999px; + background: rgba(255, 255, 255, 0.9); + color: #0e2238; + font-weight: 700; + font-size: 0.68rem; + letter-spacing: 0.01em; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12); +} +.tag-chip i { + font-size: 0.7rem; + color: #0e2238; +} +.filter-btn { + background: #ffffff; + border: 1px solid rgba(0, 0, 0, 0.08); + color: #363636; + font-weight: 700; + border-radius: 999px; + padding: 0.55rem 1rem; + box-shadow: 0 8px 18px rgba(0, 0, 0, 0.12); + transition: all 0.2s ease; +} +.filter-btn:hover { + border-color: rgba(0, 0, 0, 0.18); + color: #000; +} +.filter-btn.is-active { + background: #00c2b8; + color: #0e2238; + border-color: transparent; + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.12); +} +.search-box { + margin-bottom: 1rem; + background: #fff; + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 12px; + box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.5); + position: relative; + overflow: hidden; + padding: 0.9rem 1rem; +} +.filter-scroll { + margin-bottom: 1.1rem; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + padding: 0.25rem 0.35rem; +} +.filter-scroll::-webkit-scrollbar { + display: none; +} +.filter-rows { + display: inline-flex; + flex-direction: column; + gap: 0.45rem; + min-width: 100%; +} +.filter-row { + display: flex; + gap: 0.55rem; + flex-wrap: nowrap; + white-space: nowrap; +} +.filter-row .filter-btn { + white-space: nowrap; +} + +.modal-card { + width: auto; + max-width: 90vw; + max-height: 90vh; + border-radius: 16px; + overflow: visible; + background: #fff; + border: none; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2), 0 0 1px rgba(0,0,0,0.1); + position: relative; + padding: 0; + transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1), opacity 300ms cubic-bezier(0.4, 0, 0.2, 1); +} +.modal-card-head { + display: none; +} +.modal-card-body { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + background: transparent; + padding: 1rem; + border-radius: 16px; +} +.modal-close-btn { + position: absolute; + top: -18px; + right: -18px; + background: #fff; + color: #4a4a4a; + border: 1px solid #dbdbdb; + border-radius: 50%; + width: 40px; + height: 40px; + font-size: 1.5rem; + font-weight: 300; + line-height: 38px; /* vertically center × */ + text-align: center; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); + cursor: pointer; + z-index: 10; + transition: all 0.2s ease; +} +.modal-close-btn:hover { + background: #f5f5f5; + transform: translateY(-1px); + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15); +} +.modal-figure { + width: 100%; + margin: 0; + display: flex; + justify-content: center; +} +.modal-image { + max-width: 100%; + max-height: calc(90vh - 120px); + border-radius: 12px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); + background: #f5f5f5; + border: none; + transform: translate(var(--modal-img-translate-x, 0), var(--modal-img-translate-y, 0)) scale(var(--modal-img-scale, 0.95)); + opacity: 0; +} +.modal-caption-block { + width: 100%; + background: transparent; + border: none; + color: #1a1a1a; + border-radius: 0; + padding: 0.5rem 0.25rem 0; + box-shadow: none; + display: flex; + flex-direction: column; + gap: 0.5rem; + text-align: center; +} +.modal-caption-title { + font-weight: 700; + letter-spacing: 0; + font-size: 1.1rem; + margin: 0; + color: #1a1a1a; +} +.modal-caption-tags { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + justify-content: center; +} +.modal-caption-tags .tag-chip { + background: #f0f0f0; + color: #5a5a5a; + box-shadow: none; + border: 1px solid #e0e0e0; + font-weight: 600; +} +.modal .modal-background { + background: rgba(18, 18, 18, 0.65); + backdrop-filter: blur(10px) saturate(120%); + opacity: 0; + transition: opacity 300ms ease; +} +.modal.show-bg .modal-background { + opacity: 1; +} +.modal.chrome-hidden .modal-card { + transform: scale(0.95); + opacity: 0; +} +.modal.is-active .modal-image { + animation: modalZoomIn 380ms cubic-bezier(0.4, 0, 0.2, 1) forwards; + transform-origin: center center; +} +.modal.is-active .modal-card { + animation: popIn 300ms cubic-bezier(0.4, 0, 0.2, 1) 50ms forwards; +} +.gallery-hero { + background: #00c2b8; + position: relative; + overflow: hidden; +} +.gallery-hero .hero-body { + position: relative; + padding-top: 3rem; + padding-bottom: 3rem; +} +.gallery-kicker { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + border-radius: 999px; + background: rgba(255, 255, 255, 0.18); + border: 1px solid rgba(255, 255, 255, 0.25); + color: #0e2238; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; +} +.gallery-wrap { + background: #e7e6dd; +} +.hero-title-accent { + display: inline-flex; + align-items: center; + gap: 0.4rem; + background: rgba(255, 255, 255, 0.16); + padding: 0.4rem 0.8rem; + border-radius: 12px; +} +.hero-title-accent i { + color: #0e2238; + background: #fef6e4; + border-radius: 50%; + padding: 0.35rem; +} +.gallery-meta { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + margin-bottom: 1rem; + flex-wrap: wrap; +} +.result-count { + font-weight: 700; + color: #0e2238; +} +.result-count span { + color: #00c2b8; +} +.search-field .input { + border-radius: 8px; + padding-left: 2.5rem; + border: 1px solid rgba(0, 0, 0, 0.12); + box-shadow: none; + height: 2.6rem; + font-size: 0.95rem; +} +.search-field .icon.is-left { + top: 0.1rem; + height: 2.6rem; + display: flex; + align-items: center; + color: #0e2238; +} +.search-field .input:focus { + border-color: rgba(0, 0, 0, 0.2); + box-shadow: 0 0 0 0.08rem rgba(0, 0, 0, 0.08); +} +.help { + color: #5f7287; + font-size: 0.8rem; +} +.card .title.is-5 { + color: #0e2238; +} +.card .subtitle.is-7 { + color: #73859c; + letter-spacing: 0.01em; +} +.skip-to-gallery { + display: inline-flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 1rem; + background: #0e2238; + color: #fff; + padding: 0.55rem 1rem; + border-radius: 999px; + border: none; + box-shadow: 0 10px 24px rgba(0, 0, 0, 0.16), 0 2px 0 rgba(255, 255, 255, 0.5); + text-decoration: none; + font-weight: 700; + letter-spacing: 0.01em; +} +.skip-to-gallery i { + color: #00c2b8; +} +body.modal-open { + overflow: hidden; +} +body.modal-open main, +body.modal-open nav, +body.modal-open footer { + pointer-events: none; +} +body.modal-open #top { + display: none; +} +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} +@keyframes popIn { + from { transform: translateY(20px) scale(0.95); opacity: 0; } + to { transform: translateY(0) scale(1); opacity: 1; } +} +@keyframes modalZoomIn { + from { opacity: 0; transform: scale(0.9); filter: blur(4px); } + to { opacity: 1; transform: scale(1); filter: blur(0); } +} +@media screen and (max-width: 768px) { + .hero.gallery-hero .hero-body { + padding-top: 2.5rem; + padding-bottom: 2.5rem; + } + .gallery-wrap { + padding: 1rem; + } + .gallery-grid { + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 0.9rem; + } + .gallery-meta { + justify-content: center; + text-align: center; + } + .search-field { + width: 100%; + margin-top: 0.75rem; + } +} + +.skeleton-item { + border-radius: 12px; + overflow: hidden; + aspect-ratio: 4 / 5; + background: #e0e0e0; + position: relative; +} + +.skeleton-item::before { + content: ''; + position: absolute; + top: 0; + left: -150%; + width: 150%; + height: 100%; + background: linear-gradient(to right, transparent 0%, #f0f0f0 50%, transparent 100%); + animation: shimmer 1.5s infinite; +} + +@keyframes shimmer { + 100% { + left: 150%; + } +} \ No newline at end of file diff --git a/gallery.html b/gallery.html new file mode 100644 index 0000000..08da605 --- /dev/null +++ b/gallery.html @@ -0,0 +1,175 @@ + + + + + + + + + + + + + Beach Party Balloons - Gallery + + + + + + + + + +
+
+
+ +
+
+

Gallery

+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+ + + + + + + + + diff --git a/script.js b/script.js index c965087..9273889 100644 --- a/script.js +++ b/script.js @@ -56,6 +56,9 @@ document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () { + if (document.getElementById('photo-gallery')) { + return; + } let images = Array.from(document.querySelectorAll(".gallery-item a")); let lightboxImages = images.map(img => img.getAttribute("href")); let currentIndex = 0; @@ -133,10 +136,13 @@ document.addEventListener("DOMContentLoaded", function () { } }); -document.querySelector(".lightbox .close").addEventListener("click", function(event) { - event.preventDefault(); - document.querySelector(".lightbox").style.display = "none"; -}); + const lightboxClose = document.querySelector(".lightbox .close"); + if (lightboxClose) { + lightboxClose.addEventListener("click", function(event) { + event.preventDefault(); + document.querySelector(".lightbox").style.display = "none"; + }); + } // Swipe gestures for mobile @@ -163,25 +169,41 @@ document.querySelector(".lightbox .close").addEventListener("click", function(ev }); -// Get the button: -let mybutton = document.getElementById("top"); +// Back-to-top button (guard for pages without the element) +(() => { + const mybutton = document.getElementById("top"); + // Provide a global handler for legacy onclick usage even if button is absent + window.topFunction = () => { + document.documentElement.classList.remove('is-clipped'); + document.body.classList.remove('modal-open'); + window.scrollTo({ top: 0, left: 0, behavior: 'smooth' }); + // Ensure both scrolling contexts reset (covers iOS/Android quirks) + document.body.scrollTop = 0; + document.documentElement.scrollTop = 0; + }; -// When the user scrolls down 20px from the top of the document, show the button -window.onscroll = function() {scrollFunction()}; + if (!mybutton) return; -function scrollFunction() { - if (document.body.scrollTop > 130 || document.documentElement.scrollTop > 130) { - mybutton.style.display = "block"; - } else { - mybutton.style.display = "none"; + function scrollFunction() { + if (document.body.classList.contains('modal-open')) { + mybutton.style.display = "none"; + return; + } + const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; + if (scrollTop > 130) { + mybutton.style.display = "block"; + } else { + mybutton.style.display = "none"; + } } -} -// When the user clicks on the button, scroll to the top of the document -function topFunction() { - document.body.scrollTop = 0; // For Safari - document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE, and Opera -} + window.addEventListener('scroll', scrollFunction); + + mybutton.addEventListener('click', (e) => { + e.preventDefault(); + window.topFunction(); + }); +})(); // The open/closed sign function is now in update.js