Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
39b24a05cb | |||
d379fc63d1 | |||
af6b2289e0 | |||
5728ebb649 | |||
7f86f8f522 | |||
080209d202 | |||
e4a9c57b31 | |||
d0fe57fe9c | |||
bf71ac6dde | |||
f069ee1065 | |||
b0c991af58 | |||
224441f629 | |||
b378e9a386 | |||
2749302082 |
7
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.venv
|
||||
.output
|
||||
__pycache__
|
||||
.*
|
||||
!.gitignore
|
||||
output/
|
||||
__pycache__/
|
264
README.MD
@ -1,63 +1,73 @@
|
||||
<h1 align="center">Lumeex</h1>
|
||||
<div align="center" >
|
||||
<img src="https://git.djeex.fr/Djeex/lumeex/raw/branch/main/illustration/lumeex.png" alt="Lumeex Screenshot">
|
||||
<div align="center">
|
||||
<img src="https://git.djeex.fr/Djeex/lumeex/raw/branch/main/illustration/logo.svg" alt="Lumeex Screenshot" width="400"/>
|
||||
</div>
|
||||
<p/>
|
||||
<div align="center">
|
||||
<p>Yet another minimalist, lightweight photo gallery static site generator.</p>
|
||||
</div>
|
||||
</p>
|
||||
<div align="center">
|
||||
<img src="https://git.djeex.fr/Djeex/lumeex/raw/branch/main/illustration/lumeex.png" alt="Lumeex Screenshot" />
|
||||
</div>
|
||||
|
||||
**Lumeex** - Yet another minimalist photo gallery with a static site generator.
|
||||
Lumeex is a static site generator designed to create minimalist photo galleries that highlight your artworks over the author. It empowers users to organize and explore images using tags, with each page load presenting photos in a random order to encourage discovery of new content.
|
||||
|
||||
Lumeex is a static site generator that builds a minimalist photo gallery. The project was born from the need to create a gallery focused more on the artworks than the author, while allowing users to organize works using tags and share filtered views. In this spirit, each page load displays the photos in random order, allowing users to discover new content they might not have seen before.
|
||||
The project includes two thoughtfully designed themes—one modern, one minimalistic—both crafted to keep the spotlight on your photos:
|
||||
|
||||
The project comes with two themes: one modern, the other more minimalistic, both designed to keep the focus on the artworks:
|
||||
- Modern 👉 [demo](https://modern.djeex.fr)
|
||||
- Typewriter 👉 [demo](https://typewriter.djeex.fr)
|
||||
- **Modern** — [View Demo](https://modern.djeex.fr)
|
||||
- **Typewriter** — [View Demo](https://typewriter.djeex.fr)
|
||||
|
||||
> [!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.
|
||||
|
||||
> [!NOTE]
|
||||
> _This GitHub repository is a mirror of https://git.djeex.fr/Djeex/lumeex. You’ll find the complete package, history, and release notes there. An LLM is used for bug checking._
|
||||
|
||||
## 📌 Table of Contents
|
||||
|
||||
- [Features](#features)
|
||||
- [Python Installation](#python-installation)
|
||||
- [Configuration](#configuration)
|
||||
- [Build the Site](#build-the-site)
|
||||
- [✨ Features](#-features)
|
||||
- [🐍 Python Installation](#-python-installation)
|
||||
- [⚙️ Configuration](#-configuration)
|
||||
|
||||
## Features
|
||||
|
||||
**Gallery (Static Website)**
|
||||
- Photos displayed in a random order on each page load.
|
||||
- Tag-based filtering (with the ability to combine multiple tags).
|
||||
- Shareable URLs with active tag filters.
|
||||
- A photo carousel on the landing page.
|
||||
- A legal notice page.
|
||||
- Two visual themes (easily customizable):
|
||||
- Modern 👉 [demo](https://modern.djeex.fr)
|
||||
- Typewriter 👉 [demo](https://typewriter.djeex.fr)
|
||||
- Supports Google Fonts and local fonts.
|
||||
## ✨ Features
|
||||
|
||||
**No-Code Builder Based on YAML Files**
|
||||
- YAML files to configure site information, SEO, colors, fonts, etc.—no code needed
|
||||
- YAML files to reference and tag photos—no code needed.
|
||||
- *(Optional)* Automatically add photos to the reference file.
|
||||
### Gallery (Static Website)
|
||||
|
||||
**Simple Build Process**
|
||||
- Compiles from YAML config files (theme selection, template building, fonts, colors, etc.).
|
||||
- Automatically converts the favicon to all required formats.
|
||||
- Automatically resize social thumbnail
|
||||
- *(Optional)* Automatically resizes photos to a max width of 1140px.
|
||||
- *(Optional)* Converts images to WebP for better performance.
|
||||
- Outputs a fully generated static website, ready to be copied to any web server.
|
||||
- Photos displayed in a new random order with every page load
|
||||
- Tag-based filtering with multi-tag support
|
||||
- Shareable URLs that retain active tag filters
|
||||
- Photo carousel on the homepage
|
||||
- Legal notice page included
|
||||
- Two customizable visual themes:
|
||||
- Modern — [Demo](https://modern.djeex.fr)
|
||||
- Typewriter — [Demo](https://typewriter.djeex.fr)
|
||||
- Supports Google Fonts and locally hosted fonts
|
||||
|
||||
## Python Installation
|
||||
### No-Code Builder (YAML Based)
|
||||
|
||||
Instructions to run the Python scripts directly.
|
||||
- Configure site info, SEO, colors, fonts, and more through simple YAML files
|
||||
- Reference and tag photos without any coding required
|
||||
- *(Optional)* Automatically update photo references via script
|
||||
|
||||
**Requirements**
|
||||
### Simple Build Process
|
||||
|
||||
- Python 3.11 or higher
|
||||
- PyYAML
|
||||
- Pillow
|
||||
- Compiles static site from YAML configuration files (themes, templates, fonts, colors)
|
||||
- Converts favicon automatically to all required formats
|
||||
- Resizes social sharing thumbnails
|
||||
- *(Optional)* Automatically resizes photos to a maximum width of 1140px
|
||||
- *(Optional)* Converts images to WebP format for optimized performance
|
||||
- Outputs a complete static website ready to deploy on any web server
|
||||
|
||||
**Installation**
|
||||
|
||||
## 🐍 Python Installation
|
||||
|
||||
Run the Python scripts directly with the following prerequisites:
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Git
|
||||
- Python 3.11 or above
|
||||
|
||||
### Installation Steps
|
||||
|
||||
```sh
|
||||
git clone https://git.djeex.fr/Djeex/lumeex.git
|
||||
@ -67,168 +77,8 @@ source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
You're ready to go!
|
||||
You are now ready to use Lumeex!
|
||||
|
||||
## Configuration
|
||||
|
||||
All user configuration files are located in the `config` folder.
|
||||
|
||||
```sh
|
||||
Lumeex/
|
||||
└── config/
|
||||
├── photos/
|
||||
│ ├── gallery
|
||||
│ └── hero
|
||||
├── themes/
|
||||
│ ├── modern
|
||||
│ └── typewriter
|
||||
├── gallery.yaml
|
||||
└── site.yaml
|
||||
```
|
||||
|
||||
**`photos/`**
|
||||
|
||||
- `gallery/`: place your gallery photos here.
|
||||
- `hero/`: place carousel photos for the homepage here.
|
||||
|
||||
> [!TIP]
|
||||
> You can use `gallery.py` to automatically reference all photos in `gallery/` and `hero/` into `gallery.yaml` and `site.yaml` by running `python3 gallery.py` from the `lumeex` directory.
|
||||
> You’ll just need to tag the photos in `gallery.yaml`.
|
||||
|
||||
**`site.yaml`**
|
||||
|
||||
This file contains all your site’s metadata and settings. For example:
|
||||
|
||||
```yaml
|
||||
info:
|
||||
title: your title
|
||||
subtitle: your subtitle
|
||||
description: your description
|
||||
canonical: all, your, keywords
|
||||
author: you
|
||||
google_analytics_id: G-XXXXXXX # optional
|
||||
|
||||
social:
|
||||
instagram_url: https://www.instagram.com/yourprofile
|
||||
thumbnail: gallery/anyphoto.png # put the path from your photo folder to your file
|
||||
menu:
|
||||
items:
|
||||
- label: your_home
|
||||
href: /
|
||||
- label: your_second_menu
|
||||
href: /?tag=yourtag1
|
||||
- label: Your_third_menu
|
||||
href: /?tag=yourtag2
|
||||
|
||||
hero:
|
||||
images:
|
||||
- src: hero/your_photo_1.jpg
|
||||
- src: hero/your_photo_2.jpg
|
||||
- src: hero/your_photo_3.jpg
|
||||
|
||||
footer:
|
||||
copyright: Copyright © 2025 – You
|
||||
legal_link: '/legals.html'
|
||||
legal_label: Legal notice
|
||||
|
||||
build:
|
||||
theme: modern
|
||||
convert_images: false
|
||||
resize_images: false
|
||||
|
||||
legals:
|
||||
hoster_name: Your_hoster
|
||||
hoster_adress: Your hoster address
|
||||
hoster_contact: Your hoster contact
|
||||
intellectual_property:
|
||||
- paragraph: "Your text here"
|
||||
- paragraph: "Your second paragraph here"
|
||||
- paragraph: "Etc..."
|
||||
```
|
||||
|
||||
**`gallery.yaml`**
|
||||
|
||||
Use this file to reference the images in `photos/gallery/`. You can do this manually or automatically by running `python3 gallery.py`. You can also assign tags to the photos here.
|
||||
|
||||
```yaml
|
||||
images:
|
||||
- src: gallery/your_photo_1.jpg
|
||||
tags: ["portrait"]
|
||||
- src: gallery/your_photo_2.jpg
|
||||
tags: ["portrait", "sunset", "boat"]
|
||||
- src: gallery/your_photo_3.jpg
|
||||
tags: ["landscape", "sea", "beach", "sand"]
|
||||
```
|
||||
|
||||
**`themes/`**
|
||||
|
||||
```sh
|
||||
themes/
|
||||
└── yourtheme/
|
||||
├── fonts (optional)
|
||||
├── theme.yaml
|
||||
├── theme.css (optional)
|
||||
└── favicon.png
|
||||
```
|
||||
|
||||
**Lumeex** is shipped with two prebuilt themes:
|
||||
- Modern 👉 [demo](https://modern.djeex.fr)
|
||||
- Typewriter 👉 [demo](https://typewriter.djeex.fr)
|
||||
|
||||
You can edit existing themes or create your own. Each theme can include:
|
||||
- **Required:** a `theme.yaml` file for visual settings (colors, fonts, etc.)
|
||||
- *(Optional)* a `theme.css` file for additional styling
|
||||
- *(Optional)* a `fonts` folder for local fonts
|
||||
- *(Optional)* a square `favicon.png` (min 196px) that will be automatically converted to all required formats.
|
||||
|
||||
Example `theme.yaml`:
|
||||
|
||||
```yaml
|
||||
colors:
|
||||
primary: '#0065a1'
|
||||
primary_dark: '#005384'
|
||||
secondary: '#00b0f0'
|
||||
accent: '#ffc700'
|
||||
text_dark: '#333'
|
||||
background: '#fff'
|
||||
browser_color: '#fff'
|
||||
favicon:
|
||||
path: favicon.png
|
||||
google_fonts:
|
||||
- family: Lato
|
||||
weights:
|
||||
- '200'
|
||||
- '400'
|
||||
- '700'
|
||||
- family: Montserrat
|
||||
weights:
|
||||
- '200'
|
||||
- '400'
|
||||
- '700'
|
||||
fonts:
|
||||
primary:
|
||||
name: Lato
|
||||
fallback: sans-serif
|
||||
secondary:
|
||||
name: Montserrat
|
||||
fallback: serif
|
||||
```
|
||||
|
||||
## Build the Site
|
||||
|
||||
Once everything is configured, make sure you're in the `lumeex` directory and your Python virtual environment is activated (`source .venv/bin/activate`).
|
||||
|
||||
- *(Optional)* Run `python3 gallery.py` to auto-fill `gallery.yaml` and add carousel photos to `site.yaml`. Don't forget to add tags to your photos in `gallery.yaml`.
|
||||
- Run `python3 build.py` to generate the static site.
|
||||
- *(Optional)* Serve locally with:
|
||||
|
||||
```sh
|
||||
python3 -m http.server 3000 --directory .output
|
||||
```
|
||||
|
||||
Then visit `http://localhost:3000` or, if remote, `http://your-server-ip:3000`.
|
||||
|
||||
> [!WARNING]
|
||||
> Use this only to test your site. Don't use python server for production !
|
||||
|
||||
- Finally, copy the contents of the `.output/` directory to your favorite web server.
|
||||
### ⚙️ Configuration
|
||||
For comprehensive documentation on configuration options, customization, and demos, please visit:
|
||||
https://lumeex.djeex.fr
|
24
build.py
@ -13,7 +13,7 @@ logging.basicConfig(level=logging.INFO, format='%(message)s')
|
||||
|
||||
# Define key directories used throughout the script
|
||||
SRC_DIR = Path.cwd()
|
||||
BUILD_DIR = SRC_DIR / ".output"
|
||||
BUILD_DIR = SRC_DIR / "output"
|
||||
TEMPLATE_DIR = SRC_DIR / "src/templates"
|
||||
IMG_DIR = SRC_DIR / "config/photos"
|
||||
JS_DIR = SRC_DIR / "src/public/js"
|
||||
@ -29,8 +29,9 @@ def build():
|
||||
|
||||
# Defining build vars
|
||||
build_date = datetime.now().strftime("%Y%m%d%H%M%S")
|
||||
build_date_version = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
site_vars = load_yaml(SITE_FILE)
|
||||
gallery_sections = load_yaml(GALLERY_FILE)
|
||||
gallery_vars = load_yaml(GALLERY_FILE)
|
||||
build_section = site_vars.get("build", {})
|
||||
theme_name = site_vars.get("build", {}).get("theme", "default")
|
||||
theme_vars, theme_dir = load_theme_config(theme_name, THEMES_DIR)
|
||||
@ -57,13 +58,13 @@ def build():
|
||||
generate_favicon_ico(theme_vars, theme_dir, BUILD_DIR / "favicon.ico")
|
||||
|
||||
# Converting and resizing images if enabled
|
||||
convert_images = build_section.get("convert_images", False)
|
||||
resize_images = build_section.get("resize_images", False)
|
||||
convert_images = build_section.get("convert_images", True)
|
||||
resize_images = build_section.get("resize_images", True)
|
||||
logging.info(f"[~] convert_images = {convert_images}")
|
||||
logging.info(f"[~] resize_images = {resize_images}")
|
||||
|
||||
hero_images = site_vars.get("hero", {}).get("images", [])
|
||||
gallery_images = [img for section in gallery_sections for img in section["images"]] if isinstance(gallery_sections, list) else gallery_sections.get("images", [])
|
||||
hero_images = gallery_vars.get("hero", {}).get("images", [])
|
||||
gallery_images = gallery_vars.get("gallery", {}).get("images", [])
|
||||
|
||||
if convert_images:
|
||||
process_images(hero_images, resize_images, IMG_DIR, BUILD_DIR)
|
||||
@ -72,6 +73,9 @@ def build():
|
||||
copy_original_images(hero_images, IMG_DIR, BUILD_DIR)
|
||||
copy_original_images(gallery_images, IMG_DIR, BUILD_DIR)
|
||||
|
||||
if "hero" not in site_vars:
|
||||
site_vars["hero"] = {} # Initialize an empty hero section
|
||||
|
||||
# Adding menu
|
||||
menu_html = "\n".join(
|
||||
f'<li class="nav-item appear"><a href="{item["href"]}">{item["label"]}</a></li>'
|
||||
@ -121,7 +125,7 @@ def build():
|
||||
gallery_html = render_gallery_images(gallery_images)
|
||||
gallery = render_template(TEMPLATE_DIR / "gallery.html", {"gallery_images": gallery_html})
|
||||
|
||||
signature = f"<!-- Build with Lumeex v1.0 | https://git.djeex.fr/Djeex/lumeex | {build_date} -->"
|
||||
signature = f"<!-- Build with Lumeex v1.2 | https://git.djeex.fr/Djeex/lumeex | {build_date_version} -->"
|
||||
body = f"""
|
||||
<body>
|
||||
<div class="page-loader"><div class="spinner"></div></div>
|
||||
@ -162,7 +166,7 @@ def build():
|
||||
|
||||
# Hero carrousel generator
|
||||
if hero_images:
|
||||
generate_gallery_json_from_images(hero_images, BUILD_DIR / "data" / "gallery.json")
|
||||
generate_gallery_json_from_images(hero_images, BUILD_DIR)
|
||||
else:
|
||||
logging.warning("[~] No hero images found, skipping JSON generation.")
|
||||
|
||||
@ -171,8 +175,8 @@ def build():
|
||||
canonical_url = site_info.get("canonical", "").rstrip("/")
|
||||
if canonical_url:
|
||||
allowed_pages = ["/", "/legals/"]
|
||||
generate_robots_txt(canonical_url, allowed_pages)
|
||||
generate_sitemap_xml(canonical_url, allowed_pages)
|
||||
generate_robots_txt(canonical_url, allowed_pages, BUILD_DIR)
|
||||
generate_sitemap_xml(canonical_url, allowed_pages, BUILD_DIR)
|
||||
else:
|
||||
logging.warning("[~] No canonical URL found in site.yaml info section, skipping robots.txt and sitemap.xml generation.")
|
||||
|
||||
|
@ -1,30 +1,7 @@
|
||||
# Source your photos here
|
||||
# Relative path is set from built img folder
|
||||
# You can also use gallery.py to automatically add photos stored in your /config/photos/gallery folder
|
||||
# Use gallery.py to automatically add photos stored in your /config/photos/gallery folder
|
||||
# Add tags to your photos as shown below
|
||||
# remove the # before [] if you removed all images to use gallery.py
|
||||
images: #[]
|
||||
- src: gallery/almos-bechtold-3402kvtHhOo-unsplash.jpg
|
||||
tags: ["portrait"]
|
||||
- src: gallery/arthur-savary-nLfAqmZ2hJo-unsplash.jpg
|
||||
tags: ["portrait", "sunset", "boat"]
|
||||
- src: gallery/francesco-ungaro-Zbc9Ka8msdI-unsplash.jpg
|
||||
tags: ["landscape", "sea", "beach", "sand"]
|
||||
- src: gallery/gilley-aguilar-ywGDhTlf93E-unsplash.jpg
|
||||
tags: ["landscape", "sky", "cloud", "mountains"]
|
||||
- src: gallery/jacob-reinikainen-nGG6m3RbjSk-unsplash.jpg
|
||||
tags: ["lanscape", "sunset", "mountains"]
|
||||
- src: gallery/jonas-degener-LueP5EdWGFY-unsplash.jpg
|
||||
tags: ["landscape", "mountains", "fog"]
|
||||
- src: gallery/michiel-annaert-M27pZnHV6M0-unsplash.jpg
|
||||
tags: ["flowers", "nature"]
|
||||
- src: gallery/nir-himi-AjecvkfSHxA-unsplash.jpg
|
||||
tags: ["landscape", "mountains", "sky"]
|
||||
- src: gallery/rachel-mcdermott-0fN7Fxv1eWA-unsplash.jpg
|
||||
tags: ["portrait", "black and white"]
|
||||
- src: gallery/tianlei-wu-g5o6T-PWT3g-unsplash.jpg
|
||||
tags: ["cat", "animals"]
|
||||
- src: gallery/we-care-wild-zLweeVLU9Fo-unsplash.jpg
|
||||
tags: ["bison", "animals"]
|
||||
- src: gallery/y-s-z90w7yStOkk-unsplash.jpg
|
||||
tags: ["frog", "green", "animals"]
|
||||
# remove the # before [] if you removed all images to use gallery.py again
|
||||
hero:
|
||||
images: []
|
||||
gallery:
|
||||
images: []
|
@ -1,59 +1,34 @@
|
||||
# This file is filled with the demo info
|
||||
# Please change this by your settings
|
||||
|
||||
# Please change this by your settings.
|
||||
info:
|
||||
title: Lumeex
|
||||
subtitle: A minimalistic Gallery
|
||||
description: A minimalistic Gallery
|
||||
canonical: https://lumeex.djeex.fr
|
||||
keywords: photography, lumen, demo, gallery, minimalistic
|
||||
author: Djeex
|
||||
google_analytics_id: G-XXXXXXX # optional
|
||||
title:
|
||||
subtitle:
|
||||
description:
|
||||
canonical:
|
||||
keywords:
|
||||
author:
|
||||
|
||||
social:
|
||||
instagram_url: https://www.instagram.com/
|
||||
thumbnail: hero/jacob-reinikainen-nGG6m3RbjSk-unsplash.jpg
|
||||
|
||||
instagram_url:
|
||||
thumbnail:
|
||||
|
||||
menu:
|
||||
items:
|
||||
- label: Home
|
||||
href: /
|
||||
- label: Nature
|
||||
href: /?tag=Nature
|
||||
- label: Landscape
|
||||
href: /?tag=landscape
|
||||
- label: Portrait
|
||||
href: /?tag=portrait
|
||||
- label: Animals
|
||||
href: /?tag=animals
|
||||
|
||||
hero:
|
||||
# Source your hero carrousel images here.
|
||||
# Root folder is img.
|
||||
# You can also use gallery.py to automatically add images from config/photos/hero folder
|
||||
# remove the # before [] if you removed all images to use gallery.py
|
||||
images: #[]
|
||||
- src: hero/francesco-ungaro-Zbc9Ka8msdI-unsplash.jpg
|
||||
- src: hero/gilley-aguilar-ywGDhTlf93E-unsplash.jpg
|
||||
- src: hero/jacob-reinikainen-nGG6m3RbjSk-unsplash.jpg
|
||||
|
||||
footer:
|
||||
copyright: Copyright © 2025 – Lumeex
|
||||
copyright: Copyright © 2025
|
||||
legal_link: '/legals/'
|
||||
legal_label: Legal notice
|
||||
|
||||
# Build parameters
|
||||
build:
|
||||
theme: modern # choose a theme in config/theme folder.
|
||||
convert_images: true # use true to automatically convert images to webp small weight images.
|
||||
resize_images: true # use true to automatically resize to width 1140px (maximum width used in the gallery)
|
||||
theme: modern # choose a theme in config/theme folder
|
||||
|
||||
# Change this by your legals
|
||||
legals:
|
||||
hoster_name: Djeex
|
||||
hoster_adress: Paris, France
|
||||
hoster_contact: contact@djeex.fr
|
||||
hoster_name:
|
||||
hoster_adress:
|
||||
hoster_contact:
|
||||
intellectual_property:
|
||||
- paragraph: "Users of this website are required to comply with the provisions of the French Data Protection Act (Loi Informatique et Libertés), the violation of which may result in criminal penalties. In particular, they must refrain from any collection or misuse of personal data accessible through the site, and more generally, from any act likely to infringe upon the privacy or reputation of individuals."
|
||||
- paragraph: "The overall structure, as well as the software, texts, animated or still images, know-how, and all other components of the site, are the exclusive property of Lumeex"
|
||||
- paragraph: "Any total or partial reproduction of this website, by any means whatsoever, without the express authorization of Lumeex, is prohibited and constitutes an infringement punishable under articles L.335-2 and following of the French Intellectual Property Code. The same applies to the databases appearing on the website, which are protected by the provisions of the law of July 1, 1998, implementing into the Intellectual Property Code the European directive of March 11, 1996, on the legal protection of databases."
|
||||
- paragraph: ""
|
||||
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 16 KiB |
36
demo/config/gallery.yaml
Normal file
@ -0,0 +1,36 @@
|
||||
# Use gallery.py to automatically add photos stored in your /config/photos/gallery folder
|
||||
# Add tags to your photos as shown below
|
||||
# remove the # before [] if you removed all images to use gallery.py again
|
||||
|
||||
hero:
|
||||
images: #[]
|
||||
- src: hero/francesco-ungaro-Zbc9Ka8msdI-unsplash.jpg
|
||||
- src: hero/gilley-aguilar-ywGDhTlf93E-unsplash.jpg
|
||||
- src: hero/jacob-reinikainen-nGG6m3RbjSk-unsplash.jpg
|
||||
|
||||
gallery:
|
||||
images: #[]
|
||||
- src: gallery/almos-bechtold-3402kvtHhOo-unsplash.jpg
|
||||
tags: [portrait]
|
||||
- src: gallery/arthur-savary-nLfAqmZ2hJo-unsplash.jpg
|
||||
tags: [portrait, sunset, boat]
|
||||
- src: gallery/francesco-ungaro-Zbc9Ka8msdI-unsplash.jpg
|
||||
tags: [landscape, sea, beach, sand]
|
||||
- src: gallery/gilley-aguilar-ywGDhTlf93E-unsplash.jpg
|
||||
tags: [landscape, sky, cloud, mountains]
|
||||
- src: gallery/jacob-reinikainen-nGG6m3RbjSk-unsplash.jpg
|
||||
tags: [lanscape, sunset, mountains]
|
||||
- src: gallery/jonas-degener-LueP5EdWGFY-unsplash.jpg
|
||||
tags: [landscape, mountains, fog]
|
||||
- src: gallery/michiel-annaert-M27pZnHV6M0-unsplash.jpg
|
||||
tags: [flowers, nature]
|
||||
- src: gallery/nir-himi-AjecvkfSHxA-unsplash.jpg
|
||||
tags: [landscape, mountains, sky]
|
||||
- src: gallery/rachel-mcdermott-0fN7Fxv1eWA-unsplash.jpg
|
||||
tags: [portrait, black and white]
|
||||
- src: gallery/tianlei-wu-g5o6T-PWT3g-unsplash.jpg
|
||||
tags: [cat, animals]
|
||||
- src: gallery/we-care-wild-zLweeVLU9Fo-unsplash.jpg
|
||||
tags: [bison, animals]
|
||||
- src: gallery/y-s-z90w7yStOkk-unsplash.jpg
|
||||
tags: [frog, green, animals]
|
BIN
demo/config/photos/.DS_Store
vendored
Normal file
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 2.7 MiB |
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 2.7 MiB |
Before Width: | Height: | Size: 2.4 MiB After Width: | Height: | Size: 2.4 MiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 2.2 MiB |
Before Width: | Height: | Size: 5.7 MiB After Width: | Height: | Size: 5.7 MiB |
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 2.7 MiB |
Before Width: | Height: | Size: 364 KiB After Width: | Height: | Size: 364 KiB |
Before Width: | Height: | Size: 5.6 MiB After Width: | Height: | Size: 5.6 MiB |
Before Width: | Height: | Size: 8.4 MiB After Width: | Height: | Size: 8.4 MiB |
Before Width: | Height: | Size: 706 KiB After Width: | Height: | Size: 706 KiB |
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 2.7 MiB |
Before Width: | Height: | Size: 2.4 MiB After Width: | Height: | Size: 2.4 MiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
49
demo/config/site.yaml
Normal file
@ -0,0 +1,49 @@
|
||||
# This file is filled with the demo info
|
||||
# Please change this by your settings
|
||||
|
||||
info:
|
||||
title: Lumeex
|
||||
subtitle: A minimalistic Gallery
|
||||
description: A minimalistic Gallery
|
||||
canonical: https://lumeex.djeex.fr
|
||||
keywords: photography, lumen, demo, gallery, minimalistic
|
||||
author: Djeex
|
||||
google_analytics_id: G-XXXXXXX # optional
|
||||
|
||||
social:
|
||||
instagram_url: https://www.instagram.com/
|
||||
thumbnail: hero/jacob-reinikainen-nGG6m3RbjSk-unsplash.jpg
|
||||
|
||||
menu:
|
||||
items:
|
||||
- label: Home
|
||||
href: /
|
||||
- label: Nature
|
||||
href: /?tag=Nature
|
||||
- label: Landscape
|
||||
href: /?tag=landscape
|
||||
- label: Portrait
|
||||
href: /?tag=portrait
|
||||
- label: Animals
|
||||
href: /?tag=animals
|
||||
|
||||
footer:
|
||||
copyright: Copyright © 2025 – Lumeex
|
||||
legal_link: '/legals/'
|
||||
legal_label: Legal notice
|
||||
|
||||
# Build parameters
|
||||
build:
|
||||
theme: modern # choose a theme in config/theme folder.
|
||||
convert_images: true # use true to automatically convert images to webp small weight images.
|
||||
resize_images: true # use true to automatically resize to width 1140px (maximum width used in the gallery)
|
||||
|
||||
# Change this by your legals
|
||||
legals:
|
||||
hoster_name: Djeex
|
||||
hoster_adress: Paris, France
|
||||
hoster_contact: contact@djeex.fr
|
||||
intellectual_property:
|
||||
- paragraph: "Users of this website are required to comply with the provisions of the French Data Protection Act (Loi Informatique et Libertés), the violation of which may result in criminal penalties. In particular, they must refrain from any collection or misuse of personal data accessible through the site, and more generally, from any act likely to infringe upon the privacy or reputation of individuals."
|
||||
- paragraph: "The overall structure, as well as the software, texts, animated or still images, know-how, and all other components of the site, are the exclusive property of Lumeex"
|
||||
- paragraph: "Any total or partial reproduction of this website, by any means whatsoever, without the express authorization of Lumeex, is prohibited and constitutes an infringement punishable under articles L.335-2 and following of the French Intellectual Property Code. The same applies to the databases appearing on the website, which are protected by the provisions of the law of July 1, 1998, implementing into the Intellectual Property Code the European directive of March 11, 1996, on the legal protection of databases."
|
BIN
demo/config/themes/modern/favicon.png
Normal file
After Width: | Height: | Size: 16 KiB |
63
demo/config/themes/modern/theme.css
Normal file
@ -0,0 +1,63 @@
|
||||
/*-----------------------------------*/
|
||||
/* Modern theme for Lumeex */
|
||||
/* https://git.djeex.fr/Djeex/lumeex */
|
||||
/*-----------------------------------*/
|
||||
|
||||
.hero-background {
|
||||
border-radius: 0 0 15px 15px;
|
||||
}
|
||||
|
||||
img, tag {
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.tag, .scroll-up, .back-button {
|
||||
padding: 5px 10px;
|
||||
background: rgb(245 245 245);
|
||||
border-radius: 30px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
padding: 8px 15px 10px 15px;
|
||||
margin-left: 100px;
|
||||
}
|
||||
|
||||
.tag:hover {
|
||||
background: rgb(231, 231, 231);
|
||||
color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
.tag.active, .scroll-up:hover, .back-button:hover {
|
||||
color: var(--color-background);
|
||||
background: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
#footer {
|
||||
box-shadow: 0px 20px 100px -44px rgba(0, 0, 0, 0.5);
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
#footer {
|
||||
box-shadow: 0px 20px 100px -44px rgba(0, 0, 0, 0.5);
|
||||
border-radius: 15px 15px 0 0;
|
||||
max-width: 1140px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tag {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.hero-background {
|
||||
max-width: 90%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
}
|
32
demo/config/themes/modern/theme.yaml
Normal file
@ -0,0 +1,32 @@
|
||||
#-----------------------------------#
|
||||
# Modern theme for Lumeex #
|
||||
# https://git.djeex.fr/Djeex/lumeex #
|
||||
#-----------------------------------#
|
||||
colors:
|
||||
primary: '#0065a1'
|
||||
primary_dark: '#005384'
|
||||
secondary: '#00b0f0'
|
||||
accent: '#ffc700'
|
||||
text_dark: '#616161'
|
||||
background: '#fff'
|
||||
browser_color: '#fff'
|
||||
favicon:
|
||||
path: favicon.png
|
||||
google_fonts:
|
||||
- family: Lato
|
||||
weights:
|
||||
- '200'
|
||||
- '400'
|
||||
- '700'
|
||||
- family: Montserrat
|
||||
weights:
|
||||
- '200'
|
||||
- '400'
|
||||
- '700'
|
||||
fonts:
|
||||
primary:
|
||||
name: Lato
|
||||
fallback: sans-serif
|
||||
secondary:
|
||||
name: Montserrat
|
||||
fallback: serif
|
BIN
demo/config/themes/typewriter/favicon.png
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
demo/config/themes/typewriter/fonts/trixie.woff
Normal file
BIN
demo/config/themes/typewriter/fonts/trixie.woff2
Normal file
13
demo/config/themes/typewriter/theme.css
Normal file
@ -0,0 +1,13 @@
|
||||
/*-----------------------------------*/
|
||||
/* Typewriter theme for Lumeex */
|
||||
/* https://git.djeex.fr/Djeex/lumeex */
|
||||
/*-----------------------------------*/
|
||||
|
||||
|
||||
.tag {
|
||||
line-height: 0.5em;
|
||||
}
|
||||
|
||||
.tags {
|
||||
gap:0px;
|
||||
}
|
21
demo/config/themes/typewriter/theme.yaml
Normal file
@ -0,0 +1,21 @@
|
||||
#-----------------------------------#
|
||||
# Typewriter theme for Lumeex #
|
||||
# https://git.djeex.fr/Djeex/lumeex #
|
||||
#-----------------------------------#
|
||||
colors:
|
||||
primary: '#0065a1'
|
||||
primary_dark: '#005384'
|
||||
secondary: '#00b0f0'
|
||||
accent: '#ffc700'
|
||||
text_dark: '#616161'
|
||||
background: '#fff'
|
||||
browser_color: '#fff'
|
||||
favicon:
|
||||
path: favicon.png
|
||||
fonts:
|
||||
primary:
|
||||
name: Trixie
|
||||
fallback: sans-serif
|
||||
secondary:
|
||||
name: Trixie
|
||||
fallback: serif
|
84
gallery.py
@ -4,7 +4,6 @@ from pathlib import Path
|
||||
|
||||
# YAML file paths
|
||||
GALLERY_YAML = "config/gallery.yaml"
|
||||
SITE_YAML = "config/site.yaml"
|
||||
|
||||
# Image directories
|
||||
GALLERY_DIR = Path("config/photos/gallery")
|
||||
@ -17,7 +16,7 @@ def load_yaml(path):
|
||||
return {}
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
images = data.get("images") or []
|
||||
images = data.get("images", []) or []
|
||||
print(f"[✓] Loaded {len(images)} image(s) from {path}")
|
||||
return data
|
||||
|
||||
@ -34,47 +33,80 @@ def get_all_image_paths(directory):
|
||||
])
|
||||
|
||||
def update_gallery():
|
||||
print("\n=== Updating gallery.yaml ===")
|
||||
print("\n=== Updating gallery.yaml (gallery section) ===")
|
||||
gallery = load_yaml(GALLERY_YAML)
|
||||
gallery_images = gallery.get("images") or []
|
||||
known = {img["src"] for img in gallery_images}
|
||||
all_images = get_all_image_paths(GALLERY_DIR)
|
||||
|
||||
# Access the 'gallery' section within the gallery data, or initialize it if it doesn't exist
|
||||
gallery_section = gallery.get("gallery", {})
|
||||
|
||||
# Access the 'images' list within the 'gallery' section, or initialize it if it doesn't exist
|
||||
gallery_images = gallery_section.get("images", [])
|
||||
|
||||
all_images = set(get_all_image_paths(GALLERY_DIR))
|
||||
known_images = {img["src"] for img in gallery_images}
|
||||
|
||||
# Add new images
|
||||
new_images = [
|
||||
{"src": path, "tags": []}
|
||||
for path in all_images
|
||||
if path not in known
|
||||
if path not in known_images
|
||||
]
|
||||
|
||||
if new_images:
|
||||
gallery_images.extend(new_images)
|
||||
gallery["images"] = gallery_images
|
||||
save_yaml(gallery, GALLERY_YAML)
|
||||
print(f"[✓] Added {len(new_images)} new image(s) to gallery.yaml")
|
||||
else:
|
||||
print("[✓] No new images to add to gallery.yaml")
|
||||
print(f"[✓] Added {len(new_images)} new image(s) to gallery.yaml (gallery)")
|
||||
|
||||
# Remove deleted images
|
||||
deleted_images = known_images - all_images
|
||||
if deleted_images:
|
||||
gallery_images = [img for img in gallery_images if img["src"] not in deleted_images]
|
||||
print(f"[✓] Removed {len(deleted_images)} deleted image(s) from gallery.yaml (gallery)")
|
||||
|
||||
# Update the 'gallery' section with the modified 'images' list
|
||||
gallery_section["images"] = gallery_images
|
||||
gallery["gallery"] = gallery_section
|
||||
|
||||
save_yaml(gallery, GALLERY_YAML)
|
||||
|
||||
if not new_images and not deleted_images:
|
||||
print("[✓] No changes to gallery.yaml (gallery)")
|
||||
|
||||
def update_hero():
|
||||
print("\n=== Updating site.yaml (hero section) ===")
|
||||
site = load_yaml(SITE_YAML)
|
||||
hero_section = site.get("hero", {})
|
||||
hero_images = hero_section.get("images") or []
|
||||
known = {img["src"] for img in hero_images}
|
||||
all_images = get_all_image_paths(HERO_DIR)
|
||||
print("\n=== Updating gallery.yaml (hero section) ===")
|
||||
gallery = load_yaml(GALLERY_YAML)
|
||||
|
||||
# Access the 'hero' section within the gallery data, or initialize it if it doesn't exist
|
||||
hero_section = gallery.get("hero", {})
|
||||
|
||||
# Access the 'images' list within the 'hero' section, or initialize it if it doesn't exist
|
||||
hero_images = hero_section.get("images", [])
|
||||
|
||||
all_images = set(get_all_image_paths(HERO_DIR))
|
||||
known_images = {img["src"] for img in hero_images}
|
||||
|
||||
# Add new images
|
||||
new_images = [
|
||||
{"src": path}
|
||||
for path in all_images
|
||||
if path not in known
|
||||
if path not in known_images
|
||||
]
|
||||
|
||||
if new_images:
|
||||
hero_images.extend(new_images)
|
||||
site["hero"]["images"] = hero_images
|
||||
save_yaml(site, SITE_YAML)
|
||||
print(f"[✓] Added {len(new_images)} new image(s) to site.yaml (hero)")
|
||||
else:
|
||||
print("[✓] No new images to add to site.yaml")
|
||||
print(f"[✓] Added {len(new_images)} new image(s) to gallery.yaml (hero)")
|
||||
|
||||
# Remove deleted images
|
||||
deleted_images = known_images - all_images
|
||||
if deleted_images:
|
||||
hero_images = [img for img in hero_images if img["src"] not in deleted_images]
|
||||
print(f"[✓] Removed {len(deleted_images)} deleted image(s) from gallery.yaml (hero)")
|
||||
|
||||
# Update the 'hero' section with the modified 'images' list
|
||||
hero_section["images"] = hero_images
|
||||
gallery["hero"] = hero_section
|
||||
|
||||
save_yaml(gallery, GALLERY_YAML)
|
||||
|
||||
if not new_images and not deleted_images:
|
||||
print("[✓] No changes to gallery.yaml (hero)")
|
||||
|
||||
if __name__ == "__main__":
|
||||
update_gallery()
|
||||
|
166
illustration/logo.svg
Normal file
@ -0,0 +1,166 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 6031 1000">
|
||||
<!-- Generator: Adobe Illustrator 29.7.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 8) -->
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: #55c3ec;
|
||||
}
|
||||
|
||||
.st1 {
|
||||
fill: url(#Dégradé_sans_nom_265);
|
||||
stroke: url(#Dégradé_sans_nom_33);
|
||||
}
|
||||
|
||||
.st1, .st2, .st3, .st4, .st5, .st6, .st7, .st8, .st9, .st10, .st11, .st12 {
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
|
||||
.st2 {
|
||||
fill: url(#Dégradé_sans_nom_269);
|
||||
stroke: url(#Dégradé_sans_nom_334);
|
||||
}
|
||||
|
||||
.st3 {
|
||||
fill: url(#Dégradé_sans_nom_268);
|
||||
stroke: url(#Dégradé_sans_nom_333);
|
||||
}
|
||||
|
||||
.st4 {
|
||||
fill: url(#Dégradé_sans_nom_266);
|
||||
stroke: url(#Dégradé_sans_nom_331);
|
||||
}
|
||||
|
||||
.st5 {
|
||||
fill: url(#Dégradé_sans_nom_267);
|
||||
stroke: url(#Dégradé_sans_nom_332);
|
||||
}
|
||||
|
||||
.st13 {
|
||||
fill: url(#Dégradé_sans_nom_261);
|
||||
}
|
||||
|
||||
.st14 {
|
||||
fill: url(#Dégradé_sans_nom_262);
|
||||
}
|
||||
|
||||
.st15 {
|
||||
fill: url(#Dégradé_sans_nom_264);
|
||||
}
|
||||
|
||||
.st16 {
|
||||
fill: url(#Dégradé_sans_nom_263);
|
||||
}
|
||||
|
||||
.st6 {
|
||||
fill: url(#Dégradé_sans_nom_2616);
|
||||
stroke: url(#Dégradé_sans_nom_3311);
|
||||
}
|
||||
|
||||
.st7 {
|
||||
fill: url(#Dégradé_sans_nom_2615);
|
||||
stroke: url(#Dégradé_sans_nom_3310);
|
||||
}
|
||||
|
||||
.st17 {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.st18 {
|
||||
fill: url(#Dégradé_sans_nom_26);
|
||||
}
|
||||
|
||||
.st8 {
|
||||
fill: url(#Dégradé_sans_nom_2610);
|
||||
stroke: url(#Dégradé_sans_nom_335);
|
||||
}
|
||||
|
||||
.st9 {
|
||||
fill: url(#Dégradé_sans_nom_2613);
|
||||
stroke: url(#Dégradé_sans_nom_338);
|
||||
}
|
||||
|
||||
.st10 {
|
||||
fill: url(#Dégradé_sans_nom_2614);
|
||||
stroke: url(#Dégradé_sans_nom_339);
|
||||
}
|
||||
|
||||
.st11 {
|
||||
fill: url(#Dégradé_sans_nom_2611);
|
||||
stroke: url(#Dégradé_sans_nom_336);
|
||||
}
|
||||
|
||||
.st12 {
|
||||
fill: url(#Dégradé_sans_nom_2612);
|
||||
stroke: url(#Dégradé_sans_nom_337);
|
||||
}
|
||||
</style>
|
||||
<linearGradient id="Dégradé_sans_nom_26" data-name="Dégradé sans nom 26" x1="373.2" y1="159.5" x2="625.1" y2="411.5" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#55c3ec"/>
|
||||
<stop offset="1" stop-color="#1d71b8" stop-opacity=".8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="Dégradé_sans_nom_261" data-name="Dégradé sans nom 26" x1="143.1" y1="200.5" x2="395" y2="452.4" gradientTransform="translate(30.8 109.3)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_262" data-name="Dégradé sans nom 26" x1="81.3" y1="60.6" x2="333.2" y2="312.5" gradientTransform="translate(187.1 873.6) rotate(-90)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_263" data-name="Dégradé sans nom 26" x1="-44.4" y1="16.5" x2="207.5" y2="268.4" gradientTransform="translate(705.4 808.2) rotate(-180)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_264" data-name="Dégradé sans nom 26" x1="-67.9" y1="-58.9" x2="184" y2="193" gradientTransform="translate(770.9 385.1) rotate(90)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_265" data-name="Dégradé sans nom 26" x1="74.5" y1="560.2" x2="106.2" y2="591.8" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_33" data-name="Dégradé sans nom 33" x1="74.2" y1="559.9" x2="106.5" y2="592.2" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#55c3ec"/>
|
||||
<stop offset="1" stop-color="#1d71b8" stop-opacity=".5"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="Dégradé_sans_nom_266" data-name="Dégradé sans nom 26" x1="158.2" y1="648.8" x2="176.7" y2="667.3" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_331" data-name="Dégradé sans nom 33" x1="157.8" y1="648.4" x2="177.1" y2="667.7" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_267" data-name="Dégradé sans nom 26" x1="210.2" y1="714.7" x2="249.8" y2="754.3" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_332" data-name="Dégradé sans nom 33" x1="209.9" y1="714.3" x2="250.2" y2="754.6" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_268" data-name="Dégradé sans nom 26" x1="-54" y1="72.2" x2="-14.4" y2="111.8" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_333" data-name="Dégradé sans nom 33" x1="-54.4" y1="71.9" x2="-14.1" y2="112.2" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_269" data-name="Dégradé sans nom 26" x1="485.1" y1="-9.2" x2="503.7" y2="9.4" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_334" data-name="Dégradé sans nom 33" x1="484.8" y1="-9.6" x2="504" y2="9.7" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_2610" data-name="Dégradé sans nom 26" x1="825.1" y1="682.3" x2="856.8" y2="713.9" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_335" data-name="Dégradé sans nom 33" x1="824.8" y1="681.9" x2="857.1" y2="714.3" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_2611" data-name="Dégradé sans nom 26" x1="308.5" y1="356.5" x2="340.3" y2="388.3" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_336" data-name="Dégradé sans nom 33" x1="308.1" y1="356.2" x2="340.7" y2="388.7" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_2612" data-name="Dégradé sans nom 26" x1="540.4" y1="450.4" x2="559" y2="469" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_337" data-name="Dégradé sans nom 33" x1="540.1" y1="450" x2="559.4" y2="469.3" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_2613" data-name="Dégradé sans nom 26" x1="-336.4" y1="326.9" x2="-296.8" y2="366.5" gradientTransform="translate(342.9 490.5) rotate(-175.4)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_338" data-name="Dégradé sans nom 33" x1="-336.7" y1="326.5" x2="-296.4" y2="366.8" gradientTransform="translate(342.9 490.5) rotate(-175.4)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_2614" data-name="Dégradé sans nom 26" x1="115" y1="-29.9" x2="133.6" y2="-11.3" gradientTransform="translate(814.9 151.4) rotate(139.6)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_339" data-name="Dégradé sans nom 33" x1="114.7" y1="-30.2" x2="134" y2="-11" gradientTransform="translate(814.9 151.4) rotate(139.6)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_2615" data-name="Dégradé sans nom 26" x1="94.5" y1="304.2" x2="124.4" y2="334" gradientTransform="translate(568 142) rotate(97.9)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_3310" data-name="Dégradé sans nom 33" x1="94.2" y1="303.8" x2="124.7" y2="334.4" gradientTransform="translate(568 142) rotate(97.9)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_2616" data-name="Dégradé sans nom 26" x1="435.8" y1="254.1" x2="454.4" y2="272.7" gradientTransform="translate(257.1 -349) rotate(52.9)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_3311" data-name="Dégradé sans nom 33" x1="435.5" y1="253.8" x2="454.8" y2="273.1" gradientTransform="translate(257.1 -349) rotate(52.9)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
</defs>
|
||||
<g id="Calque_1">
|
||||
<circle class="st17" cx="499.5" cy="499.5" r="499.5"/>
|
||||
<g>
|
||||
<path class="st17" d="M1404,957.4V45h191v755h399v157.4h-590Z"/>
|
||||
<path class="st17" d="M2321.5,971.3c-49.3,0-91.5-10.2-126.5-30.7-35-20.4-61.6-49.6-79.7-87.6-18.1-37.9-27.2-83.2-27.2-135.9v-437.6h184.6v399c0,44.3,10.4,78.6,31.3,103.1,20.9,24.5,51.9,36.7,93.3,36.7s39.3-3.6,56-10.7c16.6-7.2,30.9-17.4,42.7-30.7,11.8-13.3,20.9-29.1,27.2-47.4s9.5-38.5,9.5-60.4v-389.5h184.6v677.8h-184.6v-111.9h-4.4c-11.4,25.7-26.7,48.1-45.8,67-19.2,19-42.2,33.5-68.9,43.6-26.8,10.1-57.4,15.2-92,15.2Z"/>
|
||||
<path class="st17" d="M2837.5,957.4V279.6h184.6v113.8h3.8c13.9-38.8,37.6-69.8,71.1-93,33.5-23.2,73-34.8,118.6-34.8s60.1,5.5,85.4,16.4c25.3,11,46.7,26.8,64.2,47.4,17.5,20.7,29.8,46,37,75.9h3.8c10.1-28.7,25.4-53.4,45.8-74.3,20.4-20.9,44.7-37,72.7-48.4,28-11.4,58.7-17.1,92-17.1s83,9.5,116.3,28.5c33.3,19,59.2,45.5,77.8,79.7,18.5,34.1,27.8,74.2,27.8,120.1v463.5h-184.6v-416.7c0-26.6-4.3-48.8-13-66.7-8.6-17.9-21.1-31.6-37.3-41.1-16.2-9.5-36.4-14.2-60.4-14.2s-43.5,5.4-61,16.1c-17.5,10.7-31.1,25.6-40.8,44.6-9.7,19-14.5,41.1-14.5,66.4v411.6h-177.7v-422.4c0-24.4-4.4-45.3-13.3-62.6-8.9-17.3-21.4-30.6-37.6-39.8-16.2-9.3-35.7-13.9-58.5-13.9s-43.6,5.6-61.3,16.8c-17.7,11.2-31.5,26.5-41.4,45.8-9.9,19.4-14.9,41.7-14.9,67v409.1h-184.6Z"/>
|
||||
<path class="st0" d="M4264,971.3c-69.1,0-128.6-14.2-178.3-42.7-49.7-28.5-88.1-69-115.1-121.7-27-52.7-40.5-115.1-40.5-187.2v-.6c0-72.1,13.5-134.6,40.5-187.5,27-52.9,64.8-93.8,113.5-122.7,48.7-28.9,106.1-43.3,172.3-43.3s123.4,14,171.7,42c48.3,28,85.6,67.6,111.9,118.6,26.3,51,39.5,110.7,39.5,178.9v57.5h-558.3v-117h471.1l-87.9,109.4v-71.5c0-39.6-6.1-72.8-18.3-99.6-12.2-26.8-29.2-46.9-50.9-60.4-21.7-13.5-46.9-20.2-75.6-20.2s-54.1,7-76.2,20.9c-22.1,13.9-39.4,34.3-51.9,61-12.4,26.8-18.7,59.5-18.7,98.3v72.1c0,37.1,6.2,68.9,18.7,95.5,12.4,26.6,30.2,46.9,53.4,61,23.2,14.1,50.8,21.2,82.8,21.2s47.2-4,65.8-12c18.5-8,33.7-18.1,45.5-30.4,11.8-12.2,19.8-24.7,24-37.3l1.3-3.8h169.5l-1.9,7c-5.1,24.9-15,50-29.7,75.2-14.8,25.3-34.7,48.5-59.8,69.6-25.1,21.1-55.6,37.9-91.7,50.6-36,12.6-78.3,19-126.8,19Z"/>
|
||||
<path class="st0" d="M4982.9,971.3c-69.1,0-128.6-14.2-178.3-42.7-49.7-28.5-88.1-69-115.1-121.7-27-52.7-40.5-115.1-40.5-187.2v-.6c0-72.1,13.5-134.6,40.5-187.5,27-52.9,64.8-93.8,113.5-122.7,48.7-28.9,106.1-43.3,172.3-43.3s123.4,14,171.7,42c48.3,28,85.6,67.6,111.9,118.6,26.3,51,39.5,110.7,39.5,178.9v57.5h-558.3v-117h471.1l-87.9,109.4v-71.5c0-39.6-6.1-72.8-18.3-99.6-12.2-26.8-29.2-46.9-50.9-60.4-21.7-13.5-46.9-20.2-75.6-20.2s-54.1,7-76.2,20.9c-22.1,13.9-39.4,34.3-51.9,61-12.4,26.8-18.7,59.5-18.7,98.3v72.1c0,37.1,6.2,68.9,18.7,95.5,12.4,26.6,30.2,46.9,53.4,61,23.2,14.1,50.8,21.2,82.8,21.2s47.2-4,65.8-12c18.5-8,33.7-18.1,45.5-30.4,11.8-12.2,19.8-24.7,24-37.3l1.3-3.8h169.5l-1.9,7c-5.1,24.9-15,50-29.7,75.2-14.8,25.3-34.7,48.5-59.8,69.6-25.1,21.1-55.6,37.9-91.7,50.6s-78.3,19-126.8,19Z"/>
|
||||
<path class="st0" d="M5337,957.4l212.5-337.7-210.6-340.2h208l122,228.9h3.8l120.1-228.9h201.1l-211.8,335.1,209.9,342.7h-200.4l-129-234.6h-3.8l-127.1,234.6h-194.8Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Calque_2">
|
||||
<g id="Calque_3">
|
||||
<ellipse class="st18" cx="499.2" cy="285.5" rx="139.8" ry="209.5"/>
|
||||
<ellipse class="st13" cx="299.9" cy="435.8" rx="139.8" ry="209.5" transform="translate(-207.3 586.3) rotate(-72)"/>
|
||||
<ellipse class="st14" cx="373.6" cy="666.3" rx="209.5" ry="139.8" transform="translate(-385.1 576.9) rotate(-54)"/>
|
||||
<ellipse class="st16" cx="623.9" cy="665.8" rx="139.8" ry="209.5" transform="translate(-272.2 493.9) rotate(-36)"/>
|
||||
<ellipse class="st15" cx="703.9" cy="443.1" rx="209.5" ry="139.8" transform="translate(-94.9 211.2) rotate(-16)"/>
|
||||
<circle class="st1" cx="90.4" cy="576" r="22.4"/>
|
||||
<circle class="st4" cx="175.6" cy="607.9" r="13.1"/>
|
||||
<circle class="st5" cx="140.8" cy="691.6" r="28"/>
|
||||
<circle class="st3" cx="829.7" cy="602.6" r="28"/>
|
||||
<circle class="st2" cx="908.9" cy="562.1" r="13.1"/>
|
||||
<circle class="st8" cx="840.9" cy="698.1" r="22.4"/>
|
||||
<circle class="st11" cx="466.1" cy="876.5" r="22.5"/>
|
||||
<circle class="st12" cx="538.6" cy="839.8" r="13.1"/>
|
||||
<circle class="st9" cx="686.1" cy="170.1" r="28"/>
|
||||
<circle class="st10" cx="733.7" cy="247.7" r="13.1"/>
|
||||
<circle class="st7" cx="236.9" cy="206.5" r="21.1"/>
|
||||
<circle class="st6" cx="315.4" cy="164.9" r="13.1"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 13 KiB |
@ -486,6 +486,10 @@ h2 {
|
||||
font-size:18px;
|
||||
}
|
||||
|
||||
.section img {
|
||||
margin: 0px 0 60px 0;
|
||||
}
|
||||
|
||||
.tag {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
@ -23,9 +23,10 @@ def render_gallery_images(images):
|
||||
"""
|
||||
return html
|
||||
|
||||
def generate_gallery_json_from_images(images, output_path):
|
||||
def generate_gallery_json_from_images(images, output_dir):
|
||||
try:
|
||||
img_list = [img["src"] for img in images]
|
||||
output_path = output_dir / "data" / "gallery.json"
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
json.dump(img_list, f, indent=2)
|
||||
@ -33,7 +34,7 @@ def generate_gallery_json_from_images(images, output_path):
|
||||
except Exception as e:
|
||||
logging.error(f"[✗] Error generating gallery JSON: {e}")
|
||||
|
||||
def generate_robots_txt(canonical_url, allowed_paths):
|
||||
def generate_robots_txt(canonical_url, allowed_paths, output_dir):
|
||||
robots_lines = ["User-agent: *"]
|
||||
for path in allowed_paths:
|
||||
robots_lines.append(f"Allow: {path}")
|
||||
@ -41,12 +42,12 @@ def generate_robots_txt(canonical_url, allowed_paths):
|
||||
robots_lines.append("")
|
||||
robots_lines.append(f"Sitemap: {canonical_url}/sitemap.xml")
|
||||
content = "\n".join(robots_lines)
|
||||
output_path = Path(".output/robots.txt")
|
||||
output_path = output_dir / "robots.txt"
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
logging.info(f"[✓] robots.txt generated at {output_path}")
|
||||
|
||||
def generate_sitemap_xml(canonical_url, allowed_paths):
|
||||
def generate_sitemap_xml(canonical_url, allowed_paths, output_dir):
|
||||
urlset_start = '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
|
||||
urlset_end = '</urlset>\n'
|
||||
urls = ""
|
||||
@ -54,7 +55,7 @@ def generate_sitemap_xml(canonical_url, allowed_paths):
|
||||
loc = canonical_url.rstrip("/") + path
|
||||
urls += f" <url>\n <loc>{loc}</loc>\n </url>\n"
|
||||
sitemap_content = urlset_start + urls + urlset_end
|
||||
output_path = Path(".output/sitemap.xml")
|
||||
output_path = output_dir / "sitemap.xml"
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write(sitemap_content)
|
||||
logging.info(f"[✓] sitemap.xml generated at {output_path}")
|
||||
|