Compare commits
4 Commits
d3484a4b50
...
b74f1bb350
Author | SHA1 | Date | |
---|---|---|---|
b74f1bb350 | |||
5a6f08644a | |||
031ff62168 | |||
b56d03303e |
@ -24,6 +24,8 @@ footer:
|
|||||||
# Build parameters
|
# Build parameters
|
||||||
build:
|
build:
|
||||||
theme: modern # choose a theme in config/theme folder
|
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
|
# Change this by your legals
|
||||||
legals:
|
legals:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import yaml
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from flask import Flask, jsonify, request, send_from_directory, render_template
|
from flask import Flask, jsonify, request, send_from_directory, render_template
|
||||||
from src.py.builder.gallery_builder import (
|
from src.py.builder.gallery_builder import (
|
||||||
@ -18,6 +19,8 @@ app = Flask(
|
|||||||
static_url_path=""
|
static_url_path=""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SITE_YAML = Path(__file__).resolve().parents[3] / "config" / "site.yaml"
|
||||||
|
|
||||||
# --- Photos directory (configurable) ---
|
# --- Photos directory (configurable) ---
|
||||||
PHOTOS_DIR = Path(__file__).resolve().parents[3] / "config" / "photos"
|
PHOTOS_DIR = Path(__file__).resolve().parents[3] / "config" / "photos"
|
||||||
app.config["PHOTOS_DIR"] = PHOTOS_DIR
|
app.config["PHOTOS_DIR"] = PHOTOS_DIR
|
||||||
@ -96,11 +99,62 @@ def delete_hero_photo():
|
|||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
return {"error": "File not found"}, 404
|
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>")
|
@app.route("/photos/<section>/<path:filename>")
|
||||||
def photos(section, filename):
|
def photos(section, filename):
|
||||||
"""Serve uploaded photos from disk."""
|
"""Serve uploaded photos from disk."""
|
||||||
return send_from_directory(PHOTOS_DIR / section, filename)
|
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 ---
|
# --- Run server ---
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
logging.info("Starting WebUI at http://127.0.0.1:5000")
|
logging.info("Starting WebUI at http://127.0.0.1:5000")
|
||||||
|
@ -1,48 +1,43 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Photo WebUI</title>
|
<title>Lumeex</title>
|
||||||
|
|
||||||
<!-- Link to your CSS in the package -->
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Top bar -->
|
<!-- Top bar -->
|
||||||
<div class="nav-bar">
|
<div class="nav-bar">
|
||||||
<div class="content-inner nav">
|
<div class="content-inner nav">
|
||||||
<div class="nav-cta">
|
<div class="nav-cta">
|
||||||
<div class="arrow">
|
<div class="arrow">→</div>
|
||||||
→
|
<a class="button" href="#" target="_blank">
|
||||||
</div>
|
<span id="step">🚀 Build !<i class="fa-solid fa-envelope"></i></span>
|
||||||
<a class="button" href="#" target="_blank"><span id="step">🚀 Build !<i class="fa-solid fa-envelope"></i></span></a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<input type="checkbox" id="nav-check">
|
<input type="checkbox" id="nav-check">
|
||||||
<div class="nav-header">
|
<div class="nav-header">
|
||||||
<div class="nav-title">
|
<div class="nav-title">
|
||||||
<img src="{{ url_for('static', filename='img/logo.svg') }}">
|
<img src="{{ url_for('static', filename='img/logo.svg') }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="nav-btn">
|
||||||
<div class="nav-btn">
|
<label for="nav-check">
|
||||||
<label for="nav-check">
|
<span></span>
|
||||||
<span></span>
|
<span></span>
|
||||||
<span></span>
|
<span></span>
|
||||||
<span></span>
|
</label>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
<div class="nav-links">
|
||||||
|
<ul class="nav-list">
|
||||||
<div class="nav-links">
|
<li class="nav-item appear2"><a href="#">Site info</a>
|
||||||
<ul class="nav-list">
|
<li class="nav-item appear2"><a href="#">Theme info</a>
|
||||||
<li class="nav-item appear2"><a href="#">Site info</a>
|
<li class="nav-item appear2"><a href="#">Gallery</a>
|
||||||
<li class="nav-item appear2"><a href="#qui-suis-je">Theme info</a>
|
</ul>
|
||||||
<li class="nav-item appear2"><a href="#mariages">Gallery</a>
|
</div>
|
||||||
</ul>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Toast container for notifications -->
|
<!-- Toast container for notifications -->
|
||||||
<div class="content-inner">
|
<div class="content-inner">
|
||||||
<div id="toast-container"></div>
|
<div id="toast-container"></div>
|
||||||
@ -53,9 +48,12 @@
|
|||||||
<div class="upload-section">
|
<div class="upload-section">
|
||||||
<h2>Title Carrousel</h2>
|
<h2>Title Carrousel</h2>
|
||||||
<p> Select photos to display in the Title Carrousel</p>
|
<p> Select photos to display in the Title Carrousel</p>
|
||||||
<label for="upload-hero" class="custom-upload-btn">
|
<div class="upload-actions-row">
|
||||||
📸 Upload photos
|
<label for="upload-hero" class="up-btn">
|
||||||
</label>
|
📸 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>
|
<input type="file" id="upload-hero" accept=".png,.jpg,.jpeg,.webp" multiple hidden>
|
||||||
<div id="hero"></div>
|
<div id="hero"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -64,16 +62,17 @@
|
|||||||
<div class="upload-section">
|
<div class="upload-section">
|
||||||
<h2>Gallery</h2>
|
<h2>Gallery</h2>
|
||||||
<p> Select and tags photos to display in the Gallery</p>
|
<p> Select and tags photos to display in the Gallery</p>
|
||||||
<label for="upload-gallery" class="custom-upload-btn">
|
<div class="upload-actions-row">
|
||||||
📸 Upload photos
|
<label for="upload-gallery" class="up-btn">
|
||||||
</label>
|
📸 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>
|
<input type="file" id="upload-gallery" accept=".png,.jpg,.jpeg,.webp" multiple hidden>
|
||||||
<div id="gallery"></div>
|
<div id="gallery"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="{{ url_for('static', filename='js/main.js') }}" defer></script>
|
||||||
<!-- JS files for rendering, uploading, and actions -->
|
<script src="{{ url_for('static', filename='js/upload.js') }}" defer></script>
|
||||||
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/upload.js') }}"></script>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Delete confirmation modal -->
|
<!-- Delete confirmation modal -->
|
||||||
<div id="delete-modal" class="modal" style="display:none;">
|
<div id="delete-modal" class="modal" style="display:none;">
|
||||||
|
@ -43,7 +43,7 @@ function renderGallery() {
|
|||||||
</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">
|
||||||
<button onclick="showDeleteModal('gallery', ${i})">🗑 Delete</button>
|
<button onclick="showDeleteModal('gallery', ${i})">🗑 Delete</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tag-input" data-index="${i}"></div>
|
<div class="tag-input" data-index="${i}"></div>
|
||||||
@ -53,6 +53,12 @@ function renderGallery() {
|
|||||||
|
|
||||||
renderTags(i, img.tags || []);
|
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 ---
|
// --- Render tags for a single image ---
|
||||||
@ -60,11 +66,9 @@ 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}"]`);
|
||||||
|
|
||||||
// vider
|
|
||||||
tagsDisplay.innerHTML = '';
|
tagsDisplay.innerHTML = '';
|
||||||
inputContainer.innerHTML = '';
|
inputContainer.innerHTML = '';
|
||||||
|
|
||||||
// --- rendre les tags (en haut) ---
|
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
const span = document.createElement('span');
|
const span = document.createElement('span');
|
||||||
span.className = 'tag';
|
span.className = 'tag';
|
||||||
@ -83,13 +87,11 @@ function renderTags(imgIndex, tags) {
|
|||||||
tagsDisplay.appendChild(span);
|
tagsDisplay.appendChild(span);
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- input (en bas) ---
|
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
input.type = 'text';
|
input.type = 'text';
|
||||||
input.placeholder = 'Add tag...';
|
input.placeholder = 'Add tag...';
|
||||||
inputContainer.appendChild(input);
|
inputContainer.appendChild(input);
|
||||||
|
|
||||||
// suggestion box
|
|
||||||
const suggestionBox = document.createElement('ul');
|
const suggestionBox = document.createElement('ul');
|
||||||
suggestionBox.className = 'suggestions';
|
suggestionBox.className = 'suggestions';
|
||||||
inputContainer.appendChild(suggestionBox);
|
inputContainer.appendChild(suggestionBox);
|
||||||
@ -208,13 +210,19 @@ function renderHero() {
|
|||||||
</div>
|
</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">
|
||||||
<button onclick="showDeleteModal('hero', ${i})">🗑 Delete</button>
|
<button onclick="showDeleteModal('hero', ${i})">🗑 Delete</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
container.appendChild(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 ---
|
// --- Save gallery to server ---
|
||||||
async function saveGallery() {
|
async function saveGallery() {
|
||||||
@ -273,11 +281,19 @@ function showToast(message, type = "success", duration = 3000) {
|
|||||||
}, duration);
|
}, 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 ---
|
// --- Show delete confirmation modal ---
|
||||||
function showDeleteModal(type, index) {
|
function showDeleteModal(type, index = null) {
|
||||||
pendingDelete = { type, index };
|
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';
|
document.getElementById('delete-modal').style.display = 'flex';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,18 +310,14 @@ async function confirmDelete() {
|
|||||||
await actuallyDeleteGalleryImage(pendingDelete.index);
|
await actuallyDeleteGalleryImage(pendingDelete.index);
|
||||||
} else if (pendingDelete.type === 'hero') {
|
} else if (pendingDelete.type === 'hero') {
|
||||||
await actuallyDeleteHeroImage(pendingDelete.index);
|
await actuallyDeleteHeroImage(pendingDelete.index);
|
||||||
|
} else if (pendingDelete.type === 'gallery-all') {
|
||||||
|
await actuallyDeleteAllGalleryImages();
|
||||||
|
} else if (pendingDelete.type === 'hero-all') {
|
||||||
|
await actuallyDeleteAllHeroImages();
|
||||||
}
|
}
|
||||||
hideDeleteModal();
|
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 ---
|
// --- Actual delete functions ---
|
||||||
async function actuallyDeleteGalleryImage(index) {
|
async function actuallyDeleteGalleryImage(index) {
|
||||||
const img = galleryImages[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.addEventListener('DOMContentLoaded', () => {
|
||||||
document.getElementById('delete-modal-close').onclick = hideDeleteModal;
|
document.getElementById('delete-modal-close').onclick = hideDeleteModal;
|
||||||
document.getElementById('delete-modal-cancel').onclick = hideDeleteModal;
|
document.getElementById('delete-modal-cancel').onclick = hideDeleteModal;
|
||||||
document.getElementById('delete-modal-confirm').onclick = confirmDelete;
|
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 ---
|
// --- Initialize ---
|
||||||
loadData();
|
loadData();
|
197
src/webui/js/site-info.js
Normal file
197
src/webui/js/site-info.js
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
109
src/webui/site-info/index.html
Normal file
109
src/webui/site-info/index.html
Normal 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">×</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>
|
@ -1,3 +1,4 @@
|
|||||||
|
/* --- Base Styles --- */
|
||||||
body {
|
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;
|
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;
|
margin: 20px;
|
||||||
@ -11,10 +12,12 @@ body {
|
|||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2 {
|
h1, h2 {
|
||||||
color: #FBFBFB;
|
color: #FBFBFB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Toolbar --- */
|
||||||
.toolbar {
|
.toolbar {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
@ -34,15 +37,16 @@ h1, h2 {
|
|||||||
background-color: #45a049;
|
background-color: #45a049;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Upload Section --- */
|
||||||
.upload-section {
|
.upload-section {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-section label {
|
.upload-section label {
|
||||||
margin-right: 20px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Gallery & Hero Grid --- */
|
||||||
#gallery, #hero {
|
#gallery, #hero {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||||
@ -50,6 +54,7 @@ h1, h2 {
|
|||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Photo Card --- */
|
||||||
.photo {
|
.photo {
|
||||||
background-color: rgb(67 67 67 / 26%);
|
background-color: rgb(67 67 67 / 26%);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@ -69,26 +74,25 @@ h1, h2 {
|
|||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
color: rgb(221, 221, 221);
|
color: rgb(221, 221, 221);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.photo button {
|
.photo button {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
border: none;
|
border: none;
|
||||||
background-color:rgb(121 26 19);
|
background-color:rgb(121 26 19);
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
margin: 5px 4px 0 4px;
|
margin: 5px 4px 0 4px;
|
||||||
width:100%;
|
width:100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.photo button:hover {
|
.photo button:hover {
|
||||||
background-color: #d32f2f;
|
background-color: #d32f2f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive adjustments */
|
/* --- Responsive Adjustments --- */
|
||||||
@media (max-width: 500px) {
|
@media (max-width: 500px) {
|
||||||
body {
|
body {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
@ -105,10 +109,10 @@ h1, h2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Toast notifications */
|
/* --- Toast Notifications --- */
|
||||||
#toast-container {
|
#toast-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 1rem;
|
bottom: 1rem;
|
||||||
right: 1rem;
|
right: 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -120,12 +124,13 @@ h1, h2 {
|
|||||||
background: rgba(0,0,0,0.85);
|
background: rgba(0,0,0,0.85);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 0.75rem 1.25rem;
|
padding: 0.75rem 1.25rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 30px;
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(-20px);
|
transform: translateY(20px);
|
||||||
transition: opacity 0.5s ease, transform 0.5s ease;
|
transition: opacity 0.5s ease, transform 0.5s ease;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast.show {
|
.toast.show {
|
||||||
@ -133,19 +138,19 @@ h1, h2 {
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast.success { background-color: #28a745; }
|
.toast.success { background-color: #28a7468c; }
|
||||||
.toast.error { background-color: #dc3545; }
|
.toast.error { background-color: #dc3545; }
|
||||||
|
|
||||||
/* Tags */
|
/* --- Tags --- */
|
||||||
.tag-input {
|
.tag-input {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap-reverse;
|
flex-wrap: wrap-reverse;
|
||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-input input {
|
.tag-input input {
|
||||||
@ -184,9 +189,9 @@ h1, h2 {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
z-index: 999; /* ensure it displays above other elements */
|
z-index: 999;
|
||||||
display: none;
|
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 {
|
.tag-input ul.suggestions li {
|
||||||
@ -214,6 +219,7 @@ h1, h2 {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Flex Utilities --- */
|
||||||
.flex-item {
|
.flex-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
@ -232,8 +238,7 @@ h1, h2 {
|
|||||||
width: 100%
|
width: 100%
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Top bar */
|
/* --- Top Bar & Navigation --- */
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@ -246,11 +251,10 @@ h1, h2 {
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
border-bottom: 1px solid #21212157;
|
border-bottom: 1px solid #21212157;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav img {
|
.nav img {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
@ -280,14 +284,13 @@ h1, h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-item {
|
.nav-item {
|
||||||
display: inline;
|
display: inline;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-list {
|
.nav-list {
|
||||||
list-style-type: disc;
|
list-style-type: disc;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav > .nav-links > .nav-list > .nav-item > a {
|
.nav > .nav-links > .nav-list > .nav-item > a {
|
||||||
@ -297,7 +300,6 @@ h1, h2 {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color:#fff
|
color:#fff
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav > .nav-links > .nav-list > .nav-item > a:hover {
|
.nav > .nav-links > .nav-list > .nav-item > a:hover {
|
||||||
@ -308,32 +310,31 @@ h1, h2 {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.nav-list > li + li::before{
|
.nav-list > li + li::before{
|
||||||
content: " → ";
|
content: " → ";
|
||||||
color: #ffc700;
|
color: #ffc700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-cta {
|
.nav-cta {
|
||||||
display: inline;
|
display: inline;
|
||||||
float: right;
|
float: right;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
line-height: 70px;
|
line-height: 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-cta > .arrow {
|
.nav-cta > .arrow {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
display: inline;
|
display: inline;
|
||||||
color: #ffc700;
|
color: #ffc700;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-cta > .button {
|
.nav-cta > .button {
|
||||||
padding: 10px 25px;
|
padding: 10px 25px;
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
margin: 15px 20px 15px 10px;
|
margin: 15px 20px 15px 10px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
display: inline;
|
display: inline;
|
||||||
background: linear-gradient(135deg, #26c4ff, #016074);
|
background: linear-gradient(135deg, #26c4ff, #016074);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@ -342,16 +343,16 @@ h1, h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-cta > .button:hover {
|
.nav-cta > .button:hover {
|
||||||
background: linear-gradient(135deg, #72d9ff, #26657e);
|
background: linear-gradient(135deg, #72d9ff, #26657e);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-links > ul {
|
.nav-links > ul {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom upload buttons */
|
/* --- Custom Upload Buttons --- */
|
||||||
.custom-upload-btn {
|
.up-btn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: #09A0C1;
|
background: #09A0C1;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@ -363,13 +364,15 @@ h1, h2 {
|
|||||||
transition: all 0.1s ease;
|
transition: all 0.1s ease;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
box-shadow: 0 4px 10px rgba(0,0,0,0.25);
|
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;
|
background: #55c3ec;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modal styles */
|
/* --- Modal Styles --- */
|
||||||
.modal {
|
.modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0; left: 0; right: 0; bottom: 0;
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
@ -377,8 +380,8 @@ h1, h2 {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
background: #ffffff29;
|
background: #ffffff29;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@ -391,6 +394,7 @@ h1, h2 {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-close {
|
.modal-close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 12px; right: 18px;
|
top: 12px; right: 18px;
|
||||||
@ -400,12 +404,14 @@ h1, h2 {
|
|||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
.modal-close:hover { opacity: 1; }
|
.modal-close:hover { opacity: 1; }
|
||||||
|
|
||||||
.modal-actions {
|
.modal-actions {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-btn {
|
.modal-btn {
|
||||||
padding: 0.5em 1.5em;
|
padding: 0.5em 1.5em;
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
@ -417,12 +423,44 @@ h1, h2 {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-btn.danger {
|
.modal-btn.danger {
|
||||||
background: #c62828;
|
background: #c62828;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-btn:hover {
|
.modal-btn:hover {
|
||||||
background: #55c3ec;
|
background: #55c3ec;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-btn.danger:hover {
|
.modal-btn.danger:hover {
|
||||||
background: #d32f2f;
|
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;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user