diff --git a/.gitignore b/.gitignore index ea00f27..8fb0456 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -.venv -.output -__pycache__ \ No newline at end of file +.* +!.gitignore +output/ +__pycache__/ \ No newline at end of file diff --git a/README.MD b/README.MD index 7db8eb5..a2b3887 100644 --- a/README.MD +++ b/README.MD @@ -1,63 +1,73 @@ -

Lumeex

-
- Lumeex Screenshot +
+ Lumeex Screenshot +
+

+

+

Yet another minimalist, lightweight photo gallery static site generator.

+
+

+
+ Lumeex Screenshot
-**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 \ No newline at end of file diff --git a/build.py b/build.py index 96fca6c..9950425 100644 --- a/build.py +++ b/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" @@ -31,7 +31,7 @@ def build(): 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) @@ -58,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) @@ -73,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'' @@ -122,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"" + signature = f"" body = f"""
@@ -163,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.") @@ -172,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.") diff --git a/config/gallery.yaml b/config/gallery.yaml index 1ed4308..6826862 100644 --- a/config/gallery.yaml +++ b/config/gallery.yaml @@ -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: [] \ No newline at end of file diff --git a/config/site.yaml b/config/site.yaml index a0d5494..b3211f4 100644 --- a/config/site.yaml +++ b/config/site.yaml @@ -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: "" diff --git a/config/themes/modern/favicon.png b/config/themes/modern/favicon.png index 16299b3..da3ea15 100644 Binary files a/config/themes/modern/favicon.png and b/config/themes/modern/favicon.png differ diff --git a/demo/config/gallery.yaml b/demo/config/gallery.yaml new file mode 100644 index 0000000..329528b --- /dev/null +++ b/demo/config/gallery.yaml @@ -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] diff --git a/demo/config/photos/.DS_Store b/demo/config/photos/.DS_Store new file mode 100644 index 0000000..5cf1141 Binary files /dev/null and b/demo/config/photos/.DS_Store differ diff --git a/config/photos/gallery/almos-bechtold-3402kvtHhOo-unsplash.jpg b/demo/config/photos/gallery/almos-bechtold-3402kvtHhOo-unsplash.jpg similarity index 100% rename from config/photos/gallery/almos-bechtold-3402kvtHhOo-unsplash.jpg rename to demo/config/photos/gallery/almos-bechtold-3402kvtHhOo-unsplash.jpg diff --git a/config/photos/gallery/arthur-savary-nLfAqmZ2hJo-unsplash.jpg b/demo/config/photos/gallery/arthur-savary-nLfAqmZ2hJo-unsplash.jpg similarity index 100% rename from config/photos/gallery/arthur-savary-nLfAqmZ2hJo-unsplash.jpg rename to demo/config/photos/gallery/arthur-savary-nLfAqmZ2hJo-unsplash.jpg diff --git a/config/photos/gallery/francesco-ungaro-Zbc9Ka8msdI-unsplash.jpg b/demo/config/photos/gallery/francesco-ungaro-Zbc9Ka8msdI-unsplash.jpg similarity index 100% rename from config/photos/gallery/francesco-ungaro-Zbc9Ka8msdI-unsplash.jpg rename to demo/config/photos/gallery/francesco-ungaro-Zbc9Ka8msdI-unsplash.jpg diff --git a/config/photos/gallery/gilley-aguilar-ywGDhTlf93E-unsplash.jpg b/demo/config/photos/gallery/gilley-aguilar-ywGDhTlf93E-unsplash.jpg similarity index 100% rename from config/photos/gallery/gilley-aguilar-ywGDhTlf93E-unsplash.jpg rename to demo/config/photos/gallery/gilley-aguilar-ywGDhTlf93E-unsplash.jpg diff --git a/config/photos/gallery/jacob-reinikainen-nGG6m3RbjSk-unsplash.jpg b/demo/config/photos/gallery/jacob-reinikainen-nGG6m3RbjSk-unsplash.jpg similarity index 100% rename from config/photos/gallery/jacob-reinikainen-nGG6m3RbjSk-unsplash.jpg rename to demo/config/photos/gallery/jacob-reinikainen-nGG6m3RbjSk-unsplash.jpg diff --git a/config/photos/gallery/jonas-degener-LueP5EdWGFY-unsplash.jpg b/demo/config/photos/gallery/jonas-degener-LueP5EdWGFY-unsplash.jpg similarity index 100% rename from config/photos/gallery/jonas-degener-LueP5EdWGFY-unsplash.jpg rename to demo/config/photos/gallery/jonas-degener-LueP5EdWGFY-unsplash.jpg diff --git a/config/photos/gallery/michiel-annaert-M27pZnHV6M0-unsplash.jpg b/demo/config/photos/gallery/michiel-annaert-M27pZnHV6M0-unsplash.jpg similarity index 100% rename from config/photos/gallery/michiel-annaert-M27pZnHV6M0-unsplash.jpg rename to demo/config/photos/gallery/michiel-annaert-M27pZnHV6M0-unsplash.jpg diff --git a/config/photos/gallery/nir-himi-AjecvkfSHxA-unsplash.jpg b/demo/config/photos/gallery/nir-himi-AjecvkfSHxA-unsplash.jpg similarity index 100% rename from config/photos/gallery/nir-himi-AjecvkfSHxA-unsplash.jpg rename to demo/config/photos/gallery/nir-himi-AjecvkfSHxA-unsplash.jpg diff --git a/config/photos/gallery/rachel-mcdermott-0fN7Fxv1eWA-unsplash.jpg b/demo/config/photos/gallery/rachel-mcdermott-0fN7Fxv1eWA-unsplash.jpg similarity index 100% rename from config/photos/gallery/rachel-mcdermott-0fN7Fxv1eWA-unsplash.jpg rename to demo/config/photos/gallery/rachel-mcdermott-0fN7Fxv1eWA-unsplash.jpg diff --git a/config/photos/gallery/tianlei-wu-g5o6T-PWT3g-unsplash.jpg b/demo/config/photos/gallery/tianlei-wu-g5o6T-PWT3g-unsplash.jpg similarity index 100% rename from config/photos/gallery/tianlei-wu-g5o6T-PWT3g-unsplash.jpg rename to demo/config/photos/gallery/tianlei-wu-g5o6T-PWT3g-unsplash.jpg diff --git a/config/photos/gallery/we-care-wild-zLweeVLU9Fo-unsplash.jpg b/demo/config/photos/gallery/we-care-wild-zLweeVLU9Fo-unsplash.jpg similarity index 100% rename from config/photos/gallery/we-care-wild-zLweeVLU9Fo-unsplash.jpg rename to demo/config/photos/gallery/we-care-wild-zLweeVLU9Fo-unsplash.jpg diff --git a/config/photos/gallery/y-s-z90w7yStOkk-unsplash.jpg b/demo/config/photos/gallery/y-s-z90w7yStOkk-unsplash.jpg similarity index 100% rename from config/photos/gallery/y-s-z90w7yStOkk-unsplash.jpg rename to demo/config/photos/gallery/y-s-z90w7yStOkk-unsplash.jpg diff --git a/config/photos/hero/francesco-ungaro-Zbc9Ka8msdI-unsplash.jpg b/demo/config/photos/hero/francesco-ungaro-Zbc9Ka8msdI-unsplash.jpg similarity index 100% rename from config/photos/hero/francesco-ungaro-Zbc9Ka8msdI-unsplash.jpg rename to demo/config/photos/hero/francesco-ungaro-Zbc9Ka8msdI-unsplash.jpg diff --git a/config/photos/hero/gilley-aguilar-ywGDhTlf93E-unsplash.jpg b/demo/config/photos/hero/gilley-aguilar-ywGDhTlf93E-unsplash.jpg similarity index 100% rename from config/photos/hero/gilley-aguilar-ywGDhTlf93E-unsplash.jpg rename to demo/config/photos/hero/gilley-aguilar-ywGDhTlf93E-unsplash.jpg diff --git a/config/photos/hero/jacob-reinikainen-nGG6m3RbjSk-unsplash.jpg b/demo/config/photos/hero/jacob-reinikainen-nGG6m3RbjSk-unsplash.jpg similarity index 100% rename from config/photos/hero/jacob-reinikainen-nGG6m3RbjSk-unsplash.jpg rename to demo/config/photos/hero/jacob-reinikainen-nGG6m3RbjSk-unsplash.jpg diff --git a/demo/config/site.yaml b/demo/config/site.yaml new file mode 100644 index 0000000..aaaf312 --- /dev/null +++ b/demo/config/site.yaml @@ -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." diff --git a/demo/config/themes/modern/favicon.png b/demo/config/themes/modern/favicon.png new file mode 100644 index 0000000..da3ea15 Binary files /dev/null and b/demo/config/themes/modern/favicon.png differ diff --git a/demo/config/themes/modern/theme.css b/demo/config/themes/modern/theme.css new file mode 100644 index 0000000..84c8a73 --- /dev/null +++ b/demo/config/themes/modern/theme.css @@ -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; +} + +} \ No newline at end of file diff --git a/demo/config/themes/modern/theme.yaml b/demo/config/themes/modern/theme.yaml new file mode 100644 index 0000000..3f331f5 --- /dev/null +++ b/demo/config/themes/modern/theme.yaml @@ -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 \ No newline at end of file diff --git a/demo/config/themes/typewriter/favicon.png b/demo/config/themes/typewriter/favicon.png new file mode 100644 index 0000000..16299b3 Binary files /dev/null and b/demo/config/themes/typewriter/favicon.png differ diff --git a/demo/config/themes/typewriter/fonts/trixie.woff b/demo/config/themes/typewriter/fonts/trixie.woff new file mode 100644 index 0000000..d070991 Binary files /dev/null and b/demo/config/themes/typewriter/fonts/trixie.woff differ diff --git a/demo/config/themes/typewriter/fonts/trixie.woff2 b/demo/config/themes/typewriter/fonts/trixie.woff2 new file mode 100644 index 0000000..7a0213c Binary files /dev/null and b/demo/config/themes/typewriter/fonts/trixie.woff2 differ diff --git a/demo/config/themes/typewriter/theme.css b/demo/config/themes/typewriter/theme.css new file mode 100644 index 0000000..8fa3637 --- /dev/null +++ b/demo/config/themes/typewriter/theme.css @@ -0,0 +1,13 @@ +/*-----------------------------------*/ +/* Typewriter theme for Lumeex */ +/* https://git.djeex.fr/Djeex/lumeex */ +/*-----------------------------------*/ + + +.tag { + line-height: 0.5em; +} + +.tags { + gap:0px; +} \ No newline at end of file diff --git a/demo/config/themes/typewriter/theme.yaml b/demo/config/themes/typewriter/theme.yaml new file mode 100644 index 0000000..7a30379 --- /dev/null +++ b/demo/config/themes/typewriter/theme.yaml @@ -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 \ No newline at end of file diff --git a/gallery.py b/gallery.py index aecc3a8..54d815d 100644 --- a/gallery.py +++ b/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() diff --git a/illustration/logo.svg b/illustration/logo.svg new file mode 100644 index 0000000..2425d77 --- /dev/null +++ b/illustration/logo.svg @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/py/html_generator.py b/src/py/html_generator.py index df13ba4..4eca005 100644 --- a/src/py/html_generator.py +++ b/src/py/html_generator.py @@ -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 = '\n\n' urlset_end = '\n' urls = "" @@ -54,7 +55,7 @@ def generate_sitemap_xml(canonical_url, allowed_paths): loc = canonical_url.rstrip("/") + path urls += f" \n {loc}\n \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}")