sitewww/casamento/main.js
vinicius 2ac0e030e0
Some checks failed
Deploy para Servidor de Produção / Deploy para (push) Has been cancelled
Deploy para Servidor de Produção / Determinar ambiente de destino (push) Failing after 5415h48m32s
update contendo a galeria do casamento
2026-01-18 16:03:22 -03:00

232 lines
6.8 KiB
JavaScript

// Load manifest and populate galleries with intelligent masonry layout
(async () => {
const manifest = await fetch("manifest.json").then((r) => r.json());
const imageTemplate = document.getElementById("media-template");
const videoTemplate = document.getElementById("video-template");
let currentIsMobile = window.innerWidth < 768;
// Intersection Observer for scroll-triggered animations
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("loaded");
observer.unobserve(entry.target);
}
});
},
{
rootMargin: "0px 0px -16.67% 0px",
},
);
// Function to get image dimensions
const getImageDimensions = (src) => {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve({ width: img.width, height: img.height });
img.onerror = () => resolve({ width: 1, height: 1 }); // Default to square
img.src = src;
});
};
// Function to pack items into rows
const packIntoRows = (items, isMobile) => {
if (isMobile) {
// Mobile: each item gets its own row, full width, no sizing classes
return items.map((item) => [item]);
}
const rows = [];
let i = 0;
while (i < items.length) {
const current = items[i];
// Try to fill a row intelligently
if (current.isLandscape) {
// Check if next item is also landscape
if (i + 1 < items.length && items[i + 1].isLandscape) {
// Two landscapes
rows.push([
{ ...current, className: "landscape-half" },
{ ...items[i + 1], className: "landscape-half" },
]);
i += 2;
} else if (i + 1 < items.length && !items[i + 1].isLandscape) {
// One landscape + one portrait
rows.push([
{ ...current, className: "landscape-with-portrait" },
{ ...items[i + 1], className: "portrait-with-landscape" },
]);
i += 2;
} else {
// Single landscape - centered
rows.push([{ ...current, className: "single-landscape" }]);
i += 1;
}
} else {
// Portrait - try to group optimally
const portraits = [];
let j = i;
// Count how many portraits are available
while (j < items.length && !items[j].isLandscape) {
j++;
}
const portraitCount = j - i;
// Smart grouping to avoid single portraits
if (portraitCount >= 3) {
// Group 3 together
portraits.push({ ...items[i], className: "portrait-third" });
portraits.push({ ...items[i + 1], className: "portrait-third" });
portraits.push({ ...items[i + 2], className: "portrait-third" });
i += 3;
} else if (portraitCount === 2) {
// Group 2 together
portraits.push({ ...items[i], className: "portrait-double" });
portraits.push({ ...items[i + 1], className: "portrait-double" });
i += 2;
} else {
// Single portrait - centered with constrained width
portraits.push({ ...items[i], className: "single-portrait" });
i += 1;
}
rows.push(portraits);
}
}
return rows;
};
// Store processed items for each section
const sectionItems = new Map();
// Function to render a section
const renderSection = (section, items, isMobile) => {
const grid = section.querySelector(".media-grid");
// Clear existing content
grid.innerHTML = "";
// Pack items into rows
const rows = packIntoRows(items, isMobile);
// Render rows
const fragment = document.createDocumentFragment();
rows.forEach((row) => {
const rowDiv = document.createElement("div");
rowDiv.className = "media-row";
row.forEach((item) => {
const template = item.isVideo ? videoTemplate : imageTemplate;
const clone = template.content.cloneNode(true);
if (item.isVideo) {
// Videos are not wrapped in links
const container = clone.querySelector(".media-item");
const video = clone.querySelector("video");
video.src = item.src;
// Try to use a poster image if it exists
const posterPath = item.src.replace(
/\.(mp4|mov|avi|webm|mkv)$/i,
".jpg",
);
video.poster = posterPath;
if (item.className) {
container.classList.add(item.className);
}
} else {
// Images wrapped in download links
const link = clone.querySelector("a");
const media = clone.querySelector("img");
link.href = item.src;
link.download = item.file.name;
media.src = item.src;
media.alt = `Foto - ${item.file.name}`;
if (item.className) {
link.classList.add(item.className);
}
}
rowDiv.appendChild(clone);
});
fragment.appendChild(rowDiv);
});
grid.appendChild(fragment);
// Observe all media items for scroll animation
grid
.querySelectorAll(".media-item")
.forEach((item) => observer.observe(item));
};
// Process and render all sections
for (const section of document.querySelectorAll("section[data-dir]")) {
const dir = section.dataset.dir;
const files = manifest[dir];
if (!files || files.length === 0) continue;
// Preload and classify all items
const items = await Promise.all(
files.map(async (file) => {
const isVideo = /\.(mp4|mov|avi|webm|mkv)$/i.test(file.name);
const src = `conteudo/${dir}/${file.name}`;
let isLandscape = true;
if (!isVideo) {
const dims = await getImageDimensions(src);
isLandscape = dims.width > dims.height;
}
return {
file,
isVideo,
isLandscape,
src,
dir,
};
}),
);
// Store items for this section
sectionItems.set(section, items);
// Initial render
renderSection(section, items, currentIsMobile);
}
// Handle window resize
let resizeTimeout;
window.addEventListener("resize", () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
const newIsMobile = window.innerWidth < 768;
// Only re-render if we crossed the breakpoint
if (newIsMobile !== currentIsMobile) {
currentIsMobile = newIsMobile;
// Re-render all sections
document.querySelectorAll("section[data-dir]").forEach((section) => {
const items = sectionItems.get(section);
if (items) {
renderSection(section, items, currentIsMobile);
}
});
}
}, 250); // Debounce resize events
});
})();