Compare commits
28 Commits
7675b90909
...
v2.0.1
Author | SHA1 | Date | |
---|---|---|---|
df96782500 | |||
febf4d2be5 | |||
617545e1bb | |||
922ce99679 | |||
cd0428990a | |||
d3af86be8c | |||
c825798b13 | |||
b5375343a8 | |||
757e676d2d | |||
0079c166e8 | |||
ee6d4a1fa2 | |||
c6c3162b83 | |||
04c1214cd1 | |||
b03779b487 | |||
b5f8ceeb31 | |||
1591886505 | |||
a6b63c2d2b | |||
8a04fe5aa6 | |||
2cb171806c | |||
ded97700d9 | |||
8533ce72e9 | |||
b2ba1d7c7f | |||
5d238fcf33 | |||
7a95ef0255 | |||
906699f023 | |||
643a729f94 | |||
a02da47e73 | |||
f7f2356510 |
@ -6,9 +6,8 @@ COPY requirements.txt .
|
|||||||
|
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY build.py gallery.py VERSION /app/
|
||||||
COPY ./src/ ./src/
|
COPY ./src/ ./src/
|
||||||
COPY ./build.py ./build.py
|
|
||||||
COPY ./gallery.py ./gallery.py
|
|
||||||
COPY ./config /app/default
|
COPY ./config /app/default
|
||||||
COPY ./docker/.sh/entrypoint.sh /app/entrypoint.sh
|
COPY ./docker/.sh/entrypoint.sh /app/entrypoint.sh
|
||||||
RUN chmod +x /app/entrypoint.sh
|
RUN chmod +x /app/entrypoint.sh
|
||||||
|
24
README.MD
24
README.MD
@ -18,7 +18,7 @@ The project includes two thoughtfully designed themes—one modern, one minimali
|
|||||||
- **Typewriter** — [View Demo](https://typewriter.djeex.fr)
|
- **Typewriter** — [View Demo](https://typewriter.djeex.fr)
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> This GitHub repository is a mirror of the primary source at [git.djeex.fr/Djeex/lumeex](https://git.djeex.fr/Djeex/lumeex). The main repository includes the full history, releases, and bug-checking assisted by an LLM.
|
> _This GitHub repository is a mirror of the primary source at [git.djeex.fr/Djeex/lumeex](https://git.djeex.fr/Djeex/lumeex). The main repository includes the full history and releases_.
|
||||||
|
|
||||||
|
|
||||||
## 📌 Table of Contents
|
## 📌 Table of Contents
|
||||||
@ -41,20 +41,26 @@ The project includes two thoughtfully designed themes—one modern, one minimali
|
|||||||
- Typewriter — [Demo](https://typewriter.djeex.fr)
|
- Typewriter — [Demo](https://typewriter.djeex.fr)
|
||||||
- Supports Google Fonts and locally hosted fonts
|
- Supports Google Fonts and locally hosted fonts
|
||||||
|
|
||||||
### No-Code Builder (YAML Based)
|
### No-Code Builder (WebUI Manager)
|
||||||
|
|
||||||
- Configure site info, SEO, colors, fonts, and more through simple YAML files
|
|
||||||
- Reference and tag photos without any coding required
|
<div align="center">
|
||||||
- *(Optional)* Automatically update photo references via script
|
<img src="https://git.djeex.fr/Djeex/lumeex/raw/branch/main/illustration/lumeex-webui.png" alt="Lumeex Screenshot" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
### Simple Build Process
|
|
||||||
|
|
||||||
- Compiles static site from YAML configuration files (themes, templates, fonts, colors)
|
- Configure site info, SEO, colors, fonts, and more through a simple convenient WebUI
|
||||||
|
- Add and tag your photo photos without any coding required
|
||||||
- Converts favicon automatically to all required formats
|
- Converts favicon automatically to all required formats
|
||||||
- Resizes social sharing thumbnails
|
- Resizes social sharing thumbnails
|
||||||
- *(Optional)* Automatically resizes photos to a maximum width of 1140px
|
- *(Optional)* Automatically resizes photos to a maximum width of 1140px
|
||||||
- *(Optional)* Converts images to WebP format for optimized performance
|
- *(Optional)* Converts images to WebP format for optimized performance
|
||||||
- Outputs a complete static website ready to deploy on any web server
|
- Build your static site in one click and get a zip archive or an output folder, ready to deploy to your preferred webserver
|
||||||
|
|
||||||
|
### Don't want a WebUI ?
|
||||||
|
|
||||||
|
- CLI process is documented
|
||||||
|
|
||||||
## 🐳 Docker or 🐍 Python Installation
|
## 🐳 Docker or 🐍 Python Installation
|
||||||
For comprehensive documentation on installation, configuration options, customization, and demos, please visit:
|
For comprehensive documentation on installation, configuration options, customization, and demos, please visit:
|
||||||
|
@ -43,16 +43,24 @@ start_server() {
|
|||||||
cat /tmp/build_logs_fifo >&2 &
|
cat /tmp/build_logs_fifo >&2 &
|
||||||
cat /tmp/build_logs_fifo2 >&2 &
|
cat /tmp/build_logs_fifo2 >&2 &
|
||||||
|
|
||||||
echo "Starting HTTP server on port 3000..."
|
echo "Starting preview HTTP server on port 3000..."
|
||||||
python3 -u -m http.server 3000 -d /app/output &
|
python3 -u -m http.server 3000 -d /app/output &
|
||||||
SERVER_PID=$!
|
SERVER_PID=$!
|
||||||
trap "echo 'Stopping server...'; kill -TERM $SERVER_PID 2>/dev/null; wait $SERVER_PID; exit 0" SIGINT SIGTERM
|
|
||||||
|
echo "Starting Lumeex Flask webui..."
|
||||||
|
python3 -u -m src.py.webui.webui &
|
||||||
|
WEBUI_PID=$!
|
||||||
|
|
||||||
|
trap "echo 'Stopping servers...'; kill -TERM $SERVER_PID $WEBUI_PID 2>/dev/null; wait $SERVER_PID $WEBUI_PID; exit 0" SIGINT SIGTERM
|
||||||
|
|
||||||
wait $SERVER_PID
|
wait $SERVER_PID
|
||||||
|
wait $WEBUI_PID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VERSION=$(cat VERSION)
|
||||||
if [ $# -eq 0 ]; then
|
if [ $# -eq 0 ]; then
|
||||||
echo -e "${CYAN}╭───────────────────────────────────────────╮${NC}"
|
echo -e "${CYAN}╭───────────────────────────────────────────╮${NC}"
|
||||||
echo -e "${CYAN}│${NC} Lum${CYAN}eex${NC} - Version 1.3.1${NC} ${CYAN}│${NC}"
|
echo -e "${CYAN}│${NC} Lum${CYAN}eex${NC} - Version ${VERSION}${NC} ${CYAN}│${NC}"
|
||||||
echo -e "${CYAN}├───────────────────────────────────────────┤${NC}"
|
echo -e "${CYAN}├───────────────────────────────────────────┤${NC}"
|
||||||
echo -e "${CYAN}│${NC} Source: https://git.djeex.fr/Djeex/lumeex ${CYAN}│${NC}"
|
echo -e "${CYAN}│${NC} Source: https://git.djeex.fr/Djeex/lumeex ${CYAN}│${NC}"
|
||||||
echo -e "${CYAN}│${NC} Mirror: https://github.com/Djeex/lumeex ${CYAN}│${NC}"
|
echo -e "${CYAN}│${NC} Mirror: https://github.com/Djeex/lumeex ${CYAN}│${NC}"
|
||||||
|
@ -7,4 +7,5 @@ services:
|
|||||||
- ../output:/app/output # mount output directory
|
- ../output:/app/output # mount output directory
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
|
- "5000:5000"
|
||||||
|
|
BIN
illustration/lumeex-webui.png
Normal file
BIN
illustration/lumeex-webui.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 620 KiB |
@ -3,6 +3,13 @@
|
|||||||
|
|
||||||
// Fade in effect for elements with class 'appear'
|
// Fade in effect for elements with class 'appear'
|
||||||
const setupIntersectionObserver = () => {
|
const setupIntersectionObserver = () => {
|
||||||
|
document.querySelectorAll('.appear').forEach(parent => {
|
||||||
|
const children = parent.querySelectorAll('.appear');
|
||||||
|
children.forEach((child, i) => {
|
||||||
|
child.style.transitionDelay = `${i * 0.2}s`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const items = document.querySelectorAll('.appear');
|
const items = document.querySelectorAll('.appear');
|
||||||
const io = new IntersectionObserver((entries) => {
|
const io = new IntersectionObserver((entries) => {
|
||||||
entries.forEach((entry) => {
|
entries.forEach((entry) => {
|
||||||
@ -32,6 +39,7 @@ const randomizeHeroBackground = () => {
|
|||||||
if (images.length === 0) return;
|
if (images.length === 0) return;
|
||||||
let currentIndex = Math.floor(Math.random() * images.length);
|
let currentIndex = Math.floor(Math.random() * images.length);
|
||||||
heroBg.style.backgroundImage = `url(/img/${images[currentIndex]})`;
|
heroBg.style.backgroundImage = `url(/img/${images[currentIndex]})`;
|
||||||
|
if (images.length < 2) return; // <-- Prevent interval if only one image
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
let nextIndex;
|
let nextIndex;
|
||||||
do {
|
do {
|
||||||
|
@ -192,36 +192,6 @@ h2 {
|
|||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.appear.inview:nth-child(1) {
|
|
||||||
-webkit-transition-delay: 0s;
|
|
||||||
transition-delay: 0s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.appear.inview:nth-child(2) {
|
|
||||||
-webkit-transition-delay: 0.2s;
|
|
||||||
transition-delay: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.appear.inview:nth-child(3) {
|
|
||||||
-webkit-transition-delay: 0.4s;
|
|
||||||
transition-delay: 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.appear.inview:nth-child(4) {
|
|
||||||
-webkit-transition-delay: 0.6s;
|
|
||||||
transition-delay: 0.6s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.appear.inview:nth-child(5) {
|
|
||||||
-webkit-transition-delay: 0.8s;
|
|
||||||
transition-delay: 0.8s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.appear.inview:nth-child(6) {
|
|
||||||
-webkit-transition-delay: 1s;
|
|
||||||
transition-delay: 1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* img fade in */
|
/* img fade in */
|
||||||
|
|
||||||
.fade-in-img {
|
.fade-in-img {
|
||||||
@ -491,7 +461,7 @@ h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.section img {
|
.section img {
|
||||||
margin: 0px 0 60px 0;
|
margin: 0px 0 40px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
|
@ -21,12 +21,14 @@ STYLE_DIR = SRC_DIR / "src/public/style"
|
|||||||
GALLERY_FILE = SRC_DIR / "config/gallery.yaml"
|
GALLERY_FILE = SRC_DIR / "config/gallery.yaml"
|
||||||
SITE_FILE = SRC_DIR / "config/site.yaml"
|
SITE_FILE = SRC_DIR / "config/site.yaml"
|
||||||
THEMES_DIR = SRC_DIR / "config/themes"
|
THEMES_DIR = SRC_DIR / "config/themes"
|
||||||
|
VERSION_FILE = SRC_DIR / "VERSION"
|
||||||
|
with open(VERSION_FILE, "r") as vf:
|
||||||
|
build_version = vf.read().strip()
|
||||||
|
|
||||||
def build():
|
def build():
|
||||||
build_version = "v1.3.1"
|
|
||||||
logging.info("\n")
|
logging.info("\n")
|
||||||
logging.info("=" * 24)
|
logging.info("=" * 24)
|
||||||
logging.info(f"🚀 Lumeex builder {build_version}")
|
logging.info(f"🚀 Lumeex builder v{build_version}")
|
||||||
logging.info("=" * 24)
|
logging.info("=" * 24)
|
||||||
logging.info("\n === Starting build === ")
|
logging.info("\n === Starting build === ")
|
||||||
ensure_dir(BUILD_DIR)
|
ensure_dir(BUILD_DIR)
|
||||||
|
@ -18,6 +18,10 @@ from src.py.webui.upload import upload_bp
|
|||||||
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
||||||
|
|
||||||
# --- Flask app setup ---
|
# --- Flask app setup ---
|
||||||
|
VERSION_FILE = Path(__file__).resolve().parents[3] / "VERSION"
|
||||||
|
with open(VERSION_FILE, "r") as vf:
|
||||||
|
lumeex_version = vf.read().strip()
|
||||||
|
|
||||||
WEBUI_PATH = Path(__file__).parents[2] / "webui" # Path to static/templates
|
WEBUI_PATH = Path(__file__).parents[2] / "webui" # Path to static/templates
|
||||||
app = Flask(
|
app = Flask(
|
||||||
__name__,
|
__name__,
|
||||||
@ -68,6 +72,10 @@ def get_local_fonts(theme_name):
|
|||||||
def index():
|
def index():
|
||||||
return render_template("index.html")
|
return render_template("index.html")
|
||||||
|
|
||||||
|
@app.context_processor
|
||||||
|
def inject_version():
|
||||||
|
return dict(lumeex_version=lumeex_version)
|
||||||
|
|
||||||
# --- Gallery & Hero API ---
|
# --- Gallery & Hero API ---
|
||||||
@app.route("/gallery-editor")
|
@app.route("/gallery-editor")
|
||||||
def gallery_editor():
|
def gallery_editor():
|
||||||
@ -479,5 +487,5 @@ def download_output_zip():
|
|||||||
|
|
||||||
# --- 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://0.0.0.0:5000")
|
||||||
app.run(debug=True)
|
app.run(host="0.0.0.0", port=5000, debug=True)
|
@ -12,7 +12,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="inner bottom-link appear">
|
<div class="inner bottom-link appear">
|
||||||
<p><span class="navigation-subtitle appear">{{ copyright }}</span><span class="nav-separator"> • </span><span class="navigation-bottom-link appear"><a href="{{ legal_link }}">{{ legal_label }}</a></span></p>
|
<p><span class="navigation-subtitle appear">{{ copyright }}</span><span class="nav-separator"> • </span><span class="navigation-bottom-link appear"><a href="{{ legal_link }}">{{ legal_label }}</a></span></p>
|
||||||
<p class="navigation-subtitle appear"> Built with <a href="https://git.djeex.fr/Djeex/lumeex">Lumeex</a></p>
|
<p class="navigation-subtitle appear"> Built with <a href="https://lumeex.djeex.fr">Lumeex</a></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,43 +1,9 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
{% extends "template/base.html" %}
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
{% block title %}Lumeex - Gallery Editor{% endblock %}
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Lumeex</title>
|
{% block content %}
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- Top bar -->
|
|
||||||
<div class="nav-bar">
|
|
||||||
<div class="content-inner nav">
|
|
||||||
<input type="checkbox" id="nav-check">
|
|
||||||
<div class="nav-header">
|
|
||||||
<div class="nav-title">
|
|
||||||
<a href="/"><img src="{{ url_for('static', filename='img/logo.svg') }}"></a>
|
|
||||||
</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"><a href="/gallery-editor">Gallery</a></li>
|
|
||||||
<li class="nav-item"><a href="/site-info">Site info</a></li>
|
|
||||||
<li class="nav-item"><a href="/theme-editor">Theme info</a></li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<button id="build-btn" class="button">🚀 Build !</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Toast container for notifications -->
|
|
||||||
<div class="content-inner">
|
|
||||||
<div id="toast-container"></div>
|
|
||||||
|
|
||||||
<h1>Gallery editor</h1>
|
<h1>Gallery editor</h1>
|
||||||
|
|
||||||
@ -74,53 +40,31 @@
|
|||||||
<p> Follow the steps to generate your static gallery</p>
|
<p> Follow the steps to generate your static gallery</p>
|
||||||
<ul id="stepper">
|
<ul id="stepper">
|
||||||
<li><a class="step-active" href="/gallery-editor">Upload your photos</a></li>
|
<li><a class="step-active" href="/gallery-editor">Upload your photos</a></li>
|
||||||
<div>→</div>
|
<div></div>
|
||||||
<li><a href="/site-info">Configure site info</a></li>
|
<li><a href="/site-info">Configure site info</a></li>
|
||||||
<div>→</div>
|
<div></div>
|
||||||
<li><a href="/theme-editor">Customize your theme</a></li>
|
<li><a href="/theme-editor">Customize your theme</a></li>
|
||||||
<div>→</div>
|
<div></div>
|
||||||
<li><button id="stepper-build">Generate your static site!</button></li>
|
<li><button id="stepper-build">Generate your static site!</button></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!-- Delete confirmation modal -->
|
||||||
<script src="{{ url_for('static', filename='js/main.js') }}" defer></script>
|
<div id="delete-modal" class="modal" style="display:none;">
|
||||||
<script src="{{ url_for('static', filename='js/upload.js') }}" defer></script>
|
<div class="modal-content">
|
||||||
<script src="{{ url_for('static', filename='js/build.js') }}" defer></script>
|
<span id="delete-modal-close" class="modal-close">×</span>
|
||||||
<!-- Delete confirmation modal -->
|
<h3>Confirm Deletion</h3>
|
||||||
<div id="delete-modal" class="modal" style="display:none;">
|
<p id="delete-modal-text">Are you sure you want to delete this image?</p>
|
||||||
<div class="modal-content">
|
<div class="modal-actions">
|
||||||
<span id="delete-modal-close" class="modal-close">×</span>
|
<button id="delete-modal-confirm" class="modal-btn danger">Delete</button>
|
||||||
<h3>Confirm Deletion</h3>
|
<button id="delete-modal-cancel" class="modal-btn">Cancel</button>
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
<!-- Build success modal -->
|
|
||||||
<div id="build-success-modal" class="modal" style="display:none;">
|
|
||||||
<div class="modal-content">
|
|
||||||
<span id="build-success-modal-close" class="modal-close">×</span>
|
|
||||||
<h3>✅ Build completed!</h3>
|
|
||||||
<p>Your files are available in the output folder.</p>
|
|
||||||
<button id="download-zip-btn" class="modal-btn">Download ZIP</button>
|
|
||||||
<div id="zip-loader" style="display:none;">Creating ZIP...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="footer">
|
|
||||||
<div class="content-inner">
|
|
||||||
<div class="footer-container">
|
|
||||||
<div class="footer-credit">
|
|
||||||
<p><a href="https//lumeex.djeex.fr"><span class="lum-first">Lum</span><span class="lum-second">eex</span> v1.3</a> — © 2025</p>
|
|
||||||
</div>
|
|
||||||
<div class="footer-links">
|
|
||||||
<a class="footer-link documentation" href="https://lumeex.djeex.fr"><span class="icon"><img src="/img/favicon.svg"></span><span class="icon-text">Documentation</span></a>
|
|
||||||
<a class="footer-link gitea" href="https://gitea.com/Djeex/lumeex"><span class="icon"><img src="/img/gitea.svg"></span><span class="icon-text">Giteex</span></a>
|
|
||||||
<a class="footer-link github" href="https://github.com/Djeex/lumeex"><span class="icon"><img src="/img/github.svg"></span><span class="icon-text">Github</span></a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script src="{{ url_for('static', filename='js/gallery-editor.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/upload.js') }}"></script>
|
||||||
|
{% endblock %}
|
@ -1,43 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "template/base.html" %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
{% block title %}Lumeex{% endblock %}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta charset="UTF-8">
|
{% block content %}
|
||||||
<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">
|
|
||||||
<input type="checkbox" id="nav-check">
|
|
||||||
<div class="nav-header">
|
|
||||||
<div class="nav-title">
|
|
||||||
<a href="/"><img src="{{ url_for('static', filename='img/logo.svg') }}"></a>
|
|
||||||
</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"><a href="/gallery-editor">Gallery</a></li>
|
|
||||||
<li class="nav-item"><a href="/site-info">Site info</a></li>
|
|
||||||
<li class="nav-item"><a href="/theme-editor">Theme info</a></li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<button id="build-btn" class="button">🚀 Build !</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Toast container for notifications -->
|
|
||||||
<div class="content-inner first-content">
|
|
||||||
<div id="toast-container"></div>
|
|
||||||
|
|
||||||
<h1>Static Gallery Generator</h1>
|
<h1>Static Gallery Generator</h1>
|
||||||
<p>Use this generator to create a static gallery from your photos. Then, upload the static files to your preferred web server.</p>
|
<p>Use this generator to create a static gallery from your photos. Then, upload the static files to your preferred web server.</p>
|
||||||
@ -46,40 +11,21 @@
|
|||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>Steps</h2>
|
<h2>Steps</h2>
|
||||||
<p> Follow the steps to generate your static gallery</p>
|
<p> Follow the steps to generate your static gallery</p>
|
||||||
<ul id="stepper">
|
<div class="stepper">
|
||||||
<li><a href="/gallery-editor">Upload your photos</a></li>
|
<ul id="stepper">
|
||||||
<div>→</div>
|
<li><a href="/gallery-editor">Upload your photos</a></li>
|
||||||
<li><a href="/site-info">Configure site info</a></li>
|
<div></div>
|
||||||
<div>→</div>
|
<li><a href="/site-info">Configure site info</a></li>
|
||||||
<li><a href="/theme-editor">Customize your theme</a></li>
|
<div></div>
|
||||||
<div>→</div>
|
<li><a href="/theme-editor">Customize your theme</a></li>
|
||||||
<li><button id="stepper-build">Generate your static site!</button></li>
|
<div></div>
|
||||||
</ul>
|
<li><button id="stepper-build">Generate your static site!</button></li>
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
|
||||||
<!-- Build success modal -->
|
|
||||||
<div id="build-success-modal" class="modal" style="display:none;">
|
|
||||||
<div class="modal-content">
|
|
||||||
<span id="build-success-modal-close" class="modal-close">×</span>
|
|
||||||
<h3>✅ Build completed!</h3>
|
|
||||||
<p>Your files are available in the output folder.</p>
|
|
||||||
<button id="download-zip-btn" class="modal-btn">Download ZIP</button>
|
|
||||||
<div id="zip-loader" style="display:none;">Creating ZIP...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="footer">
|
|
||||||
<div class="content-inner">
|
|
||||||
<div class="footer-container">
|
|
||||||
<div class="footer-credit">
|
|
||||||
<p><a href="https//lumeex.djeex.fr"><span class="lum-first">Lum</span><span class="lum-second">eex</span> v1.3</a> — © 2025</p>
|
|
||||||
</div>
|
|
||||||
<div class="footer-links">
|
|
||||||
<a class="footer-link documentation" href="https://lumeex.djeex.fr"><span class="icon"><img src="/img/favicon.svg"></span><span class="icon-text">Documentation</span></a>
|
|
||||||
<a class="footer-link gitea" href="https://gitea.com/Djeex/lumeex"><span class="icon"><img src="/img/gitea.svg"></span><span class="icon-text">Giteex</span></a>
|
|
||||||
<a class="footer-link github" href="https://github.com/Djeex/lumeex"><span class="icon"><img src="/img/github.svg"></span><span class="icon-text">Github</span></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
{% endblock %}
|
||||||
</html>
|
|
||||||
|
{% block scripts %}
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -4,6 +4,21 @@
|
|||||||
* @param {string} type - "success" or "error".
|
* @param {string} type - "success" or "error".
|
||||||
* @param {number} duration - Duration in ms.
|
* @param {number} duration - Duration in ms.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// --- Loader helpers ---
|
||||||
|
function showLoader(text = "Uploading...") {
|
||||||
|
const loader = document.getElementById("global-loader");
|
||||||
|
if (loader) {
|
||||||
|
loader.classList.add("active");
|
||||||
|
document.getElementById("loader-text").textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function hideLoader() {
|
||||||
|
const loader = document.getElementById("global-loader");
|
||||||
|
if (loader) loader.classList.remove("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Toast helpers ---
|
||||||
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;
|
||||||
@ -29,9 +44,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
// Build action handler
|
// Build action handler
|
||||||
async function handleBuildClick() {
|
async function handleBuildClick() {
|
||||||
|
showLoader("Building static site...");
|
||||||
// Trigger build on backend
|
// Trigger build on backend
|
||||||
const res = await fetch("/api/build", { method: "POST" });
|
const res = await fetch("/api/build", { method: "POST" });
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
// Show build success modal
|
// Show build success modal
|
||||||
if (buildModal) buildModal.style.display = "flex";
|
if (buildModal) buildModal.style.display = "flex";
|
||||||
|
@ -92,6 +92,14 @@ 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');
|
||||||
|
validateBtn.textContent = '✔️';
|
||||||
|
validateBtn.className = 'validate-tag-btn';
|
||||||
|
validateBtn.style.display = 'none'; // hidden by default
|
||||||
|
validateBtn.style.marginLeft = '4px';
|
||||||
|
inputContainer.appendChild(validateBtn);
|
||||||
|
|
||||||
const suggestionBox = document.createElement('ul');
|
const suggestionBox = document.createElement('ul');
|
||||||
suggestionBox.className = 'suggestions';
|
suggestionBox.className = 'suggestions';
|
||||||
inputContainer.appendChild(suggestionBox);
|
inputContainer.appendChild(suggestionBox);
|
||||||
@ -148,8 +156,14 @@ function renderTags(imgIndex, tags) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
input.addEventListener('input', updateSuggestions);
|
input.addEventListener('input', () => {
|
||||||
input.addEventListener('focus', updateSuggestions);
|
updateSuggestions();
|
||||||
|
validateBtn.style.display = input.value.trim() ? 'inline-block' : 'none';
|
||||||
|
});
|
||||||
|
input.addEventListener('focus', () => {
|
||||||
|
updateSuggestions();
|
||||||
|
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');
|
||||||
@ -172,11 +186,13 @@ function renderTags(imgIndex, tags) {
|
|||||||
}
|
}
|
||||||
input.value = '';
|
input.value = '';
|
||||||
updateSuggestions();
|
updateSuggestions();
|
||||||
|
validateBtn.style.display = 'none';
|
||||||
} else if ([' ', ','].includes(e.key)) {
|
} else if ([' ', ','].includes(e.key)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
addTag(input.value);
|
addTag(input.value);
|
||||||
input.value = '';
|
input.value = '';
|
||||||
updateSuggestions();
|
updateSuggestions();
|
||||||
|
validateBtn.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -184,9 +200,20 @@ function renderTags(imgIndex, tags) {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
suggestionBox.style.display = 'none';
|
suggestionBox.style.display = 'none';
|
||||||
input.value = '';
|
input.value = '';
|
||||||
|
validateBtn.style.display = 'none';
|
||||||
}, 150);
|
}, 150);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Validate button action ---
|
||||||
|
validateBtn.onclick = () => {
|
||||||
|
if (input.value.trim()) {
|
||||||
|
addTag(input.value.trim());
|
||||||
|
input.value = '';
|
||||||
|
updateSuggestions();
|
||||||
|
validateBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
input.focus();
|
input.focus();
|
||||||
updateSuggestions();
|
updateSuggestions();
|
||||||
}
|
}
|
@ -12,6 +12,19 @@ function showToast(message, type = "success", duration = 3000) {
|
|||||||
}, duration);
|
}, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Loader helpers ---
|
||||||
|
function showLoader(text = "Uploading...") {
|
||||||
|
const loader = document.getElementById("global-loader");
|
||||||
|
if (loader) {
|
||||||
|
loader.classList.add("active");
|
||||||
|
document.getElementById("loader-text").textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function hideLoader() {
|
||||||
|
const loader = document.getElementById("global-loader");
|
||||||
|
if (loader) loader.classList.remove("active");
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
// Form and menu logic
|
// Form and menu logic
|
||||||
const form = document.getElementById("site-info-form");
|
const form = document.getElementById("site-info-form");
|
||||||
@ -130,10 +143,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
thumbnailUpload.addEventListener("change", async (e) => {
|
thumbnailUpload.addEventListener("change", async (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
showLoader("Uploading thumbnail...");
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
const res = await fetch("/api/thumbnail/upload", { method: "POST", body: formData });
|
const res = await fetch("/api/thumbnail/upload", { method: "POST", body: formData });
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
if (thumbnailInput) thumbnailInput.value = result.filename;
|
if (thumbnailInput) thumbnailInput.value = result.filename;
|
||||||
updateThumbnailPreview(`/photos/${result.filename}?t=${Date.now()}`);
|
updateThumbnailPreview(`/photos/${result.filename}?t=${Date.now()}`);
|
||||||
@ -183,12 +198,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
themeUpload.addEventListener("change", async (e) => {
|
themeUpload.addEventListener("change", async (e) => {
|
||||||
const files = Array.from(e.target.files);
|
const files = Array.from(e.target.files);
|
||||||
if (files.length === 0) return;
|
if (files.length === 0) return;
|
||||||
|
showLoader("Uploading theme...");
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
formData.append("files", file, file.webkitRelativePath || file.name);
|
formData.append("files", file, file.webkitRelativePath || file.name);
|
||||||
});
|
});
|
||||||
const res = await fetch("/api/theme/upload", { method: "POST", body: formData });
|
const res = await fetch("/api/theme/upload", { method: "POST", body: formData });
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
showToast("✅ Theme uploaded!", "success");
|
showToast("✅ Theme uploaded!", "success");
|
||||||
// Refresh theme select after upload
|
// Refresh theme select after upload
|
||||||
@ -239,12 +256,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
};
|
};
|
||||||
deleteThemeModalConfirm.onclick = async () => {
|
deleteThemeModalConfirm.onclick = async () => {
|
||||||
if (!themeToDelete) return;
|
if (!themeToDelete) return;
|
||||||
|
showLoader("Removing theme...");
|
||||||
const res = await fetch("/api/theme/remove", {
|
const res = await fetch("/api/theme/remove", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ theme: themeToDelete })
|
body: JSON.stringify({ theme: themeToDelete })
|
||||||
});
|
});
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
showToast("✅ Theme removed!", "success");
|
showToast("✅ Theme removed!", "success");
|
||||||
// Refresh theme select
|
// Refresh theme select
|
||||||
@ -379,7 +398,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
// Check if thumbnail is set before saving (uploaded or present in input)
|
// Check if thumbnail is set before saving (uploaded or present in input)
|
||||||
if (!thumbnailInput || !thumbnailInput.value) {
|
if (!thumbnailInput || !thumbnailInput.value) {
|
||||||
|
showLoader("Saving...");
|
||||||
showToast("❌ Thumbnail is required.", "error");
|
showToast("❌ Thumbnail is required.", "error");
|
||||||
|
hideLoader();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,6 +438,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
intellectual_property: ipParagraphs
|
intellectual_property: ipParagraphs
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// --- REMOVE loader for save ---
|
||||||
|
// showLoader("Saving...");
|
||||||
const res = await fetch("/api/site-info", {
|
const res = await fetch("/api/site-info", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
@ -31,6 +31,20 @@ function showToast(message, type = "success", duration = 3000) {
|
|||||||
}, duration);
|
}, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Loader helpers ---
|
||||||
|
function showLoader(text = "Uploading...") {
|
||||||
|
const loader = document.getElementById("global-loader");
|
||||||
|
if (loader) {
|
||||||
|
loader.classList.add("active");
|
||||||
|
document.getElementById("loader-text").textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function hideLoader() {
|
||||||
|
const loader = document.getElementById("global-loader");
|
||||||
|
if (loader) loader.classList.remove("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Color Picker
|
||||||
function setupColorPicker(colorId, btnId, textId, initial) {
|
function setupColorPicker(colorId, btnId, textId, initial) {
|
||||||
const colorInput = document.getElementById(colorId);
|
const colorInput = document.getElementById(colorId);
|
||||||
const colorBtn = document.getElementById(btnId);
|
const colorBtn = document.getElementById(btnId);
|
||||||
@ -40,7 +54,6 @@ function setupColorPicker(colorId, btnId, textId, initial) {
|
|||||||
colorBtn.style.background = initial;
|
colorBtn.style.background = initial;
|
||||||
textInput.value = initial.toUpperCase();
|
textInput.value = initial.toUpperCase();
|
||||||
|
|
||||||
// Color input is positioned over the button and is clickable
|
|
||||||
colorInput.addEventListener("input", () => {
|
colorInput.addEventListener("input", () => {
|
||||||
colorBtn.style.background = colorInput.value;
|
colorBtn.style.background = colorInput.value;
|
||||||
textInput.value = colorInput.value.toUpperCase();
|
textInput.value = colorInput.value.toUpperCase();
|
||||||
@ -178,11 +191,13 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
showToast("Only .woff and .woff2 fonts are allowed.", "error");
|
showToast("Only .woff and .woff2 fonts are allowed.", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
showLoader("Uploading font...");
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
formData.append("theme", themeInfo.theme_name);
|
formData.append("theme", themeInfo.theme_name);
|
||||||
const res = await fetch("/api/font/upload", { method: "POST", body: formData });
|
const res = await fetch("/api/font/upload", { method: "POST", body: formData });
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
showToast("✅ Font uploaded!", "success");
|
showToast("✅ Font uploaded!", "success");
|
||||||
localFonts = await fetchLocalFonts(themeInfo.theme_name);
|
localFonts = await fetchLocalFonts(themeInfo.theme_name);
|
||||||
@ -219,9 +234,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
};
|
};
|
||||||
deleteFontModalConfirm.onclick = async () => {
|
deleteFontModalConfirm.onclick = async () => {
|
||||||
if (!fontToDelete) return;
|
if (!fontToDelete) return;
|
||||||
|
showLoader("Removing font...");
|
||||||
const result = await removeFont(themeInfo.theme_name, fontToDelete);
|
const result = await removeFont(themeInfo.theme_name, fontToDelete);
|
||||||
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
showToast("Font removed!", "✅ success");
|
showToast("Font removed!", "success");
|
||||||
localFonts = await fetchLocalFonts(themeInfo.theme_name);
|
localFonts = await fetchLocalFonts(themeInfo.theme_name);
|
||||||
refreshLocalFonts();
|
refreshLocalFonts();
|
||||||
} else {
|
} else {
|
||||||
@ -272,11 +289,13 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
showToast("Invalid file type for favicon.", "error");
|
showToast("Invalid file type for favicon.", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
showLoader("Uploading favicon...");
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
formData.append("theme", themeInfo.theme_name);
|
formData.append("theme", themeInfo.theme_name);
|
||||||
const res = await fetch("/api/favicon/upload", { method: "POST", body: formData });
|
const res = await fetch("/api/favicon/upload", { method: "POST", body: formData });
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
faviconInput.value = result.filename;
|
faviconInput.value = result.filename;
|
||||||
updateFaviconPreview(`/themes/${themeInfo.theme_name}/${result.filename}?t=${Date.now()}`);
|
updateFaviconPreview(`/themes/${themeInfo.theme_name}/${result.filename}?t=${Date.now()}`);
|
||||||
@ -303,12 +322,14 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
deleteFaviconModalConfirm.onclick = async () => {
|
deleteFaviconModalConfirm.onclick = async () => {
|
||||||
|
showLoader("Removing favicon...");
|
||||||
const res = await fetch("/api/favicon/remove", {
|
const res = await fetch("/api/favicon/remove", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ theme: themeInfo.theme_name })
|
body: JSON.stringify({ theme: themeInfo.theme_name })
|
||||||
});
|
});
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
faviconInput.value = "";
|
faviconInput.value = "";
|
||||||
updateFaviconPreview("");
|
updateFaviconPreview("");
|
||||||
@ -335,13 +356,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
if (addGoogleFontBtn) {
|
if (addGoogleFontBtn) {
|
||||||
addGoogleFontBtn.addEventListener("click", async () => {
|
addGoogleFontBtn.addEventListener("click", async () => {
|
||||||
googleFonts.push({ family: "", weights: [] });
|
googleFonts.push({ family: "", weights: [] });
|
||||||
// Save immediately to backend
|
|
||||||
await fetch("/api/theme-google-fonts", {
|
await fetch("/api/theme-google-fonts", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
|
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
|
||||||
});
|
});
|
||||||
// Fetch updated theme info and refresh dropdowns
|
|
||||||
const updatedThemeInfo = await fetchThemeInfo();
|
const updatedThemeInfo = await fetchThemeInfo();
|
||||||
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
|
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
|
||||||
googleFonts.length = 0;
|
googleFonts.length = 0;
|
||||||
@ -353,13 +372,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
|
|
||||||
const googleFontsFields = document.getElementById("google-fonts-fields");
|
const googleFontsFields = document.getElementById("google-fonts-fields");
|
||||||
if (googleFontsFields) {
|
if (googleFontsFields) {
|
||||||
// Save on blur for family/weights fields
|
|
||||||
googleFontsFields.addEventListener("blur", async (e) => {
|
googleFontsFields.addEventListener("blur", async (e) => {
|
||||||
if (
|
if (
|
||||||
e.target.name &&
|
e.target.name &&
|
||||||
(e.target.name.endsWith("[family]") || e.target.name.endsWith("[weights]"))
|
(e.target.name.endsWith("[family]") || e.target.name.endsWith("[weights]"))
|
||||||
) {
|
) {
|
||||||
// Update googleFonts array from the form fields
|
|
||||||
const fontFields = googleFontsFields.querySelectorAll(".input-field");
|
const fontFields = googleFontsFields.querySelectorAll(".input-field");
|
||||||
googleFonts.length = 0;
|
googleFonts.length = 0;
|
||||||
fontFields.forEach(field => {
|
fontFields.forEach(field => {
|
||||||
@ -368,13 +385,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
.split(",").map(w => w.trim()).filter(Boolean);
|
.split(",").map(w => w.trim()).filter(Boolean);
|
||||||
googleFonts.push({ family, weights });
|
googleFonts.push({ family, weights });
|
||||||
});
|
});
|
||||||
// Save immediately to backend
|
|
||||||
await fetch("/api/theme-google-fonts", {
|
await fetch("/api/theme-google-fonts", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
|
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
|
||||||
});
|
});
|
||||||
// Fetch updated theme info and refresh dropdowns
|
|
||||||
const updatedThemeInfo = await fetchThemeInfo();
|
const updatedThemeInfo = await fetchThemeInfo();
|
||||||
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
|
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
|
||||||
googleFonts.length = 0;
|
googleFonts.length = 0;
|
||||||
@ -382,20 +397,17 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
renderGoogleFonts(googleFonts);
|
renderGoogleFonts(googleFonts);
|
||||||
refreshFontDropdowns();
|
refreshFontDropdowns();
|
||||||
}
|
}
|
||||||
}, true); // Use capture phase to catch blur from children
|
}, true);
|
||||||
|
|
||||||
// Delegate remove button click for Google Fonts
|
|
||||||
googleFontsFields.addEventListener("click", async (e) => {
|
googleFontsFields.addEventListener("click", async (e) => {
|
||||||
if (e.target.classList.contains("remove-google-font")) {
|
if (e.target.classList.contains("remove-google-font")) {
|
||||||
const idx = Number(e.target.dataset.idx);
|
const idx = Number(e.target.dataset.idx);
|
||||||
googleFonts.splice(idx, 1);
|
googleFonts.splice(idx, 1);
|
||||||
// Save immediately to backend
|
|
||||||
await fetch("/api/theme-google-fonts", {
|
await fetch("/api/theme-google-fonts", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
|
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
|
||||||
});
|
});
|
||||||
// Fetch updated theme info and refresh dropdowns
|
|
||||||
const updatedThemeInfo = await fetchThemeInfo();
|
const updatedThemeInfo = await fetchThemeInfo();
|
||||||
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
|
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
|
||||||
googleFonts.length = 0;
|
googleFonts.length = 0;
|
||||||
@ -406,9 +418,9 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form submit
|
|
||||||
document.getElementById("theme-editor-form").addEventListener("submit", async (e) => {
|
document.getElementById("theme-editor-form").addEventListener("submit", async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
showLoader("Saving theme...");
|
||||||
const data = {};
|
const data = {};
|
||||||
data.colors = {
|
data.colors = {
|
||||||
primary: document.getElementById("color-primary-text").value,
|
primary: document.getElementById("color-primary-text").value,
|
||||||
@ -445,6 +457,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ theme_name: themeInfo.theme_name, theme_yaml: data })
|
body: JSON.stringify({ theme_name: themeInfo.theme_name, theme_yaml: data })
|
||||||
});
|
});
|
||||||
|
hideLoader();
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
showToast("✅ Theme saved!", "success");
|
showToast("✅ Theme saved!", "success");
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,41 +1,64 @@
|
|||||||
|
// --- Loader helpers ---
|
||||||
|
function showLoader(text = "Uploading...") {
|
||||||
|
const loader = document.getElementById("global-loader");
|
||||||
|
if (loader) {
|
||||||
|
loader.classList.add("active");
|
||||||
|
document.getElementById("loader-text").textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function hideLoader() {
|
||||||
|
const loader = document.getElementById("global-loader");
|
||||||
|
if (loader) loader.classList.remove("active");
|
||||||
|
}
|
||||||
|
|
||||||
// --- Upload gallery images ---
|
// --- Upload gallery images ---
|
||||||
document.getElementById('upload-gallery').addEventListener('change', async (e) => {
|
const galleryInput = document.getElementById('upload-gallery');
|
||||||
const files = e.target.files;
|
if (galleryInput) {
|
||||||
if (!files.length) return;
|
galleryInput.addEventListener('change', async (e) => {
|
||||||
|
const files = e.target.files;
|
||||||
|
if (!files.length) return;
|
||||||
|
showLoader("Uploading photos...");
|
||||||
|
const formData = new FormData();
|
||||||
|
for (const file of files) formData.append('files', file);
|
||||||
|
|
||||||
const formData = new FormData();
|
try {
|
||||||
for (const file of files) formData.append('files', file);
|
const res = await fetch('/api/gallery/upload', { method: 'POST', body: formData });
|
||||||
|
const data = await res.json();
|
||||||
try {
|
hideLoader();
|
||||||
const res = await fetch('/api/gallery/upload', { method: 'POST', body: formData });
|
if (res.ok) {
|
||||||
const data = await res.json();
|
showToast(`✅ ${data.uploaded.length} gallery image(s) uploaded!`, "success");
|
||||||
if (res.ok) {
|
if (typeof refreshGallery === "function") refreshGallery();
|
||||||
showToast(`✅ ${data.uploaded.length} gallery image(s) uploaded!`, "success");
|
} else showToast('Error: ' + data.error, "error");
|
||||||
refreshGallery();
|
} catch(err) {
|
||||||
} else showToast('Error: ' + data.error, "error");
|
hideLoader();
|
||||||
} catch(err) {
|
console.error(err);
|
||||||
console.error(err);
|
showToast('Server error!', "error");
|
||||||
showToast('Server error!', "error");
|
} finally { e.target.value = ''; }
|
||||||
} finally { e.target.value = ''; }
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
// --- Upload hero images ---
|
// --- Upload hero images ---
|
||||||
document.getElementById('upload-hero').addEventListener('change', async (e) => {
|
const heroInput = document.getElementById('upload-hero');
|
||||||
const files = e.target.files;
|
if (heroInput) {
|
||||||
if (!files.length) return;
|
heroInput.addEventListener('change', async (e) => {
|
||||||
|
const files = e.target.files;
|
||||||
|
if (!files.length) return;
|
||||||
|
showLoader("Uploading hero photos...");
|
||||||
|
const formData = new FormData();
|
||||||
|
for (const file of files) formData.append('files', file);
|
||||||
|
|
||||||
const formData = new FormData();
|
try {
|
||||||
for (const file of files) formData.append('files', file);
|
const res = await fetch('/api/hero/upload', { method: 'POST', body: formData });
|
||||||
|
const data = await res.json();
|
||||||
try {
|
hideLoader();
|
||||||
const res = await fetch('/api/hero/upload', { method: 'POST', body: formData });
|
if (res.ok) {
|
||||||
const data = await res.json();
|
showToast(`✅ ${data.uploaded.length} hero image(s) uploaded!`, "success");
|
||||||
if (res.ok) {
|
if (typeof refreshHero === "function") refreshHero();
|
||||||
showToast(`✅ ${data.uploaded.length} hero image(s) uploaded!`, "success");
|
} else showToast('Error: ' + data.error, "error");
|
||||||
refreshHero();
|
} catch(err) {
|
||||||
} else showToast('Error: ' + data.error, "error");
|
hideLoader();
|
||||||
} catch(err) {
|
console.error(err);
|
||||||
console.error(err);
|
showToast('Server error!', "error");
|
||||||
showToast('Server error!', "error");
|
} finally { e.target.value = ''; }
|
||||||
} finally { e.target.value = ''; }
|
});
|
||||||
});
|
}
|
@ -1,43 +1,9 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "template/base.html" %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
{% block title %}Lumeex - Site Info{% endblock %}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta charset="UTF-8">
|
{% block content %}
|
||||||
<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">
|
|
||||||
<input type="checkbox" id="nav-check">
|
|
||||||
<div class="nav-header">
|
|
||||||
<div class="nav-title">
|
|
||||||
<a href="/"><img src="{{ url_for('static', filename='img/logo.svg') }}"></a>
|
|
||||||
</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"><a href="/gallery-editor">Gallery</a></li>
|
|
||||||
<li class="nav-item"><a href="/site-info">Site info</a></li>
|
|
||||||
<li class="nav-item"><a href="/theme-editor">Theme info</a></li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<button id="build-btn" class="button">🚀 Build !</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Toast container for notifications -->
|
|
||||||
<div id="site-info" class="content-inner">
|
|
||||||
<div id="toast-container"></div>
|
|
||||||
<h1>Edit Site Info</h1>
|
<h1>Edit Site Info</h1>
|
||||||
<form id="site-info-form">
|
<form id="site-info-form">
|
||||||
<!-- Info Section -->
|
<!-- Info Section -->
|
||||||
@ -171,64 +137,46 @@
|
|||||||
<p> Follow the steps to generate your static gallery</p>
|
<p> Follow the steps to generate your static gallery</p>
|
||||||
<ul id="stepper">
|
<ul id="stepper">
|
||||||
<li><a href="/gallery-editor">Upload your photos</a></li>
|
<li><a href="/gallery-editor">Upload your photos</a></li>
|
||||||
<div>→</div>
|
<div></div>
|
||||||
<li><a class="step-active" href="/site-info">Configure site info</a></li>
|
<li><a class="step-active" href="/site-info">Configure site info</a></li>
|
||||||
<div>→</div>
|
<div></div>
|
||||||
<li><a href="/theme-editor">Customize your theme</a></li>
|
<li><a href="/theme-editor">Customize your theme</a></li>
|
||||||
<div>→</div>
|
<div></div>
|
||||||
<li><button id="stepper-build">Generate your static site!</button></li>
|
<li><button id="stepper-build">Generate your static site!</button></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Delete confirmation modal (now outside .content-inner) -->
|
<!-- Delete thumbnail confirmation modal-->
|
||||||
<div id="delete-modal" class="modal" style="display:none;">
|
<div class="content-inner">
|
||||||
<div class="modal-content">
|
<div id="delete-modal" class="modal" style="display:none;">
|
||||||
<span id="delete-modal-close" class="modal-close">×</span>
|
<div class="modal-content">
|
||||||
<h3>Confirm Deletion</h3>
|
<span id="delete-modal-close" class="modal-close">×</span>
|
||||||
<p id="delete-modal-text">Are you sure you want to remove this thumbnail?</p>
|
<h3>Confirm Deletion</h3>
|
||||||
<div class="modal-actions">
|
<p id="delete-modal-text">Are you sure you want to remove this thumbnail?</p>
|
||||||
<button id="delete-modal-confirm" class="modal-btn danger">Remove</button>
|
<div class="modal-actions">
|
||||||
<button id="delete-modal-cancel" class="modal-btn">Cancel</button>
|
<button id="delete-modal-confirm" class="modal-btn danger">Remove</button>
|
||||||
|
<button id="delete-modal-cancel" class="modal-btn">Cancel</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Delete theme confirmation modal -->
|
<!-- Delete theme confirmation modal -->
|
||||||
<div id="delete-theme-modal" class="modal" style="display:none;">
|
<div class="content-inner">
|
||||||
<div class="modal-content">
|
<div id="delete-theme-modal" class="modal" style="display:none;">
|
||||||
<span id="delete-theme-modal-close" class="modal-close">×</span>
|
<div class="modal-content">
|
||||||
<h3>Confirm Theme Deletion</h3>
|
<span id="delete-theme-modal-close" class="modal-close">×</span>
|
||||||
<p id="delete-theme-modal-text">Are you sure you want to remove this theme?</p>
|
<h3>Confirm Theme Deletion</h3>
|
||||||
<div class="modal-actions">
|
<p id="delete-theme-modal-text">Are you sure you want to remove this theme?</p>
|
||||||
<button id="delete-theme-modal-confirm" class="modal-btn danger">Remove</button>
|
<div class="modal-actions">
|
||||||
<button id="delete-theme-modal-cancel" class="modal-btn">Cancel</button>
|
<button id="delete-theme-modal-confirm" class="modal-btn danger">Remove</button>
|
||||||
</div>
|
<button id="delete-theme-modal-cancel" class="modal-btn">Cancel</button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Build success modal -->
|
|
||||||
<div id="build-success-modal" class="modal" style="display:none;">
|
|
||||||
<div class="modal-content">
|
|
||||||
<span id="build-success-modal-close" class="modal-close">×</span>
|
|
||||||
<h3>✅ Build completed!</h3>
|
|
||||||
<p>Your files are available in the output folder.</p>
|
|
||||||
<button id="download-zip-btn" class="modal-btn">Download ZIP</button>
|
|
||||||
<div id="zip-loader" style="display:none;">Creating ZIP...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="footer">
|
|
||||||
<div class="content-inner">
|
|
||||||
<div class="footer-container">
|
|
||||||
<div class="footer-credit">
|
|
||||||
<p><a href="https//lumeex.djeex.fr"><span class="lum-first">Lum</span><span class="lum-second">eex</span> v1.3</a> — © 2025</p>
|
|
||||||
</div>
|
|
||||||
<div class="footer-links">
|
|
||||||
<a class="footer-link documentation" href="https://lumeex.djeex.fr"><span class="icon"><img src="/img/favicon.svg"></span><span class="icon-text">Documentation</span></a>
|
|
||||||
<a class="footer-link gitea" href="https://gitea.com/Djeex/lumeex"><span class="icon"><img src="/img/gitea.svg"></span><span class="icon-text">Giteex</span></a>
|
|
||||||
<a class="footer-link github" href="https://github.com/Djeex/lumeex"><span class="icon"><img src="/img/github.svg"></span><span class="icon-text">Github</span></a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="{{ url_for('static', filename='js/site-info.js')}}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/build.js') }}"></script>
|
{% endblock %}
|
||||||
</body>
|
|
||||||
</html>
|
{% block scripts %}
|
||||||
|
<script src="{{ url_for('static', filename='js/site-info.js') }}"></script>
|
||||||
|
{% endblock %}
|
@ -1,14 +1,13 @@
|
|||||||
/* --- Base Styles --- */
|
/* --- 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;
|
|
||||||
background: #111010;
|
background: #111010;
|
||||||
/* background:radial-gradient(ellipse at bottom center, #002a30, #000000bd), radial-gradient(ellipse at top center, #0558a8, #000000fa); */
|
|
||||||
color: #FBFBFB;
|
color: #FBFBFB;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
margin:0px;
|
margin:0px;
|
||||||
|
width: 100vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@ -16,15 +15,6 @@ a {
|
|||||||
color: #d3d3d3;
|
color: #d3d3d3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-inner {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 1140px;
|
|
||||||
padding: 0 40px;
|
|
||||||
padding-top: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
h1, h2 {
|
h1, h2 {
|
||||||
color: #FBFBFB;
|
color: #FBFBFB;
|
||||||
}
|
}
|
||||||
@ -32,26 +22,204 @@ h2 {
|
|||||||
color: #55c3ec;
|
color: #55c3ec;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Toolbar --- */
|
.content-inner {
|
||||||
.toolbar {
|
margin: 0 auto;
|
||||||
margin-bottom: 20px;
|
max-width: 1220px;
|
||||||
|
padding-top: 70px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.inner {
|
||||||
|
padding: 0 40px;
|
||||||
|
margin: auto;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar button {
|
|
||||||
margin-right: 10px;
|
/* --- Navbar & Burger Menu --- */
|
||||||
padding: 8px 12px;
|
|
||||||
|
.nav-bar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 70px;
|
||||||
|
background: #0c0d0c29;
|
||||||
|
z-index: 1000;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-bottom: 1px solid #21212157;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
max-width: 1140px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 40px;
|
||||||
|
margin: 0 auto;
|
||||||
|
height: 70px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 22px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav img {
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: bold;
|
||||||
|
color:#fff;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item a:hover {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #55c3ec;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list > li + li::before {
|
||||||
|
content: " → ";
|
||||||
|
color: #ffc700;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
padding: 10px 25px;
|
||||||
|
border-radius: 40px;
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
background: linear-gradient(135deg, #26c4ff, #016074);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
border: none;
|
border: none;
|
||||||
background-color: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 4px;
|
transition: background 0.2s;
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar button:hover {
|
.button:hover {
|
||||||
background-color: #45a049;
|
background: linear-gradient(135deg, #72d9ff, #26657e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Burger Menu --- */
|
||||||
|
.nav-burger {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: auto;
|
||||||
|
z-index: 1100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-burger span {
|
||||||
|
display: block;
|
||||||
|
width: 28px;
|
||||||
|
height: 4px;
|
||||||
|
margin: 4px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: 0.3s;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Responsive Navbar & Burger --- */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.nav-burger {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list > li::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
position: absolute;
|
||||||
|
top: 70px;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 24px 0 12px 0;
|
||||||
|
display: none;
|
||||||
|
z-index: 1099;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
background-color: #000000d4
|
||||||
|
}
|
||||||
|
.nav-links .nav-list {
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.nav-links .nav-item {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.nav-links .nav-item a,
|
||||||
|
.nav-links .nav-item button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 18px;
|
||||||
|
border: none;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show menu when burger is checked */
|
||||||
|
.nav-toggle:checked ~ .nav-burger + .nav-links {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
/* Animate burger to X */
|
||||||
|
.nav-toggle:checked ~ .nav-burger span:nth-child(1) {
|
||||||
|
transform: translateY(12px) rotate(45deg);
|
||||||
|
}
|
||||||
|
.nav-toggle:checked ~ .nav-burger span:nth-child(2) {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.nav-toggle:checked ~ .nav-burger span:nth-child(3) {
|
||||||
|
transform: translateY(-12px) rotate(-45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
/* --- Upload Section --- */
|
/* --- Upload Section --- */
|
||||||
.section {
|
.section {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
@ -60,6 +228,8 @@ h2 {
|
|||||||
border: 1px solid #2f2e2e80;
|
border: 1px solid #2f2e2e80;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 0px 20px 20px 20px;
|
padding: 0px 20px 20px 20px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section label {
|
.section label {
|
||||||
@ -113,22 +283,6 @@ h2 {
|
|||||||
background-color: #d32f2f;
|
background-color: #d32f2f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Responsive Adjustments --- */
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
body {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar button {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Toast Notifications --- */
|
/* --- Toast Notifications --- */
|
||||||
#toast-container {
|
#toast-container {
|
||||||
@ -165,7 +319,6 @@ h2 {
|
|||||||
/* --- Tags --- */
|
/* --- Tags --- */
|
||||||
.tag-input {
|
.tag-input {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap-reverse;
|
|
||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
@ -231,6 +384,24 @@ h2 {
|
|||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.photo button.validate-tag-btn {
|
||||||
|
border-radius: 30px;
|
||||||
|
border: none;
|
||||||
|
background: #049b3d;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 4px;
|
||||||
|
transition: all ease 0.2s;
|
||||||
|
width: 35px;
|
||||||
|
border: 1px solid #585858;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo button.validate-tag-btn:hover {
|
||||||
|
background: #02cb4e;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.suggestions li.selected {
|
.suggestions li.selected {
|
||||||
background-color: #007782;
|
background-color: #007782;
|
||||||
color: white;
|
color: white;
|
||||||
@ -259,149 +430,7 @@ h2 {
|
|||||||
width: 100%
|
width: 100%
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Top Bar & Navigation --- */
|
/* --- Upload Buttons --- */
|
||||||
.nav {
|
|
||||||
height: 100%;
|
|
||||||
max-width: 1140px;
|
|
||||||
padding: 0 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-bar {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 70px;
|
|
||||||
background-color: #0c0d0c29;
|
|
||||||
z-index: 1000;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
border-bottom: 1px solid #21212157;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav img {
|
|
||||||
height: 30px;
|
|
||||||
padding: 0px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > .nav-header {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > .nav-header > .nav-title {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 22px;
|
|
||||||
color: #fff;
|
|
||||||
padding: 0;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > .nav-btn {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn label {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > .nav-links {
|
|
||||||
display: inline;
|
|
||||||
float: right;
|
|
||||||
font-size: 14px;
|
|
||||||
height: 100%;
|
|
||||||
line-height: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-list {
|
|
||||||
list-style-type: disc;
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > .nav-links > .nav-list > .nav-item > a {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0px 15px 0px 15px;
|
|
||||||
text-decoration: none;
|
|
||||||
height: 100%;
|
|
||||||
font-weight: 700;
|
|
||||||
color:#fff
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > .nav-links > .nav-list > .nav-item > a:hover {
|
|
||||||
color: #55c3ec;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > .nav-links > .nav-list > .nav-item > a:active {
|
|
||||||
color: #55c3ec;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > #nav-check {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-list > li + li::before{
|
|
||||||
content: " → ";
|
|
||||||
color: #ffc700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-cta {
|
|
||||||
display: inline;
|
|
||||||
float: right;
|
|
||||||
height: 70px;
|
|
||||||
line-height: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-cta > .arrow {
|
|
||||||
font-size: 12px;
|
|
||||||
display: inline;
|
|
||||||
color: #ffc700;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-bar .button {
|
|
||||||
padding: 10px 25px;
|
|
||||||
border-radius: 40px;
|
|
||||||
margin: 15px 20px 15px 10px;
|
|
||||||
font-size: 14px;
|
|
||||||
display: inline;
|
|
||||||
background: linear-gradient(135deg, #26c4ff, #016074);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: 700;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-bar .button:hover {
|
|
||||||
background: linear-gradient(135deg, #72d9ff, #26657e);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-links > ul {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn span {
|
|
||||||
display: block;
|
|
||||||
height: 4px;
|
|
||||||
width: 28px;
|
|
||||||
background: #fff;
|
|
||||||
margin: 4px 0;
|
|
||||||
border-radius: 2px;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
/* --- Custom Upload Buttons --- */
|
|
||||||
.up-btn, .footer-links a{
|
.up-btn, .footer-links a{
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: #00000000;
|
background: #00000000;
|
||||||
@ -413,7 +442,6 @@ h2 {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
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);*/
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
border: 1px solid #585858;
|
border: 1px solid #585858;
|
||||||
}
|
}
|
||||||
@ -430,18 +458,20 @@ h2 {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
margin: 0 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
background: #131313;
|
background: #000000a3;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 2rem 2.5rem;
|
padding: 2rem 2.5rem;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 4px 24px rgba(0,0,0,0.25);
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
|
||||||
min-width: 300px;
|
min-width: 200px;
|
||||||
max-width: 90vw;
|
max-width: 90vw;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-close {
|
.modal-close {
|
||||||
@ -493,11 +523,12 @@ h2 {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove All Buttons */
|
/* --- Remove All Buttons --- */
|
||||||
#remove-all-hero, #remove-all-gallery {
|
#remove-all-hero, #remove-all-gallery {
|
||||||
background: #2d2d2d;
|
background: #2d2d2d;
|
||||||
color: white;
|
color: white;
|
||||||
display: none;
|
display: none;
|
||||||
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#remove-all-gallery:hover,
|
#remove-all-gallery:hover,
|
||||||
@ -505,7 +536,6 @@ h2 {
|
|||||||
background: rgb(121, 26, 19);
|
background: rgb(121, 26, 19);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive: stack buttons vertically on small screens */
|
|
||||||
@media (max-width: 500px) {
|
@media (max-width: 500px) {
|
||||||
.upload-actions-row {
|
.upload-actions-row {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -514,8 +544,7 @@ h2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Site Info --- */
|
/* --- Forms --- */
|
||||||
|
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
background-color: rgb(67 67 67 / 26%);
|
background-color: rgb(67 67 67 / 26%);
|
||||||
@ -543,7 +572,7 @@ legend {
|
|||||||
|
|
||||||
.input-field {
|
.input-field {
|
||||||
flex: 1 1 calc(33.333% - 18px);
|
flex: 1 1 calc(33.333% - 18px);
|
||||||
min-width: 220px;
|
min-width: 150px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -575,6 +604,16 @@ label {
|
|||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
|
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#theme-editor-form input, #theme-editor-form textarea,#theme-editor-form select {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#theme-editor-form .fields {
|
||||||
|
gap: 0 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#site-info-form input::placeholder,
|
#site-info-form input::placeholder,
|
||||||
#theme-editor-form input::placeholder,
|
#theme-editor-form input::placeholder,
|
||||||
#site-info-form textarea::placeholder,
|
#site-info-form textarea::placeholder,
|
||||||
@ -634,7 +673,7 @@ img#thumbnail-preview {
|
|||||||
background: #00000000;
|
background: #00000000;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 18px;
|
border-radius: 30px;
|
||||||
padding: 7px 18px;
|
padding: 7px 18px;
|
||||||
font-size: 0.98em;
|
font-size: 0.98em;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
@ -648,20 +687,6 @@ img#thumbnail-preview {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
#site-info-form, #theme-editor-form {
|
|
||||||
padding: 18px 8px;
|
|
||||||
}
|
|
||||||
.fields,
|
|
||||||
fieldset {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
.input-field {
|
|
||||||
min-width: 100%;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#site-info-form button.remove-menu-item, #site-info-form button.remove-ip-paragraph, #theme-editor-form button.remove-menu-item, #theme-editor-form button.remove-ip-paragraph {
|
#site-info-form button.remove-menu-item, #site-info-form button.remove-ip-paragraph, #theme-editor-form button.remove-menu-item, #theme-editor-form button.remove-ip-paragraph {
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
@ -693,19 +718,21 @@ img#thumbnail-preview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.color-fields {
|
.fields.color-fields {
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#theme-editor button.color-btn {
|
#theme-editor-form button.color-btn {
|
||||||
height: 100%;
|
height: 40px;
|
||||||
border-radius: 8px;
|
border-radius: 40px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="color"].color-input {
|
input[type="color"].color-input {
|
||||||
@ -744,13 +771,13 @@ fieldset p, .section p {
|
|||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Home --- */
|
/* --- Stepper --- */
|
||||||
|
|
||||||
#stepper {
|
#stepper {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 18px;
|
gap: 18px;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
align-items: stretch; /* Ensures all children match tallest height */
|
align-items: stretch;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
@ -763,6 +790,11 @@ fieldset p, .section p {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#stepper > div::before {
|
||||||
|
content: "→";
|
||||||
|
color: #ffc700;
|
||||||
|
}
|
||||||
|
|
||||||
#stepper li a, #stepper li button, #stepper > div {
|
#stepper li a, #stepper li button, #stepper > div {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
@ -815,11 +847,11 @@ justify-content: center;
|
|||||||
}
|
}
|
||||||
|
|
||||||
#stepper li {
|
#stepper li {
|
||||||
flex: 1 1 auto; /* li can grow/shrink, width is flexible */
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#stepper > div {
|
#stepper > div {
|
||||||
flex: 0 0 auto; /* Do not grow or shrink, width is auto */
|
flex: 0 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -827,10 +859,16 @@ justify-content: center;
|
|||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
min-width: 0; /* Prevent unwanted minimum width */
|
min-width: 0;
|
||||||
width: auto; /* Let width fit content */
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stepper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Footer --- */
|
||||||
|
|
||||||
#footer {
|
#footer {
|
||||||
background-color: #0c0d0c29;
|
background-color: #0c0d0c29;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
@ -888,3 +926,114 @@ justify-content: center;
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Global Loader & Spinner --- */
|
||||||
|
#global-loader {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; width: 100vw; height: 100vh;
|
||||||
|
z-index: 99999;
|
||||||
|
background: rgba(0,0,0,0.4);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#global-loader.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-inner {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.9s cubic-bezier(.4,0,.2,1);
|
||||||
|
background: #0c0d0c29;
|
||||||
|
padding: 32px 48px;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 2px 24px #000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#global-loader.active .loader-inner {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-spinner {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border: 6px solid #55c3ec;
|
||||||
|
border-top: 6px solid #222;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loader-text {
|
||||||
|
margin-top: 18px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin { 100% { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
|
||||||
|
/* --- Responsive Adjustments --- */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
.inner {
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
.section label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu-items-list > div {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stepper {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stepper li {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-container, .footer-links {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ip-list > div {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
#stepper > div {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
#stepper > div::before {
|
||||||
|
content: "↓";
|
||||||
|
color: #ffc700;
|
||||||
|
}
|
||||||
|
#stepper > div {
|
||||||
|
/* Hide the default arrow */
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
#site-info-form, #theme-editor-form {
|
||||||
|
padding: 18px 8px;
|
||||||
|
}
|
||||||
|
.input-field {
|
||||||
|
min-width: 100%;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#color-picker .input-field{
|
||||||
|
min-width: 170px;
|
||||||
|
}
|
||||||
|
#theme-editor-form .fields.color-fields {
|
||||||
|
gap: 0 8px;
|
||||||
|
position: relative;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
}
|
86
src/webui/template/base.html
Normal file
86
src/webui/template/base.html
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{% block title %}Lumeex{% endblock %}</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
|
||||||
|
<meta name="darkreader-lock">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Top bar -->
|
||||||
|
<div class="nav-bar">
|
||||||
|
<div class="content-inner nav">
|
||||||
|
<div class="nav-header">
|
||||||
|
<a href="/" class="nav-title">
|
||||||
|
<img src="{{ url_for('static', filename='img/logo.svg') }}">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<!-- Burger toggle input and label -->
|
||||||
|
<input type="checkbox" id="nav-toggle" class="nav-toggle" hidden>
|
||||||
|
<label for="nav-toggle" class="nav-burger">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</label>
|
||||||
|
<div class="nav-links">
|
||||||
|
<ul class="nav-list">
|
||||||
|
<li class="nav-item"><a href="/gallery-editor">Gallery</a></li>
|
||||||
|
<li class="nav-item"><a href="/site-info">Site info</a></li>
|
||||||
|
<li class="nav-item"><a href="/theme-editor">Theme info</a></li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<button id="build-btn" class="button">🚀 Build!</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Toast container for notifications -->
|
||||||
|
<div class="content-inner first-content">
|
||||||
|
<div class="inner">
|
||||||
|
<div id="toast-container"></div>
|
||||||
|
<!-- Page content -->
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
<!-- Build success modal -->
|
||||||
|
<div class="content-inner">
|
||||||
|
<div id="build-success-modal" class="modal" style="display:none;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<span id="build-success-modal-close" class="modal-close">×</span>
|
||||||
|
<h3>✅ Build completed!</h3>
|
||||||
|
<p>Your files are available in the output folder.</p>
|
||||||
|
<button id="download-zip-btn" class="modal-btn">Download ZIP</button>
|
||||||
|
<div id="zip-loader" style="display:none;">Creating ZIP...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Footer -->
|
||||||
|
<div id="footer">
|
||||||
|
<div class="content-inner">
|
||||||
|
<div class="inner">
|
||||||
|
<div class="footer-container">
|
||||||
|
<div class="footer-credit">
|
||||||
|
<p><a href="https//lumeex.djeex.fr"><span class="lum-first">Lum</span><span class="lum-second">eex</span> v{{ lumeex_version }}</a> — © 2025</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer-links">
|
||||||
|
<a class="footer-link documentation" href="https://lumeex.djeex.fr"><span class="icon"><img src="/img/favicon.svg"></span><span class="icon-text">Documentation</span></a>
|
||||||
|
<a class="footer-link gitea" href="https://gitea.com/Djeex/lumeex"><span class="icon"><img src="/img/gitea.svg"></span><span class="icon-text">Giteex</span></a>
|
||||||
|
<a class="footer-link github" href="https://github.com/Djeex/lumeex"><span class="icon"><img src="/img/github.svg"></span><span class="icon-text">Github</span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Loader -->
|
||||||
|
<div id="global-loader">
|
||||||
|
<div class="loader-inner">
|
||||||
|
<div class="loader-spinner"></div>
|
||||||
|
<div id="loader-text">Uploading...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Scripts -->
|
||||||
|
<script src="{{ url_for('static', filename='js/build.js') }}" defer></script>
|
||||||
|
{% block scripts %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,52 +1,17 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "template/base.html" %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
{% block title %}Lumeex - Theme Editor{% endblock %}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta charset="UTF-8">
|
{% block content %}
|
||||||
<title>Theme Editor</title>
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
|
<h1>Edit Theme</h1>
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- Top bar -->
|
|
||||||
<div class="nav-bar">
|
|
||||||
<div class="content-inner nav">
|
|
||||||
<input type="checkbox" id="nav-check">
|
|
||||||
<div class="nav-header">
|
|
||||||
<div class="nav-title">
|
|
||||||
<a href="/"><img src="{{ url_for('static', filename='img/logo.svg') }}"></a>
|
|
||||||
</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"><a href="/gallery-editor">Gallery</a></li>
|
|
||||||
<li class="nav-item"><a href="/site-info">Site info</a></li>
|
|
||||||
<li class="nav-item"><a href="/theme-editor">Theme info</a></li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<button id="build-btn" class="button">🚀 Build !</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Toast container for notifications -->
|
|
||||||
<div id="theme-editor" class="content-inner">
|
|
||||||
<div id="toast-container"></div>
|
|
||||||
<h1>Edit Theme</h1>
|
|
||||||
<!-- Show current theme -->
|
<!-- Show current theme -->
|
||||||
<div class="theme-info">
|
<div class="theme-info">
|
||||||
<strong>Current theme:</strong> <span id="current-theme"></span>
|
<strong>Current theme:</strong> <span id="current-theme"></span>
|
||||||
</div>
|
</div>
|
||||||
<form id="theme-editor-form">
|
<form id="theme-editor-form">
|
||||||
<!-- Colors Section -->
|
<!-- Colors Section -->
|
||||||
<fieldset>
|
<fieldset id="color-picker">
|
||||||
<h2>Colors</h2>
|
<h2>Colors</h2>
|
||||||
<p>Set the color values for your theme</p>
|
<p>Set the color values for your theme</p>
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
@ -175,11 +140,11 @@
|
|||||||
<p> Follow the steps to generate your static gallery</p>
|
<p> Follow the steps to generate your static gallery</p>
|
||||||
<ul id="stepper">
|
<ul id="stepper">
|
||||||
<li><a href="/gallery-editor">Upload your photos</a></li>
|
<li><a href="/gallery-editor">Upload your photos</a></li>
|
||||||
<div>→</div>
|
<div></div>
|
||||||
<li><a href="/site-info">Configure site info</a></li>
|
<li><a href="/site-info">Configure site info</a></li>
|
||||||
<div>→</div>
|
<div></div>
|
||||||
<li><a class="step-active" href="/theme-editor">Customize your theme</a></li>
|
<li><a class="step-active" href="/theme-editor">Customize your theme</a></li>
|
||||||
<div>→</div>
|
<div></div>
|
||||||
<li><button id="stepper-build">Generate your static site!</button></li>
|
<li><button id="stepper-build">Generate your static site!</button></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -209,31 +174,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Build success modal -->
|
|
||||||
<div id="build-success-modal" class="modal" style="display:none;">
|
{% endblock %}
|
||||||
<div class="modal-content">
|
|
||||||
<span id="build-success-modal-close" class="modal-close">×</span>
|
{% block scripts %}
|
||||||
<h3>✅ Build completed!</h3>
|
|
||||||
<p>Your files are available in the output folder.</p>
|
|
||||||
<button id="download-zip-btn" class="modal-btn">Download ZIP</button>
|
|
||||||
<div id="zip-loader" style="display:none;">Creating ZIP...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="footer">
|
|
||||||
<div class="content-inner">
|
|
||||||
<div class="footer-container">
|
|
||||||
<div class="footer-credit">
|
|
||||||
<p><a href="https//lumeex.djeex.fr"><span class="lum-first">Lum</span><span class="lum-second">eex</span> v1.3</a> — © 2025</p>
|
|
||||||
</div>
|
|
||||||
<div class="footer-links">
|
|
||||||
<a class="footer-link documentation" href="https://lumeex.djeex.fr"><span class="icon"><img src="/img/favicon.svg"></span><span class="icon-text">Documentation</span></a>
|
|
||||||
<a class="footer-link gitea" href="https://gitea.com/Djeex/lumeex"><span class="icon"><img src="/img/gitea.svg"></span><span class="icon-text">Giteex</span></a>
|
|
||||||
<a class="footer-link github" href="https://github.com/Djeex/lumeex"><span class="icon"><img src="/img/github.svg"></span><span class="icon-text">Github</span></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script src="{{ url_for('static', filename='js/theme-editor.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/theme-editor.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/build.js') }}"></script>
|
{% endblock %}
|
||||||
</body>
|
|
||||||
</html>
|
|
Reference in New Issue
Block a user