4 Commits

Author SHA1 Message Date
b74f1bb350 Site-info front 2025-08-17 13:52:25 +02:00
5a6f08644a Fixed responsive issue 2025-08-17 12:38:29 +02:00
031ff62168 Better css 2025-08-17 11:26:21 +02:00
b56d03303e Remove all button 2025-08-17 10:57:59 +02:00
7 changed files with 575 additions and 127 deletions

View File

@ -24,6 +24,8 @@ footer:
# Build parameters
build:
theme: modern # choose a theme in config/theme folder
convert_images: true # true to enable image conversion
resize_images: true # true to enable image resizing
# Change this by your legals
legals:

View File

@ -1,4 +1,5 @@
import logging
import yaml
from pathlib import Path
from flask import Flask, jsonify, request, send_from_directory, render_template
from src.py.builder.gallery_builder import (
@ -18,6 +19,8 @@ app = Flask(
static_url_path=""
)
SITE_YAML = Path(__file__).resolve().parents[3] / "config" / "site.yaml"
# --- Photos directory (configurable) ---
PHOTOS_DIR = Path(__file__).resolve().parents[3] / "config" / "photos"
app.config["PHOTOS_DIR"] = PHOTOS_DIR
@ -96,11 +99,62 @@ def delete_hero_photo():
return {"status": "ok"}
return {"error": "File not found"}, 404
@app.route("/api/gallery/delete_all", methods=["POST"])
def delete_all_gallery_photos():
"""Delete all gallery photos from disk and YAML."""
gallery_dir = PHOTOS_DIR / "gallery"
deleted = 0
# Remove all files in gallery folder
for file in gallery_dir.glob("*"):
if file.is_file():
file.unlink()
deleted += 1
# Clear YAML gallery images
data = load_yaml(GALLERY_YAML)
data["gallery"]["images"] = []
save_yaml(data, GALLERY_YAML)
return jsonify({"status": "ok", "deleted": deleted})
@app.route("/api/hero/delete_all", methods=["POST"])
def delete_all_hero_photos():
"""Delete all hero photos from disk and YAML."""
hero_dir = PHOTOS_DIR / "hero"
deleted = 0
# Remove all files in hero folder
for file in hero_dir.glob("*"):
if file.is_file():
file.unlink()
deleted += 1
# Clear YAML hero images
data = load_yaml(GALLERY_YAML)
data["hero"]["images"] = []
save_yaml(data, GALLERY_YAML)
return jsonify({"status": "ok", "deleted": deleted})
@app.route("/photos/<section>/<path:filename>")
def photos(section, filename):
"""Serve uploaded photos from disk."""
return send_from_directory(PHOTOS_DIR / section, filename)
@app.route("/site-info")
def site_info():
return render_template("site-info/index.html")
@app.route("/api/site-info", methods=["GET"])
def get_site_info():
with open(SITE_YAML, "r") as f:
data = yaml.safe_load(f)
return jsonify(data)
@app.route("/api/site-info", methods=["POST"])
def update_site_info():
data = request.json
with open(SITE_YAML, "w") as f:
yaml.safe_dump(data, f, sort_keys=False, allow_unicode=True)
return jsonify({"status": "ok"})
# --- Run server ---
if __name__ == "__main__":
logging.info("Starting WebUI at http://127.0.0.1:5000")

View File

@ -1,10 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<title>Photo WebUI</title>
<!-- Link to your CSS in the package -->
<title>Lumeex</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
</head>
<body>
@ -12,10 +11,10 @@
<div class="nav-bar">
<div class="content-inner nav">
<div class="nav-cta">
<div class="arrow">
</div>
<a class="button" href="#" target="_blank"><span id="step">🚀 Build !<i class="fa-solid fa-envelope"></i></span></a>
<div class="arrow"></div>
<a class="button" href="#" target="_blank">
<span id="step">🚀 Build !<i class="fa-solid fa-envelope"></i></span>
</a>
</div>
<input type="checkbox" id="nav-check">
<div class="nav-header">
@ -23,7 +22,6 @@
<img src="{{ url_for('static', filename='img/logo.svg') }}">
</div>
</div>
<div class="nav-btn">
<label for="nav-check">
<span></span>
@ -31,16 +29,13 @@
<span></span>
</label>
</div>
<div class="nav-links">
<ul class="nav-list">
<li class="nav-item appear2"><a href="#">Site info</a>
<li class="nav-item appear2"><a href="#qui-suis-je">Theme info</a>
<li class="nav-item appear2"><a href="#mariages">Gallery</a>
<li class="nav-item appear2"><a href="#">Theme info</a>
<li class="nav-item appear2"><a href="#">Gallery</a>
</ul>
</div>
</div>
</div>
<!-- Toast container for notifications -->
@ -53,9 +48,12 @@
<div class="upload-section">
<h2>Title Carrousel</h2>
<p> Select photos to display in the Title Carrousel</p>
<label for="upload-hero" class="custom-upload-btn">
<div class="upload-actions-row">
<label for="upload-hero" class="up-btn">
📸 Upload photos
</label>
<button id="remove-all-hero" class="up-btn">🗑 Delete all</button>
</div>
<input type="file" id="upload-hero" accept=".png,.jpg,.jpeg,.webp" multiple hidden>
<div id="hero"></div>
</div>
@ -64,16 +62,17 @@
<div class="upload-section">
<h2>Gallery</h2>
<p> Select and tags photos to display in the Gallery</p>
<label for="upload-gallery" class="custom-upload-btn">
<div class="upload-actions-row">
<label for="upload-gallery" class="up-btn">
📸 Upload photos
</label>
<button id="remove-all-gallery" class="up-btn">🗑 Delete all</button>
</div>
<input type="file" id="upload-gallery" accept=".png,.jpg,.jpeg,.webp" multiple hidden>
<div id="gallery"></div>
</div>
<!-- JS files for rendering, uploading, and actions -->
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
<script src="{{ url_for('static', filename='js/upload.js') }}"></script>
<script src="{{ url_for('static', filename='js/main.js') }}" defer></script>
<script src="{{ url_for('static', filename='js/upload.js') }}" defer></script>
</div>
<!-- Delete confirmation modal -->
<div id="delete-modal" class="modal" style="display:none;">

View File

@ -53,6 +53,12 @@ function renderGallery() {
renderTags(i, img.tags || []);
});
// Show/hide Remove All button
const removeAllBtn = document.getElementById('remove-all-gallery');
if (removeAllBtn) {
removeAllBtn.style.display = galleryImages.length > 0 ? 'inline-block' : 'none';
}
}
// --- Render tags for a single image ---
@ -60,11 +66,9 @@ function renderTags(imgIndex, tags) {
const tagsDisplay = document.querySelector(`.tags-display[data-index="${imgIndex}"]`);
const inputContainer = document.querySelector(`.tag-input[data-index="${imgIndex}"]`);
// vider
tagsDisplay.innerHTML = '';
inputContainer.innerHTML = '';
// --- rendre les tags (en haut) ---
tags.forEach(tag => {
const span = document.createElement('span');
span.className = 'tag';
@ -83,13 +87,11 @@ function renderTags(imgIndex, tags) {
tagsDisplay.appendChild(span);
});
// --- input (en bas) ---
const input = document.createElement('input');
input.type = 'text';
input.placeholder = 'Add tag...';
inputContainer.appendChild(input);
// suggestion box
const suggestionBox = document.createElement('ul');
suggestionBox.className = 'suggestions';
inputContainer.appendChild(suggestionBox);
@ -210,11 +212,17 @@ function renderHero() {
<div class="flex-item flex-end">
<button onclick="showDeleteModal('hero', ${i})">🗑 Delete</button>
</div>
</div>
`;
container.appendChild(div);
});
}
// Show/hide Remove All button
const removeAllBtn = document.getElementById('remove-all-hero');
if (removeAllBtn) {
removeAllBtn.style.display = heroImages.length > 0 ? 'inline-block' : 'none';
}
}
// --- Save gallery to server ---
async function saveGallery() {
@ -273,11 +281,19 @@ function showToast(message, type = "success", duration = 3000) {
}, duration);
}
let pendingDelete = null; // { type: 'gallery'|'hero', index: number }
let pendingDelete = null; // { type: 'gallery'|'hero'|'gallery-all'|'hero-all', index: number|null }
// --- Show delete confirmation modal ---
function showDeleteModal(type, index) {
function showDeleteModal(type, index = null) {
pendingDelete = { type, index };
const modalText = document.getElementById('delete-modal-text');
if (type === 'gallery-all') {
modalText.textContent = "Are you sure you want to delete ALL gallery images?";
} else if (type === 'hero-all') {
modalText.textContent = "Are you sure you want to delete ALL hero images?";
} else {
modalText.textContent = "Are you sure you want to delete this image?";
}
document.getElementById('delete-modal').style.display = 'flex';
}
@ -294,18 +310,14 @@ async function confirmDelete() {
await actuallyDeleteGalleryImage(pendingDelete.index);
} else if (pendingDelete.type === 'hero') {
await actuallyDeleteHeroImage(pendingDelete.index);
} else if (pendingDelete.type === 'gallery-all') {
await actuallyDeleteAllGalleryImages();
} else if (pendingDelete.type === 'hero-all') {
await actuallyDeleteAllHeroImages();
}
hideDeleteModal();
}
// --- Modal event listeners ---
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('delete-modal-close').onclick = hideDeleteModal;
document.getElementById('delete-modal-cancel').onclick = hideDeleteModal;
document.getElementById('delete-modal-confirm').onclick = confirmDelete;
});
// --- Actual delete functions ---
async function actuallyDeleteGalleryImage(index) {
const img = galleryImages[index];
@ -349,14 +361,51 @@ async function actuallyDeleteHeroImage(index) {
}
}
// --- Modal event listeners ---
// --- Bulk delete functions ---
async function actuallyDeleteAllGalleryImages() {
try {
const res = await fetch('/api/gallery/delete_all', { method: 'POST' });
const data = await res.json();
if (res.ok) {
galleryImages = [];
renderGallery();
await saveGallery();
showToast("✅ All gallery images removed!", "success");
} else showToast("Error: " + data.error, "error");
} catch(err) {
console.error(err);
showToast("Server error!", "error");
}
}
async function actuallyDeleteAllHeroImages() {
try {
const res = await fetch('/api/hero/delete_all', { method: 'POST' });
const data = await res.json();
if (res.ok) {
heroImages = [];
renderHero();
await saveHero();
showToast("✅ All hero images removed!", "success");
} else showToast("Error: " + data.error, "error");
} catch(err) {
console.error(err);
showToast("Server error!", "error");
}
}
// --- Modal event listeners and bulk delete buttons ---
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('delete-modal-close').onclick = hideDeleteModal;
document.getElementById('delete-modal-cancel').onclick = hideDeleteModal;
document.getElementById('delete-modal-confirm').onclick = confirmDelete;
// Bulk delete buttons
const removeAllGalleryBtn = document.getElementById('remove-all-gallery');
const removeAllHeroBtn = document.getElementById('remove-all-hero');
if (removeAllGalleryBtn) removeAllGalleryBtn.onclick = () => showDeleteModal('gallery-all');
if (removeAllHeroBtn) removeAllHeroBtn.onclick = () => showDeleteModal('hero-all');
});
// --- Initialize ---
loadData();

197
src/webui/js/site-info.js Normal file
View File

@ -0,0 +1,197 @@
document.addEventListener("DOMContentLoaded", () => {
const form = document.getElementById("site-info-form");
const status = document.getElementById("site-info-status");
const menuList = document.getElementById("menu-items-list");
const addMenuBtn = document.getElementById("add-menu-item");
let menuItems = [];
function renderMenuItems() {
menuList.innerHTML = "";
menuItems.forEach((item, idx) => {
const div = document.createElement("div");
div.style.display = "flex";
div.style.gap = "8px";
div.style.marginBottom = "6px";
div.innerHTML = `
<input type="text" placeholder="Label" value="${item.label || ""}" style="flex:1;" data-idx="${idx}" data-type="label">
<input type="text" placeholder="URL" value="${item.href || ""}" style="flex:2;" data-idx="${idx}" data-type="href">
<button type="button" class="remove-menu-item" data-idx="${idx}">🗑</button>
`;
menuList.appendChild(div);
});
}
function updateMenuItemsFromInputs() {
const inputs = menuList.querySelectorAll("input");
const items = [];
for (let i = 0; i < inputs.length; i += 2) {
const label = inputs[i].value.trim();
const href = inputs[i + 1].value.trim();
if (label || href) items.push({ label, href });
}
menuItems = items;
}
const ipList = document.getElementById("ip-list");
const addIpBtn = document.getElementById("add-ip-paragraph");
let ipParagraphs = [];
function renderIpParagraphs() {
ipList.innerHTML = "";
ipParagraphs.forEach((item, idx) => {
const div = document.createElement("div");
div.style.display = "flex";
div.style.gap = "8px";
div.style.marginBottom = "6px";
div.innerHTML = `
<input type="text" placeholder="Paragraph" value="${item.paragraph || ""}" style="flex:1;" data-idx="${idx}">
<button type="button" class="remove-ip-paragraph" data-idx="${idx}">🗑</button>
`;
ipList.appendChild(div);
});
}
function updateIpParagraphsFromInputs() {
const inputs = ipList.querySelectorAll("input");
ipParagraphs = Array.from(inputs).map(input => ({
paragraph: input.value.trim()
})).filter(item => item.paragraph !== "");
}
// --- Build checkboxes ---
const convertImagesCheckbox = document.getElementById("convert-images-checkbox");
const resizeImagesCheckbox = document.getElementById("resize-images-checkbox");
// Load config
if (form) {
fetch("/api/site-info")
.then(res => res.json())
.then(data => {
ipParagraphs = Array.isArray(data.legals?.intellectual_property)
? data.legals.intellectual_property
: [];
renderIpParagraphs();
menuItems = Array.isArray(data.menu?.items) ? data.menu.items : [];
renderMenuItems();
form.elements["info.title"].value = data.info?.title || "";
form.elements["info.subtitle"].value = data.info?.subtitle || "";
form.elements["info.description"].value = data.info?.description || "";
form.elements["info.canonical"].value = data.info?.canonical || "";
form.elements["info.keywords"].value = Array.isArray(data.info?.keywords) ? data.info.keywords.join(", ") : (data.info?.keywords || "");
form.elements["info.author"].value = data.info?.author || "";
form.elements["social.instagram_url"].value = data.social?.instagram_url || "";
form.elements["social.thumbnail"].value = data.social?.thumbnail || "";
form.elements["footer.copyright"].value = data.footer?.copyright || "";
form.elements["footer.legal_label"].value = data.footer?.legal_label || "";
form.elements["build.theme"].value = data.build?.theme || "";
form.elements["legals.hoster_name"].value = data.legals?.hoster_name || "";
form.elements["legals.hoster_adress"].value = data.legals?.hoster_adress || "";
form.elements["legals.hoster_contact"].value = data.legals?.hoster_contact || "";
// --- Build checkboxes ---
if (convertImagesCheckbox) {
convertImagesCheckbox.checked = !!data.build?.convert_images;
}
if (resizeImagesCheckbox) {
resizeImagesCheckbox.checked = !!data.build?.resize_images;
}
});
}
// Add menu item
if (addMenuBtn) {
addMenuBtn.addEventListener("click", () => {
menuItems.push({ label: "", href: "" });
renderMenuItems();
});
}
// Remove menu item
menuList.addEventListener("click", (e) => {
if (e.target.classList.contains("remove-menu-item")) {
const idx = parseInt(e.target.getAttribute("data-idx"));
menuItems.splice(idx, 1);
renderMenuItems();
}
});
// Update menuItems on input change
menuList.addEventListener("input", () => {
updateMenuItemsFromInputs();
});
// Add paragraph
if (addIpBtn) {
addIpBtn.addEventListener("click", () => {
ipParagraphs.push({ paragraph: "" });
renderIpParagraphs();
});
}
// Remove paragraph
ipList.addEventListener("click", (e) => {
if (e.target.classList.contains("remove-ip-paragraph")) {
const idx = parseInt(e.target.getAttribute("data-idx"));
ipParagraphs.splice(idx, 1);
renderIpParagraphs();
}
});
// Update ipParagraphs on input change
ipList.addEventListener("input", () => {
updateIpParagraphsFromInputs();
});
// Save config
if (form) {
form.addEventListener("submit", async (e) => {
e.preventDefault();
updateMenuItemsFromInputs();
updateIpParagraphsFromInputs();
// --- Build object with checkboxes ---
const build = {
theme: form.elements["build.theme"].value,
convert_images: !!(convertImagesCheckbox && convertImagesCheckbox.checked),
resize_images: !!(resizeImagesCheckbox && resizeImagesCheckbox.checked)
};
const payload = {
info: {
title: form.elements["info.title"].value,
subtitle: form.elements["info.subtitle"].value,
description: form.elements["info.description"].value,
canonical: form.elements["info.canonical"].value,
keywords: form.elements["info.keywords"].value.split(",").map(i => i.trim()).filter(Boolean),
author: form.elements["info.author"].value
},
social: {
instagram_url: form.elements["social.instagram_url"].value,
thumbnail: form.elements["social.thumbnail"].value
},
menu: {
items: menuItems
},
footer: {
copyright: form.elements["footer.copyright"].value,
legal_label: form.elements["footer.legal_label"].value
},
build,
legals: {
hoster_name: form.elements["legals.hoster_name"].value,
hoster_adress: form.elements["legals.hoster_adress"].value,
hoster_contact: form.elements["legals.hoster_contact"].value,
intellectual_property: ipParagraphs
}
};
const res = await fetch("/api/site-info", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
const result = await res.json();
status.textContent = result.status === "ok" ? "✅ Saved!" : "❌ Error saving";
setTimeout(() => status.textContent = "", 2000);
});
}
});

View File

@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<title>Lumeex</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
</head>
<body>
<!-- Top bar -->
<div class="nav-bar">
<div class="content-inner nav">
<div class="nav-cta">
<div class="arrow"></div>
<a class="button" href="#" target="_blank">
<span id="step">🚀 Build !<i class="fa-solid fa-envelope"></i></span>
</a>
</div>
<input type="checkbox" id="nav-check">
<div class="nav-header">
<div class="nav-title">
<img src="{{ url_for('static', filename='img/logo.svg') }}">
</div>
</div>
<div class="nav-btn">
<label for="nav-check">
<span></span>
<span></span>
<span></span>
</label>
</div>
<div class="nav-links">
<ul class="nav-list">
<li class="nav-item appear2"><a href="#">Site info</a>
<li class="nav-item appear2"><a href="#">Theme info</a>
<li class="nav-item appear2"><a href="#">Gallery</a>
</ul>
</div>
</div>
</div>
<!-- Toast container for notifications -->
<div class="content-inner">
<div id="toast-container"></div>
<h1>Edit Site Info</h1>
<form id="site-info-form">
<fieldset>
<legend>Info</legend>
<label>Title: <input type="text" name="info.title"></label><br>
<label>Subtitle: <input type="text" name="info.subtitle"></label><br>
<label>Description: <textarea name="info.description"></textarea></label><br>
<label>Canonical URL: <input type="text" name="info.canonical"></label><br>
<label>Keywords (comma separated): <input type="text" name="info.keywords"></label><br>
<label>Author: <input type="text" name="info.author"></label><br>
</fieldset>
<fieldset>
<legend>Social</legend>
<label>Instagram URL: <input type="text" name="social.instagram_url"></label><br>
<label>Thumbnail: <input type="text" name="social.thumbnail"></label><br>
</fieldset>
<fieldset>
<legend>Menu</legend>
<div id="menu-items-list"></div>
<button type="button" id="add-menu-item">+ Add menu item</button>
</fieldset>
<fieldset>
<legend>Footer</legend>
<label>Copyright: <input type="text" name="footer.copyright"></label><br>
<label>Legal Label: <input type="text" name="footer.legal_label"></label><br>
</fieldset>
<fieldset>
<legend>Build</legend>
<label>Theme: <input type="text" name="build.theme"></label><br>
<label>
<input type="checkbox" name="build.convert_images" id="convert-images-checkbox">
Convert images
</label><br>
<label>
<input type="checkbox" name="build.resize_images" id="resize-images-checkbox">
Resize images
</label><br>
</fieldset>
<fieldset>
<legend>Legals</legend>
<label>Hoster Name: <input type="text" name="legals.hoster_name"></label><br>
<label>Hoster Address: <input type="text" name="legals.hoster_adress"></label><br>
<label>Hoster Contact: <input type="text" name="legals.hoster_contact"></label><br>
<div>
<label>Intellectual Property:</label>
<div id="ip-list"></div>
<button type="button" id="add-ip-paragraph">+ Add paragraph</button>
</div>
</fieldset>
<button type="submit">Save</button>
</form>
<div id="site-info-status"></div>
</div>
<div id="delete-modal" class="modal" style="display:none;">
<div class="modal-content">
<span id="delete-modal-close" class="modal-close">&times;</span>
<h3>Confirm Deletion</h3>
<p id="delete-modal-text">Are you sure you want to delete this image?</p>
<div class="modal-actions">
<button id="delete-modal-confirm" class="modal-btn danger">Delete</button>
<button id="delete-modal-cancel" class="modal-btn">Cancel</button>
</div>
</div>
<script src="{{ url_for('static', filename='js/site-info.js') }}"></script>
</body>
</html>

View File

@ -1,3 +1,4 @@
/* --- Base Styles --- */
body {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
margin: 20px;
@ -11,10 +12,12 @@ body {
max-width: 90%;
margin: 0 auto;
}
h1, h2 {
color: #FBFBFB;
}
/* --- Toolbar --- */
.toolbar {
margin-bottom: 20px;
}
@ -34,15 +37,16 @@ h1, h2 {
background-color: #45a049;
}
/* --- Upload Section --- */
.upload-section {
margin-bottom: 30px;
}
.upload-section label {
margin-right: 20px;
cursor: pointer;
}
/* --- Gallery & Hero Grid --- */
#gallery, #hero {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
@ -50,6 +54,7 @@ h1, h2 {
margin-top: 30px;
}
/* --- Photo Card --- */
.photo {
background-color: rgb(67 67 67 / 26%);
border-radius: 6px;
@ -69,7 +74,6 @@ h1, h2 {
padding: 4px 6px;
border-radius: 30px;
color: rgb(221, 221, 221);
}
.photo button {
@ -88,7 +92,7 @@ h1, h2 {
background-color: #d32f2f;
}
/* Responsive adjustments */
/* --- Responsive Adjustments --- */
@media (max-width: 500px) {
body {
margin: 10px;
@ -105,10 +109,10 @@ h1, h2 {
}
}
/* Toast notifications */
/* --- Toast Notifications --- */
#toast-container {
position: fixed;
top: 1rem;
bottom: 1rem;
right: 1rem;
display: flex;
flex-direction: column;
@ -120,12 +124,13 @@ h1, h2 {
background: rgba(0,0,0,0.85);
color: white;
padding: 0.75rem 1.25rem;
border-radius: 0.5rem;
border-radius: 30px;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
opacity: 0;
transform: translateY(-20px);
transform: translateY(20px);
transition: opacity 0.5s ease, transform 0.5s ease;
pointer-events: none;
backdrop-filter: blur(20px);
}
.toast.show {
@ -133,10 +138,10 @@ h1, h2 {
transform: translateY(0);
}
.toast.success { background-color: #28a745; }
.toast.success { background-color: #28a7468c; }
.toast.error { background-color: #dc3545; }
/* Tags */
/* --- Tags --- */
.tag-input {
display: flex;
flex-wrap: wrap-reverse;
@ -184,9 +189,9 @@ h1, h2 {
padding: 0;
max-height: 150px;
overflow-y: auto;
z-index: 999; /* ensure it displays above other elements */
z-index: 999;
display: none;
box-shadow: 0 4px 8px rgba(0,0,0,0.15); /* subtle shadow for visibility */
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.tag-input ul.suggestions li {
@ -214,6 +219,7 @@ h1, h2 {
cursor: pointer;
}
/* --- Flex Utilities --- */
.flex-item {
display: flex;
}
@ -232,8 +238,7 @@ h1, h2 {
width: 100%
}
/* Top bar */
/* --- Top Bar & Navigation --- */
.nav {
height: 100%;
}
@ -246,7 +251,6 @@ h1, h2 {
z-index: 1;
backdrop-filter: blur(20px);
border-bottom: 1px solid #21212157;
}
.nav img {
@ -281,7 +285,6 @@ h1, h2 {
.nav-item {
display: inline;
}
.nav-list {
@ -297,7 +300,6 @@ h1, h2 {
height: 100%;
font-weight: 700;
color:#fff
}
.nav > .nav-links > .nav-list > .nav-item > a:hover {
@ -308,7 +310,6 @@ h1, h2 {
display: none;
}
.nav-list > li + li::before{
content: " → ";
color: #ffc700;
@ -350,8 +351,8 @@ h1, h2 {
display: inline-block;
}
/* Custom upload buttons */
.custom-upload-btn {
/* --- Custom Upload Buttons --- */
.up-btn {
display: inline-block;
background: #09A0C1;
color: #fff;
@ -363,13 +364,15 @@ h1, h2 {
transition: all 0.1s ease;
user-select: none;
box-shadow: 0 4px 10px rgba(0,0,0,0.25);
font-size: 14px;
border: none;
}
.custom-upload-btn:hover {
.up-btn:hover {
background: #55c3ec;
}
/* Modal styles */
/* --- Modal Styles --- */
.modal {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
@ -377,8 +380,8 @@ h1, h2 {
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: #ffffff29;
color: #fff;
@ -391,6 +394,7 @@ h1, h2 {
text-align: center;
backdrop-filter: blur(20px);
}
.modal-close {
position: absolute;
top: 12px; right: 18px;
@ -400,12 +404,14 @@ h1, h2 {
opacity: 0.7;
}
.modal-close:hover { opacity: 1; }
.modal-actions {
margin-top: 2rem;
display: flex;
gap: 1rem;
justify-content: center;
}
.modal-btn {
padding: 0.5em 1.5em;
border-radius: 30px;
@ -417,12 +423,44 @@ h1, h2 {
font-size: 1rem;
transition: background 0.2s;
}
.modal-btn.danger {
background: #c62828;
}
.modal-btn:hover {
background: #55c3ec;
}
.modal-btn.danger:hover {
background: #d32f2f;
}
/* --- Upload Actions Row --- */
.upload-actions-row {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 10px;
}
/* Remove All Buttons */
#remove-all-hero, #remove-all-gallery {
background: rgb(121, 26, 19);
color: white;
display: none;
}
#remove-all-gallery:hover,
#remove-all-hero:hover {
background: #d32f2f;
}
/* Responsive: stack buttons vertically on small screens */
@media (max-width: 500px) {
.upload-actions-row {
flex-direction: column;
align-items: stretch;
gap: 8px;
}
}