Beta-2.1 - The clearer, the faster #22
@@ -1,19 +1,14 @@
 | 
				
			|||||||
// --- Arrays to store gallery and hero images ---
 | 
					 | 
				
			||||||
let galleryImages = [];
 | 
					let galleryImages = [];
 | 
				
			||||||
let heroImages = [];
 | 
					let heroImages = [];
 | 
				
			||||||
let allTags = []; // global tag list
 | 
					let allTags = [];
 | 
				
			||||||
let showOnlyUntagged = false;
 | 
					let showOnlyUntagged = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Fade-in helper ---
 | 
					// --- Fade-in helper ---
 | 
				
			||||||
function applyFadeInImages(container) {
 | 
					function applyFadeInImages(container) {
 | 
				
			||||||
  const fadeImages = container.querySelectorAll("img.fade-in-img");
 | 
					  container.querySelectorAll("img.fade-in-img").forEach(img => {
 | 
				
			||||||
  fadeImages.forEach(img => {
 | 
					    const onLoad = () => img.classList.add("loaded");
 | 
				
			||||||
    const onLoad = () => {
 | 
					    if (img.complete && img.naturalHeight !== 0) onLoad();
 | 
				
			||||||
      img.classList.add("loaded");
 | 
					    else {
 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    if (img.complete && img.naturalHeight !== 0) {
 | 
					 | 
				
			||||||
      onLoad();
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      img.addEventListener("load", onLoad, { once: true });
 | 
					      img.addEventListener("load", onLoad, { once: true });
 | 
				
			||||||
      img.addEventListener("error", () => {
 | 
					      img.addEventListener("error", () => {
 | 
				
			||||||
        console.warn("Image failed to load:", img.dataset?.src || img.src);
 | 
					        console.warn("Image failed to load:", img.dataset?.src || img.src);
 | 
				
			||||||
@@ -25,13 +20,10 @@ function applyFadeInImages(container) {
 | 
				
			|||||||
// --- Load images from server on page load ---
 | 
					// --- Load images from server on page load ---
 | 
				
			||||||
async function loadData() {
 | 
					async function loadData() {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const galleryRes = await fetch('/api/gallery');
 | 
					    galleryImages = await (await fetch('/api/gallery')).json();
 | 
				
			||||||
    galleryImages = await galleryRes.json();
 | 
					 | 
				
			||||||
    updateAllTags();
 | 
					    updateAllTags();
 | 
				
			||||||
    renderGallery();
 | 
					    renderGallery();
 | 
				
			||||||
 | 
					    heroImages = await (await fetch('/api/hero')).json();
 | 
				
			||||||
    const heroRes = await fetch('/api/hero');
 | 
					 | 
				
			||||||
    heroImages = await heroRes.json();
 | 
					 | 
				
			||||||
    renderHero();
 | 
					    renderHero();
 | 
				
			||||||
  } catch(err) {
 | 
					  } catch(err) {
 | 
				
			||||||
    console.error(err);
 | 
					    console.error(err);
 | 
				
			||||||
@@ -43,31 +35,44 @@ async function loadData() {
 | 
				
			|||||||
function updateAllTags() {
 | 
					function updateAllTags() {
 | 
				
			||||||
  allTags = [];
 | 
					  allTags = [];
 | 
				
			||||||
  galleryImages.forEach(img => {
 | 
					  galleryImages.forEach(img => {
 | 
				
			||||||
    if (img.tags) img.tags.forEach(t => {
 | 
					    (img.tags || []).forEach(t => {
 | 
				
			||||||
      if (!allTags.includes(t)) allTags.push(t);
 | 
					      if (!allTags.includes(t)) allTags.push(t);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// --- Helper: update count and button visibility ---
 | 
				
			||||||
 | 
					function updateCountAndButtons(prefix, count) {
 | 
				
			||||||
 | 
					  const countTop = document.getElementById(`${prefix}-count`);
 | 
				
			||||||
 | 
					  const countBottom = document.getElementById(`${prefix}-count-bottom`);
 | 
				
			||||||
 | 
					  if (countTop) countTop.innerHTML = `<p>${count} photos</p>`;
 | 
				
			||||||
 | 
					  if (countBottom) countBottom.innerHTML = `<p>${count} photos</p>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const removeAllBtn = document.getElementById(`remove-all-${prefix}`);
 | 
				
			||||||
 | 
					  const removeAllBtnBottom = document.getElementById(`remove-all-${prefix}-bottom`);
 | 
				
			||||||
 | 
					  if (removeAllBtn) removeAllBtn.style.display = count > 0 ? 'inline-block' : 'none';
 | 
				
			||||||
 | 
					  if (removeAllBtnBottom) removeAllBtnBottom.style.display = count > 0 ? 'inline-block' : 'none';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const bottomUpload = document.getElementById(`bottom-${prefix}-upload`);
 | 
				
			||||||
 | 
					  if (bottomUpload) bottomUpload.style.display = count > 0 ? 'flex' : 'none';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Render gallery images with tags and delete buttons ---
 | 
					// --- Render gallery images with tags and delete buttons ---
 | 
				
			||||||
function renderGallery() {
 | 
					function renderGallery() {
 | 
				
			||||||
  const container = document.getElementById('gallery');
 | 
					  const container = document.getElementById('gallery');
 | 
				
			||||||
  container.innerHTML = '';
 | 
					  container.innerHTML = '';
 | 
				
			||||||
 | 
					  let imagesToShow = showOnlyUntagged
 | 
				
			||||||
  // Filter images if toggle is on
 | 
					    ? galleryImages.filter(img => !img.tags || img.tags.length === 0)
 | 
				
			||||||
  let imagesToShow = galleryImages;
 | 
					    : galleryImages;
 | 
				
			||||||
  if (showOnlyUntagged) {
 | 
					 | 
				
			||||||
    imagesToShow = galleryImages.filter(img => !img.tags || img.tags.length === 0);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  imagesToShow.forEach((img) => {
 | 
					  imagesToShow.forEach((img) => {
 | 
				
			||||||
    const i = galleryImages.indexOf(img); // Use real index for tag/delete actions
 | 
					    const i = galleryImages.indexOf(img);
 | 
				
			||||||
    const div = document.createElement('div');
 | 
					    const div = document.createElement('div');
 | 
				
			||||||
    div.className = 'photo flex-item flex-column';
 | 
					    div.className = 'photo flex-item flex-column';
 | 
				
			||||||
    div.innerHTML = `
 | 
					    div.innerHTML = `
 | 
				
			||||||
        <div class="flex-item">
 | 
					      <div class="flex-item">
 | 
				
			||||||
          <img class="fade-in-img" src="/photos/${img.src}">
 | 
					        <img class="fade-in-img" src="/photos/${img.src}">
 | 
				
			||||||
        </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="tags-display" data-index="${i}"></div>
 | 
					      <div class="tags-display" data-index="${i}"></div>
 | 
				
			||||||
      <div class="flex-item flex-full">
 | 
					      <div class="flex-item flex-full">
 | 
				
			||||||
        <div class="flex-item flex-end">
 | 
					        <div class="flex-item flex-end">
 | 
				
			||||||
@@ -77,41 +82,10 @@ function renderGallery() {
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
    container.appendChild(div);
 | 
					    container.appendChild(div);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    renderTags(i, img.tags || []);
 | 
					    renderTags(i, img.tags || []);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Update gallery count (top)
 | 
					  updateCountAndButtons('gallery', imagesToShow.length);
 | 
				
			||||||
  const galleryCount = document.getElementById('gallery-count');
 | 
					 | 
				
			||||||
  if (galleryCount) {
 | 
					 | 
				
			||||||
    galleryCount.innerHTML = `<p>${imagesToShow.length} photos</p>`;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Update gallery count (bottom)
 | 
					 | 
				
			||||||
  const galleryCountBottom = document.getElementById('gallery-count-bottom');
 | 
					 | 
				
			||||||
  if (galleryCountBottom) {
 | 
					 | 
				
			||||||
    galleryCountBottom.innerHTML = `<p>${imagesToShow.length} photos</p>`;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Show/hide Remove All button (top)
 | 
					 | 
				
			||||||
  const removeAllBtn = document.getElementById('remove-all-gallery');
 | 
					 | 
				
			||||||
  if (removeAllBtn) {
 | 
					 | 
				
			||||||
    removeAllBtn.style.display = imagesToShow.length > 0 ? 'inline-block' : 'none';
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Show/hide bottom upload row
 | 
					 | 
				
			||||||
  const bottomGalleryUpload = document.getElementById('bottom-gallery-upload');
 | 
					 | 
				
			||||||
  if (bottomGalleryUpload) {
 | 
					 | 
				
			||||||
    bottomGalleryUpload.style.display = imagesToShow.length > 0 ? 'flex' : 'none';
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Show/hide Remove All button (bottom)
 | 
					 | 
				
			||||||
  const removeAllBtnBottom = document.getElementById('remove-all-gallery-bottom');
 | 
					 | 
				
			||||||
  if (removeAllBtnBottom) {
 | 
					 | 
				
			||||||
    removeAllBtnBottom.style.display = imagesToShow.length > 0 ? 'inline-block' : 'none';
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Fade-in effect for loaded images
 | 
					 | 
				
			||||||
  applyFadeInImages(container);
 | 
					  applyFadeInImages(container);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -119,7 +93,6 @@ function renderGallery() {
 | 
				
			|||||||
function renderTags(imgIndex, tags) {
 | 
					function renderTags(imgIndex, tags) {
 | 
				
			||||||
  const tagsDisplay = document.querySelector(`.tags-display[data-index="${imgIndex}"]`);
 | 
					  const tagsDisplay = document.querySelector(`.tags-display[data-index="${imgIndex}"]`);
 | 
				
			||||||
  const inputContainer = document.querySelector(`.tag-input[data-index="${imgIndex}"]`);
 | 
					  const inputContainer = document.querySelector(`.tag-input[data-index="${imgIndex}"]`);
 | 
				
			||||||
 | 
					 | 
				
			||||||
  tagsDisplay.innerHTML = '';
 | 
					  tagsDisplay.innerHTML = '';
 | 
				
			||||||
  inputContainer.innerHTML = '';
 | 
					  inputContainer.innerHTML = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -127,7 +100,6 @@ function renderTags(imgIndex, tags) {
 | 
				
			|||||||
    const span = document.createElement('span');
 | 
					    const span = document.createElement('span');
 | 
				
			||||||
    span.className = 'tag';
 | 
					    span.className = 'tag';
 | 
				
			||||||
    span.textContent = tag;
 | 
					    span.textContent = tag;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const remove = document.createElement('span');
 | 
					    const remove = document.createElement('span');
 | 
				
			||||||
    remove.className = 'remove-tag';
 | 
					    remove.className = 'remove-tag';
 | 
				
			||||||
    remove.textContent = '×';
 | 
					    remove.textContent = '×';
 | 
				
			||||||
@@ -136,7 +108,6 @@ function renderTags(imgIndex, tags) {
 | 
				
			|||||||
      updateTags(imgIndex, tags);
 | 
					      updateTags(imgIndex, tags);
 | 
				
			||||||
      renderTags(imgIndex, tags);
 | 
					      renderTags(imgIndex, tags);
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					 | 
				
			||||||
    span.appendChild(remove);
 | 
					    span.appendChild(remove);
 | 
				
			||||||
    tagsDisplay.appendChild(span);
 | 
					    tagsDisplay.appendChild(span);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
@@ -146,11 +117,10 @@ function renderTags(imgIndex, tags) {
 | 
				
			|||||||
  input.placeholder = 'Add tag...';
 | 
					  input.placeholder = 'Add tag...';
 | 
				
			||||||
  inputContainer.appendChild(input);
 | 
					  inputContainer.appendChild(input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // --- Validate button ---
 | 
					 | 
				
			||||||
  const validateBtn = document.createElement('button');
 | 
					  const validateBtn = document.createElement('button');
 | 
				
			||||||
  validateBtn.textContent = '✔️';
 | 
					  validateBtn.textContent = '✔️';
 | 
				
			||||||
  validateBtn.className = 'validate-tag-btn';
 | 
					  validateBtn.className = 'validate-tag-btn';
 | 
				
			||||||
  validateBtn.style.display = 'none'; // hidden by default
 | 
					  validateBtn.style.display = 'none';
 | 
				
			||||||
  validateBtn.style.marginLeft = '4px';
 | 
					  validateBtn.style.marginLeft = '4px';
 | 
				
			||||||
  inputContainer.appendChild(validateBtn);
 | 
					  inputContainer.appendChild(validateBtn);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -170,30 +140,20 @@ function renderTags(imgIndex, tags) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const updateSuggestions = () => {
 | 
					  const updateSuggestions = () => {
 | 
				
			||||||
    const value = input.value.toLowerCase();
 | 
					    const value = input.value.toLowerCase();
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const allTagsFlat = galleryImages.flatMap(img => img.tags || []);
 | 
					    const allTagsFlat = galleryImages.flatMap(img => img.tags || []);
 | 
				
			||||||
    const tagCount = {};
 | 
					    const tagCount = {};
 | 
				
			||||||
    allTagsFlat.forEach(t => tagCount[t] = (tagCount[t] || 0) + 1);
 | 
					    allTagsFlat.forEach(t => tagCount[t] = (tagCount[t] || 0) + 1);
 | 
				
			||||||
 | 
					    const allTagsSorted = Object.keys(tagCount).sort((a, b) => tagCount[b] - tagCount[a]);
 | 
				
			||||||
    const allTagsSorted = Object.keys(tagCount)
 | 
					 | 
				
			||||||
      .sort((a, b) => tagCount[b] - tagCount[a]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const suggestions = allTagsSorted.filter(t => t.toLowerCase().startsWith(value) && !tags.includes(t));
 | 
					    const suggestions = allTagsSorted.filter(t => t.toLowerCase().startsWith(value) && !tags.includes(t));
 | 
				
			||||||
 | 
					 | 
				
			||||||
    suggestionBox.innerHTML = '';
 | 
					    suggestionBox.innerHTML = '';
 | 
				
			||||||
    selectedIndex = -1;
 | 
					    selectedIndex = -1;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (suggestions.length) {
 | 
					    if (suggestions.length) {
 | 
				
			||||||
      suggestionBox.style.display = 'block';
 | 
					      suggestionBox.style.display = 'block';
 | 
				
			||||||
      suggestions.forEach((s, idx) => {
 | 
					      suggestions.forEach((s, idx) => {
 | 
				
			||||||
        const li = document.createElement('li');
 | 
					        const li = document.createElement('li');
 | 
				
			||||||
        li.style.fontStyle = 'italic';
 | 
					        li.style.fontStyle = 'italic';
 | 
				
			||||||
        li.style.textAlign = 'left';
 | 
					        li.style.textAlign = 'left';
 | 
				
			||||||
 | 
					        li.innerHTML = `<b>${s.substring(0, input.value.length)}</b>${s.substring(input.value.length)}`;
 | 
				
			||||||
        const boldPart = `<b>${s.substring(0, input.value.length)}</b>`;
 | 
					 | 
				
			||||||
        const rest = s.substring(input.value.length);
 | 
					 | 
				
			||||||
        li.innerHTML = boldPart + rest;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        li.addEventListener('mousedown', (e) => {
 | 
					        li.addEventListener('mousedown', (e) => {
 | 
				
			||||||
          e.preventDefault();
 | 
					          e.preventDefault();
 | 
				
			||||||
          addTag(s);
 | 
					          addTag(s);
 | 
				
			||||||
@@ -201,7 +161,6 @@ function renderTags(imgIndex, tags) {
 | 
				
			|||||||
          input.focus();
 | 
					          input.focus();
 | 
				
			||||||
          updateSuggestions();
 | 
					          updateSuggestions();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					 | 
				
			||||||
        li.onmouseover = () => selectedIndex = idx;
 | 
					        li.onmouseover = () => selectedIndex = idx;
 | 
				
			||||||
        suggestionBox.appendChild(li);
 | 
					        suggestionBox.appendChild(li);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
@@ -218,7 +177,6 @@ function renderTags(imgIndex, tags) {
 | 
				
			|||||||
    updateSuggestions();
 | 
					    updateSuggestions();
 | 
				
			||||||
    validateBtn.style.display = input.value.trim() ? 'inline-block' : 'none';
 | 
					    validateBtn.style.display = input.value.trim() ? 'inline-block' : 'none';
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					 | 
				
			||||||
  input.addEventListener('keydown', (e) => {
 | 
					  input.addEventListener('keydown', (e) => {
 | 
				
			||||||
    const items = suggestionBox.querySelectorAll('li');
 | 
					    const items = suggestionBox.querySelectorAll('li');
 | 
				
			||||||
    if (e.key === 'ArrowDown') {
 | 
					    if (e.key === 'ArrowDown') {
 | 
				
			||||||
@@ -249,26 +207,22 @@ function renderTags(imgIndex, tags) {
 | 
				
			|||||||
      validateBtn.style.display = 'none';
 | 
					      validateBtn.style.display = 'none';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					 | 
				
			||||||
  input.addEventListener('blur', () => {
 | 
					  input.addEventListener('blur', () => {
 | 
				
			||||||
    suggestionBox.style.display = 'none';
 | 
					    suggestionBox.style.display = 'none';
 | 
				
			||||||
    input.value = '';
 | 
					    input.value = '';
 | 
				
			||||||
    validateBtn.style.display = 'none';
 | 
					    validateBtn.style.display = 'none';
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					  validateBtn.addEventListener('mousedown', (e) => {
 | 
				
			||||||
  // --- Validate button action ---
 | 
					    e.preventDefault();
 | 
				
			||||||
  validateBtn.onclick = () => {
 | 
					 | 
				
			||||||
    if (input.value.trim()) {
 | 
					    if (input.value.trim()) {
 | 
				
			||||||
      addTag(input.value.trim());
 | 
					      addTag(input.value.trim());
 | 
				
			||||||
      input.value = '';
 | 
					      input.value = '';
 | 
				
			||||||
      updateSuggestions();
 | 
					      updateSuggestions();
 | 
				
			||||||
      validateBtn.style.display = 'none';
 | 
					      validateBtn.style.display = 'none';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  });
 | 
				
			||||||
  updateSuggestions();
 | 
					  updateSuggestions();
 | 
				
			||||||
  if (!input.value.trim()) {
 | 
					  if (!input.value.trim()) suggestionBox.style.display = 'none';
 | 
				
			||||||
    suggestionBox.style.display = 'none';
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Update tags in galleryImages array ---
 | 
					// --- Update tags in galleryImages array ---
 | 
				
			||||||
@@ -296,38 +250,7 @@ function renderHero() {
 | 
				
			|||||||
    `;
 | 
					    `;
 | 
				
			||||||
    container.appendChild(div);
 | 
					    container.appendChild(div);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					  updateCountAndButtons('hero', heroImages.length);
 | 
				
			||||||
  // Update hero count (top)
 | 
					 | 
				
			||||||
  const heroCount = document.getElementById('hero-count');
 | 
					 | 
				
			||||||
  if (heroCount) {
 | 
					 | 
				
			||||||
    heroCount.innerHTML = `<p>${heroImages.length} photos</p>`;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Update hero count (bottom)
 | 
					 | 
				
			||||||
  const heroCountBottom = document.getElementById('hero-count-bottom');
 | 
					 | 
				
			||||||
  if (heroCountBottom) {
 | 
					 | 
				
			||||||
    heroCountBottom.innerHTML = `<p>${heroImages.length} photos</p>`;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Show/hide Remove All button (top)
 | 
					 | 
				
			||||||
  const removeAllBtn = document.getElementById('remove-all-hero');
 | 
					 | 
				
			||||||
  if (removeAllBtn) {
 | 
					 | 
				
			||||||
    removeAllBtn.style.display = heroImages.length > 0 ? 'inline-block' : 'none';
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Show/hide bottom upload row
 | 
					 | 
				
			||||||
  const bottomHeroUpload = document.getElementById('bottom-hero-upload');
 | 
					 | 
				
			||||||
  if (bottomHeroUpload) {
 | 
					 | 
				
			||||||
    bottomHeroUpload.style.display = heroImages.length > 0 ? 'flex' : 'none';
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Show/hide Remove All button (bottom)
 | 
					 | 
				
			||||||
  const removeAllBtnBottom = document.getElementById('remove-all-hero-bottom');
 | 
					 | 
				
			||||||
  if (removeAllBtnBottom) {
 | 
					 | 
				
			||||||
    removeAllBtnBottom.style.display = heroImages.length > 0 ? 'inline-block' : 'none';
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Fade-in effect for loaded images
 | 
					 | 
				
			||||||
  applyFadeInImages(container);
 | 
					  applyFadeInImages(container);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -374,33 +297,27 @@ async function refreshHero() {
 | 
				
			|||||||
function showToast(message, type = "success", duration = 3000) {
 | 
					function showToast(message, type = "success", duration = 3000) {
 | 
				
			||||||
  const container = document.getElementById("toast-container");
 | 
					  const container = document.getElementById("toast-container");
 | 
				
			||||||
  if (!container) return;
 | 
					  if (!container) return;
 | 
				
			||||||
 | 
					 | 
				
			||||||
  const toast = document.createElement("div");
 | 
					  const toast = document.createElement("div");
 | 
				
			||||||
  toast.className = `toast ${type}`;
 | 
					  toast.className = `toast ${type}`;
 | 
				
			||||||
  toast.textContent = message;
 | 
					  toast.textContent = message;
 | 
				
			||||||
  container.appendChild(toast);
 | 
					  container.appendChild(toast);
 | 
				
			||||||
 | 
					 | 
				
			||||||
  requestAnimationFrame(() => toast.classList.add("show"));
 | 
					  requestAnimationFrame(() => toast.classList.add("show"));
 | 
				
			||||||
 | 
					 | 
				
			||||||
  setTimeout(() => {
 | 
					  setTimeout(() => {
 | 
				
			||||||
    toast.classList.remove("show");
 | 
					    toast.classList.remove("show");
 | 
				
			||||||
    toast.addEventListener("transitionend", () => toast.remove());
 | 
					    toast.addEventListener("transitionend", () => toast.remove());
 | 
				
			||||||
  }, duration);
 | 
					  }, duration);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let pendingDelete = null; // { type: 'gallery'|'hero'|'gallery-all'|'hero-all', index: number|null }
 | 
					let pendingDelete = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Show delete confirmation modal ---
 | 
					// --- Show delete confirmation modal ---
 | 
				
			||||||
function showDeleteModal(type, index = null) {
 | 
					function showDeleteModal(type, index = null) {
 | 
				
			||||||
  pendingDelete = { type, index };
 | 
					  pendingDelete = { type, index };
 | 
				
			||||||
  const modalText = document.getElementById('delete-modal-text');
 | 
					  const modalText = document.getElementById('delete-modal-text');
 | 
				
			||||||
  if (type === 'gallery-all') {
 | 
					  modalText.textContent =
 | 
				
			||||||
    modalText.textContent = "Are you sure you want to delete ALL gallery images?";
 | 
					    type === 'gallery-all' ? "Are you sure you want to delete ALL gallery images?"
 | 
				
			||||||
  } else if (type === 'hero-all') {
 | 
					    : type === 'hero-all' ? "Are you sure you want to delete ALL hero images?"
 | 
				
			||||||
    modalText.textContent = "Are you sure you want to delete ALL hero images?";
 | 
					    : "Are you sure you want to delete this image?";
 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    modalText.textContent = "Are you sure you want to delete this image?";
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  document.getElementById('delete-modal').style.display = 'flex';
 | 
					  document.getElementById('delete-modal').style.display = 'flex';
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -413,15 +330,10 @@ function hideDeleteModal() {
 | 
				
			|||||||
// --- Confirm deletion ---
 | 
					// --- Confirm deletion ---
 | 
				
			||||||
async function confirmDelete() {
 | 
					async function confirmDelete() {
 | 
				
			||||||
  if (!pendingDelete) return;
 | 
					  if (!pendingDelete) return;
 | 
				
			||||||
  if (pendingDelete.type === 'gallery') {
 | 
					  if (pendingDelete.type === 'gallery') await actuallyDeleteGalleryImage(pendingDelete.index);
 | 
				
			||||||
    await actuallyDeleteGalleryImage(pendingDelete.index);
 | 
					  else if (pendingDelete.type === 'hero') await actuallyDeleteHeroImage(pendingDelete.index);
 | 
				
			||||||
  } else if (pendingDelete.type === 'hero') {
 | 
					  else if (pendingDelete.type === 'gallery-all') await actuallyDeleteAllGalleryImages();
 | 
				
			||||||
    await actuallyDeleteHeroImage(pendingDelete.index);
 | 
					  else if (pendingDelete.type === 'hero-all') await actuallyDeleteAllHeroImages();
 | 
				
			||||||
  } else if (pendingDelete.type === 'gallery-all') {
 | 
					 | 
				
			||||||
    await actuallyDeleteAllGalleryImages();
 | 
					 | 
				
			||||||
  } else if (pendingDelete.type === 'hero-all') {
 | 
					 | 
				
			||||||
    await actuallyDeleteAllHeroImages();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  hideDeleteModal();
 | 
					  hideDeleteModal();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -503,41 +415,36 @@ async function actuallyDeleteAllHeroImages() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// --- Modal event listeners and bulk delete buttons ---
 | 
					// --- Modal event listeners and bulk delete buttons ---
 | 
				
			||||||
document.addEventListener('DOMContentLoaded', () => {
 | 
					document.addEventListener('DOMContentLoaded', () => {
 | 
				
			||||||
  document.getElementById('delete-modal-close').onclick = hideDeleteModal;
 | 
					  ['delete-modal-close', 'delete-modal-cancel'].forEach(id => {
 | 
				
			||||||
  document.getElementById('delete-modal-cancel').onclick = hideDeleteModal;
 | 
					    const el = document.getElementById(id);
 | 
				
			||||||
  document.getElementById('delete-modal-confirm').onclick = confirmDelete;
 | 
					    if (el) el.onclick = hideDeleteModal;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  const confirmBtn = document.getElementById('delete-modal-confirm');
 | 
				
			||||||
 | 
					  if (confirmBtn) confirmBtn.onclick = confirmDelete;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // --- Radio toggle for gallery filter ---
 | 
					  // Gallery filter radios
 | 
				
			||||||
  const showAllRadio = document.getElementById('show-all-radio');
 | 
					  const showAllRadio = document.getElementById('show-all-radio');
 | 
				
			||||||
  const showUntaggedRadio = document.getElementById('show-untagged-radio');
 | 
					  const showUntaggedRadio = document.getElementById('show-untagged-radio');
 | 
				
			||||||
 | 
					  if (showAllRadio) showAllRadio.addEventListener('change', () => {
 | 
				
			||||||
  if (showAllRadio && showUntaggedRadio) {
 | 
					    showOnlyUntagged = false;
 | 
				
			||||||
    showAllRadio.addEventListener('change', () => {
 | 
					    renderGallery();
 | 
				
			||||||
      if (showAllRadio.checked) {
 | 
					  });
 | 
				
			||||||
        showOnlyUntagged = false;
 | 
					  if (showUntaggedRadio) showUntaggedRadio.addEventListener('change', () => {
 | 
				
			||||||
        renderGallery();
 | 
					    showOnlyUntagged = true;
 | 
				
			||||||
      }
 | 
					    renderGallery();
 | 
				
			||||||
    });
 | 
					  });
 | 
				
			||||||
    showUntaggedRadio.addEventListener('change', () => {
 | 
					 | 
				
			||||||
      if (showUntaggedRadio.checked) {
 | 
					 | 
				
			||||||
        showOnlyUntagged = true;
 | 
					 | 
				
			||||||
        renderGallery();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Bulk delete buttons
 | 
					  // Bulk delete buttons
 | 
				
			||||||
  const removeAllGalleryBtn = document.getElementById('remove-all-gallery');
 | 
					  [
 | 
				
			||||||
  const removeAllGalleryBtnBottom = document.getElementById('remove-all-gallery-bottom');
 | 
					    ['remove-all-gallery', 'gallery-all'],
 | 
				
			||||||
  const removeAllHeroBtn = document.getElementById('remove-all-hero');
 | 
					    ['remove-all-gallery-bottom', 'gallery-all'],
 | 
				
			||||||
  const removeAllHeroBtnBottom = document.getElementById('remove-all-hero-bottom');
 | 
					    ['remove-all-hero', 'hero-all'],
 | 
				
			||||||
  if (removeAllGalleryBtn) removeAllGalleryBtn.onclick = () => showDeleteModal('gallery-all');
 | 
					    ['remove-all-hero-bottom', 'hero-all']
 | 
				
			||||||
  if (removeAllGalleryBtnBottom) removeAllGalleryBtnBottom.onclick = () => showDeleteModal('gallery-all');
 | 
					  ].forEach(([btnId, type]) => {
 | 
				
			||||||
  if (removeAllHeroBtn) removeAllHeroBtn.onclick = () => showDeleteModal('hero-all');
 | 
					    const btn = document.getElementById(btnId);
 | 
				
			||||||
  if (removeAllHeroBtnBottom) removeAllHeroBtnBottom.onclick = () => showDeleteModal('hero-all');
 | 
					    if (btn) btn.onclick = () => showDeleteModal(type);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// --- Initialize ---
 | 
					// --- Initialize ---
 | 
				
			||||||
loadData();
 | 
					loadData();
 | 
				
			||||||
		Reference in New Issue
	
	Block a user