1st commit

This commit is contained in:
2025-08-06 11:16:18 +00:00
commit b9e3467c01
49 changed files with 2345 additions and 0 deletions

45
src/public/js/lazy.js Normal file
View File

@ -0,0 +1,45 @@
// js for Lumeex
// https://git.djeex.fr/Djeex/lumeex
window.addEventListener("DOMContentLoaded", () => {
// Lazy loading
const lazyImages = document.querySelectorAll('img.lazyload');
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
console.log("Lazy-loading image:", img.dataset.src);
img.src = img.dataset.src;
img.onload = () => {
img.classList.add("loaded");
};
obs.unobserve(img);
}
});
}, {
rootMargin: "0px 0px 300px 0px",
threshold: 0.01
});
lazyImages.forEach(img => observer.observe(img));
// Fade-in effect for loaded images (even outside lazy ones)
const fadeImages = document.querySelectorAll("img.fade-in-img");
fadeImages.forEach(img => {
const onLoad = () => {
console.log("Image loaded (fade-in):", img.src);
img.classList.add("loaded");
};
if (img.complete && img.naturalHeight !== 0) {
onLoad(); // already loaded
} else {
img.addEventListener("load", onLoad, { once: true });
img.addEventListener("error", () => {
console.warn("Image failed to load:", img.dataset.src || img.src);
});
}
});
});

152
src/public/js/lumeex.js Normal file
View File

@ -0,0 +1,152 @@
// js for Lumeex
// https://git.djeex.fr/Djeex/lumeex
// Fade in effect for elements with class 'appear'
const setupIntersectionObserver = () => {
const items = document.querySelectorAll('.appear');
const io = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
entry.target.classList.toggle('inview', entry.isIntersecting);
});
});
items.forEach((item) => io.observe(item));
};
// Loader fade out after page load
const setupLoader = () => {
window.addEventListener('load', () => {
setTimeout(() => {
const loader = document.querySelector('.page-loader');
if (loader) {
loader.classList.add('hidden');
}
}, 50);
});
};
// Gallery randomizer to shuffle gallery sections on page load
const shuffleGallery = () => {
const gallery = document.querySelector('.gallery');
if (!gallery) return;
const sections = Array.from(gallery.querySelectorAll('.section'));
while (sections.length) {
const randomIndex = Math.floor(Math.random() * sections.length);
gallery.appendChild(sections.splice(randomIndex, 1)[0]);
}
};
// Hero background randomizer
const randomizeHeroBackground = () => {
const heroBg = document.querySelector(".hero-background");
if (!heroBg) return;
fetch("/data/gallery.json")
.then((res) => res.json())
.then((images) => {
if (images.length === 0) return;
let currentIndex = Math.floor(Math.random() * images.length);
heroBg.style.backgroundImage = `url(/img/${images[currentIndex]})`;
setInterval(() => {
let nextIndex;
do {
nextIndex = Math.floor(Math.random() * images.length);
} while (nextIndex === currentIndex);
const nextImage = images[nextIndex];
heroBg.style.setProperty("--next-image", `url(/img/${nextImage})`);
heroBg.classList.add("fade-in");
const onTransitionEnd = () => {
heroBg.style.backgroundImage = `url(/img/${nextImage})`;
heroBg.classList.remove("fade-in");
heroBg.removeEventListener("transitionend", onTransitionEnd);
};
heroBg.addEventListener("transitionend", onTransitionEnd);
currentIndex = nextIndex;
}, 7000);
})
.catch(console.error);
};
// Tags filter functionality
const setupTagFilter = () => {
const allSections = document.querySelectorAll('.section[data-tags]');
const allTags = document.querySelectorAll('.tag');
let activeTags = [];
const applyFilter = () => {
allSections.forEach((section) => {
const sectionTags = section.dataset.tags.toLowerCase().split(/\s+/);
const hasAllTags = activeTags.every((tag) => sectionTags.includes(tag));
section.style.display = hasAllTags ? '' : 'none';
});
allTags.forEach((tagEl) => {
const tagText = tagEl.textContent.replace('#', '').toLowerCase();
tagEl.classList.toggle('active', activeTags.includes(tagText));
});
const base = window.location.pathname;
const query = activeTags.length > 0 ? `?tag=${activeTags.join(',')}` : '';
window.history.pushState({}, '', base + query);
};
allTags.forEach((tagEl) => {
tagEl.addEventListener('click', () => {
const tagText = tagEl.textContent.replace('#', '').toLowerCase();
activeTags = activeTags.includes(tagText)
? activeTags.filter((t) => t !== tagText)
: [...activeTags, tagText];
applyFilter();
});
});
window.addEventListener('DOMContentLoaded', () => {
const params = new URLSearchParams(window.location.search);
const urlTags = params.get('tag');
if (urlTags) {
activeTags = urlTags.split(',').map((t) => t.toLowerCase());
applyFilter();
}
});
};
// Disable right-click context menu and image dragging
const disableRightClickAndDrag = () => {
document.addEventListener("contextmenu", (e) => e.preventDefault());
document.addEventListener("dragstart", (e) => {
if (e.target.tagName === "IMG") {
e.preventDefault();
}
});
};
// Scroll-to-top button functionality
const setupScrollToTopButton = () => {
const scrollBtn = document.getElementById("scrollToTop");
window.addEventListener("scroll", () => {
scrollBtn.style.display = window.scrollY > 300 ? "block" : "none";
});
scrollBtn.addEventListener("click", () => {
window.scrollTo({ top: 0, behavior: "smooth" });
});
};
// Adjust navigation list items
const fixNavSeparators = () => {
const items = document.querySelectorAll('.nav-list li');
let prevTop = null;
items.forEach((item) => {
const top = item.getBoundingClientRect().top;
item.classList.toggle('first-on-line', prevTop !== null && top !== prevTop);
prevTop = top;
});
};
// Initialize all functions
document.addEventListener("DOMContentLoaded", () => {
setupIntersectionObserver();
setupLoader();
shuffleGallery();
randomizeHeroBackground();
setupTagFilter();
disableRightClickAndDrag();
setupScrollToTopButton();
fixNavSeparators();
});
window.addEventListener('resize', fixNavSeparators);

BIN
src/public/style/.DS_Store vendored Normal file

Binary file not shown.

496
src/public/style/style.css Normal file
View File

@ -0,0 +1,496 @@
/*-----------------------------------*/
/* CSS style for Lumeex */
/* https://git.djeex.fr/Djeex/lumeex */
/*-----------------------------------*/
:root {
--color-primary: #0065a1;
--color-primary-dark: #005384;
--color-secondary: #00b0f0;
--color-accent: #ffc700;
--color-text-dark: #333333;
--color-background: #ffffff;
}
/* Custom scroll bar */
/* width */
::-webkit-scrollbar {
width: 3px;
}
/* Track */
::-webkit-scrollbar-track {
border-radius: 10px;
background-color:var(--color-background)
}
/* Handle */
::-webkit-scrollbar-thumb {
background-color: var(--color-primary);
border-radius: 10px;
}
/* Scroll to top */
.scroll-up {
position: fixed;
bottom: 20px;
right: 20px;
background: none;
color: var(--color-primary-dark);
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
font-size: 20px;
cursor: pointer;
z-index: 1000;
display: none;
transition: opacity 0.3s ease;
}
.scroll-up:hover, .back-button:hover {
color:var(--color-secondary);
}
/* back button */
.back-button {
padding: 20px;
text-decoration: none;
color: var(--color-primary-dark);
font-size: 1.1rem;
font-family: arial;
border-radius: 8px;
transition: background 0.2s ease;
}
/* Body structure */
html,body {
height: 100%;
margin: 0px;
padding: 0px;
font-family: var(--font-secondary), Helvetica, sans-serif;
min-width:320px;
font-weight: 400;
line-height:1.5;
color:var(--color-primary-dark);
}
html {
scroll-behavior: smooth;
}
@media screen and (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
body a, body a:hover {
transition: all 0.25s ease-out;
text-decoration:none;
}
.inner {
max-width:1200px;
margin-left: auto;
margin-right: auto;
}
h2 {
font-family: var(--font-primary), Arial, sans-serif;
font-size: 18px;
text-align: left;
}
/* Loader */
.page-loader {
width: 100%;
height: 100vh;
position: fixed;
background: var(--color-background);
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
opacity: 1;
transition: opacity 1s ease-out;
}
/* Hide the loader with a fade-out effect */
.page-loader.hidden {
opacity: 0;
pointer-events: none;
}
/* Spinner */
.spinner {
width: 80px;
height: 80px;
background-color: var(--color-primary);
border-radius: 100%;
animation: sk-scaleout 1.0s infinite ease-in-out;
}
@keyframes sk-scaleout {
0% {
transform: scale(0);
}
100% {
transform: scale(1.0);
opacity: 0;
}
}
/* navigation */
.nav-item {
display: inline;
}
.nav-list {
list-style-type: disc;
margin: 0px;
padding: 0px;
}
.nav-list li {
white-space: nowrap;
}
.nav-list > li + li::before {
content: " • ";
color: var(--color-accent);
margin: -5px;
}
.nav-list li.first-on-line::before {
visibility: hidden;
}
/* animation */
.appear {
-webkit-transition: all 0.3s;
transition: all 0.3s;
opacity: 0;
-webkit-transform: translateY(20px);
transform: translateY(20px);
}
.appear.inview {
opacity: 1;
-webkit-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 */
.fade-in-img {
opacity: 0;
transform: scale(1.02);
transition: opacity 1.2s ease-out, transform 1.2s ease-out;
will-change: opacity, transform;
}
.fade-in-img.loaded {
opacity: 1;
transform: scale(1);
}
/* tag */
.tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 10px;
}
.tag {
padding: 5px 5px;
cursor: pointer;
font-size: 18px;
transition: background 0.2s ease;
}
.tag:hover,
.tag.active {
color: var(--color-secondary);
}
/* Content */
/* wrapper */
.content-wrapper {
margin: 0 100px;
}
/* Hero */
#hero {
height: 100%;
width: 100%;
}
#hero .content-wrapper, #hero .section {
height:100%;
}
.hero-background {
height: 66%;
width: 100%;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
position: relative;
display: flex;
align-items: center;
flex-direction: column-reverse;
z-index: 0;
overflow: hidden;
}
.hero-background::after {
content: "";
position: absolute;
inset: 0;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
opacity: 0;
transition: opacity 1.5s ease-in-out;
z-index: 1; /* derrière le contenu */
pointer-events: none;
background-image: var(--next-image);
}
.hero-background.fade-in::after {
opacity: 1;
background-image: var(--next-image);
}
.hero-menu {
font-size: 18px;
}
.hero-title {
text-align: center;
margin-bottom: 40px;
color: var(--color-background);
margin-right: auto;
margin-left: auto;
display: flex;
position: relative;
z-index: 2;
}
.hero-title h1 {
font-size: 38px;
margin: 0;
}
.hero-title p {
margin: 0;
font-style: italic;
font-size: 22px;
}
/* Sections */
.section {
max-width: 1140px;
margin:auto;
}
.section img {
width:100%;
margin: 0 0 60px 0;
}
.text-block {
padding:10px;
margin:10px;
}
.text-block p {
font-size: 18px;
font-family: var(--font-secondary), Helvetica, sans-serif;
font-weight: 200;
}
/* Buttons */
/* Text */
.text-inner {
margin-left: 10%;
margin-right:10%;
}
/* Footer */
.navigation {
text-align: center;
padding-top: 40px;
}
.navigation-title {
font-family: var(--font-primary), Arial, sans-serif;
font-weight: bold;
font-size: 32px;
color:var(--color-text-dark);
margin-top: 0px;
margin-bottom: 20px;
}
.navigation a {
color:var(--color-primary);
}
.navigation a:hover {
color:var(--color-secondary);
}
.navigation-subtitle {
color:var(--color-text-dark);
font-size: 12px;
}
.navigation-bottom-link {
color:var(--color-accent);
font-size: 12px;
}
.social {
display: inline;
text-align: center;
padding: 0;
}
.social-item {
display: inline;
text-align: center;
font-size: 25px;
padding: 10px;
}
.navigation .nav-links {
margin: 20px 0;
font-family: var(--font-secondary), Helvetica, sans-serif;
font-weight: 200;
}
.nav-list > li + li::before {
margin: 3px;
}
.bottom-link {
padding-bottom: 40px;
}
.bottom-link p {
margin-left:10px;
margin-right:10px;
}
/* legals */
#legals.content-wrapper {
max-width: 1140px;
margin-top: 100px;
margin-bottom: 100px;
margin-left: auto;
margin-right: auto;
}
.legals-content h2 {
font-size: 22px;
}
.legals-content {
margin: 0 100px;
}
/* responsive */
@media (max-width: 1000px) {
.button {
font-size: 14px;
}
}
@media (max-width: 768px) {
.content-wrapper {
margin: 0;
}
.content-wrapper.gallery {
margin: 0 5%;
}
.navigation .nav-links {
margin: 20px;
}
.nav-links > ul {
padding: 0;
list-style-type: none;
font-size: 20px;
top: 15%;
position: relative;
}
h2 {
font-size:18px;
}
#legals.content-wrapper {
max-width: 90%;
margin: auto;
margin-top: 50px;
}
.legals-content {
margin: 0;
}
.back-button {
padding-left: 0;
margin-top: 60px;
}
}

0
src/py/__init__.py Normal file
View File

71
src/py/css_generator.py Normal file
View File

@ -0,0 +1,71 @@
import logging
from pathlib import Path
from shutil import copyfile
def generate_css_variables(colors_dict, output_path):
css_lines = [":root {"]
for key, value in colors_dict.items():
css_lines.append(f" --color-{key.replace('_', '-')}: {value};")
css_lines.append("}")
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, "w", encoding="utf-8") as f:
f.write("\n".join(css_lines))
logging.info(f"[✓] CSS variables written to {output_path}")
def generate_fonts_css(fonts_dir, output_path, fonts_cfg=None):
font_files = list(fonts_dir.glob("*"))
font_faces = {}
preload_links = []
format_map = {".woff2": "woff2", ".woff": "woff", ".ttf": "truetype", ".otf": "opentype"}
for font_file in font_files:
name = font_file.stem
ext = font_file.suffix.lower()
if ext not in format_map:
continue
font_faces.setdefault(name, []).append((font_file.name, format_map[ext]))
dest_font_path = output_path.parent.parent / "fonts" / font_file.name
dest_font_path.parent.mkdir(parents=True, exist_ok=True)
copyfile(font_file, dest_font_path)
preload_links.append(
f'<link rel="preload" href="fonts/{font_file.name}" as="font" type="font/{format_map[ext]}" crossorigin>'
)
css_lines = []
for font_name, sources in font_faces.items():
css_lines.append(f"@font-face {{")
css_lines.append(f" font-family: '{font_name}';")
srcs = [f"url('../fonts/{file}') format('{fmt}')" for file, fmt in sorted(sources)]
css_lines.append(f" src: {', '.join(srcs)};")
css_lines.append(" font-weight: normal;")
css_lines.append(" font-style: normal;")
css_lines.append("}")
if fonts_cfg:
css_lines.append(":root {")
if "primary" in fonts_cfg:
p = fonts_cfg["primary"]
css_lines.append(f" --font-primary: '{p['name']}', {p['fallback']};")
if "secondary" in fonts_cfg:
s = fonts_cfg["secondary"]
css_lines.append(f" --font-secondary: '{s['name']}', {s['fallback']};")
css_lines.append("}")
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text("\n\n".join(css_lines), encoding="utf-8")
logging.info(f"[✓] Generated fonts CSS: {output_path}")
return preload_links
def generate_google_fonts_link(fonts):
if not fonts:
return ""
families = []
for font in fonts:
family = font["family"].replace(" ", "+")
weights = font.get("weights", [])
if weights:
families.append(f"{family}:wght@{';'.join(weights)}")
else:
families.append(family)
href = "https://fonts.googleapis.com/css2?" + "&".join(f"family={f}" for f in families) + "&display=swap"
return f'<link href="{href}" rel="stylesheet">'

60
src/py/html_generator.py Normal file
View File

@ -0,0 +1,60 @@
import json
import logging
from pathlib import Path
def render_template(template_path, context):
with open(template_path, encoding="utf-8") as f:
content = f.read()
for key, value in context.items():
placeholder = "{{ " + key + " }}"
content = content.replace(placeholder, str(value) if value is not None else "")
return content
def render_gallery_images(images):
html = ""
for img in images:
tags = " ".join(img.get("tags", []))
tag_html = "".join(f'<span class="tag">#{t}</span>' for t in img.get("tags", []))
html += f"""
<div class="section" data-tags="{tags}">
<div class="tags">{tag_html}</div>
<img class="fade-in-img lazyload" data-src="/img/{img['src']}" alt="{img.get('alt', '')}" loading="lazy">
</div>
"""
return html
def generate_gallery_json_from_images(images, output_path):
try:
img_list = [img["src"] for img in images]
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)
logging.info(f"[✓] Generated hero gallery JSON: {output_path}")
except Exception as e:
logging.error(f"[✗] Error generating gallery JSON: {e}")
def generate_robots_txt(canonical_url, allowed_paths):
robots_lines = ["User-agent: *"]
for path in allowed_paths:
robots_lines.append(f"Allow: {path}")
robots_lines.append("Disallow: /")
robots_lines.append("")
robots_lines.append(f"Sitemap: {canonical_url}/sitemap.xml")
content = "\n".join(robots_lines)
output_path = Path(".output/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):
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 = ""
for path in 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")
with open(output_path, "w", encoding="utf-8") as f:
f.write(sitemap_content)
logging.info(f"[✓] sitemap.xml generated at {output_path}")

73
src/py/image_processor.py Normal file
View File

@ -0,0 +1,73 @@
import logging
from pathlib import Path
from PIL import Image
from shutil import copyfile
def convert_and_resize_image(input_path, output_path, resize=True, max_width=1140):
try:
img = Image.open(input_path)
if img.mode != "RGB":
img = img.convert("RGB")
if resize:
width, height = img.size
if width > max_width:
new_height = int((max_width / width) * height)
img = img.resize((max_width, new_height), Image.LANCZOS)
output_path.parent.mkdir(parents=True, exist_ok=True)
img.save(output_path, "WEBP", quality=100)
logging.info(f"[✓] Processed: {input_path}{output_path}")
except Exception as e:
logging.error(f"[✗] Failed to process {input_path}: {e}")
def process_images(images, resize_images, img_dir, build_dir):
for img in images:
src_path = img_dir / img["src"]
webp_path = build_dir / "img" / Path(img["src"]).with_suffix(".webp")
convert_and_resize_image(src_path, webp_path, resize=resize_images)
img["src"] = str(Path(img["src"]).with_suffix(".webp"))
def copy_original_images(images, img_dir, build_dir):
for img in images:
src_path = img_dir / img["src"]
dest_path = build_dir / "img" / img["src"]
try:
dest_path.parent.mkdir(parents=True, exist_ok=True)
copyfile(src_path, dest_path)
logging.info(f"[✓] Copied original: {src_path}{dest_path}")
except Exception as e:
logging.error(f"[✗] Failed to copy {src_path}: {e}")
def generate_favicons_from_logo(theme_vars, theme_dir, output_dir):
logo_path = get_favicon_path(theme_vars, theme_dir)
if not logo_path:
logging.warning("[~] No favicon path defined, skipping favicon PNGs.")
return
output_dir.mkdir(parents=True, exist_ok=True)
specs = [(32, "favicon-32.png"), (96, "favicon-96.png"), (128, "favicon-128.png"),
(192, "favicon-192.png"), (196, "favicon-196.png"), (152, "favicon-152.png"), (180, "favicon-180.png")]
img = Image.open(logo_path).convert("RGBA")
for size, name in specs:
img.resize((size, size), Image.LANCZOS).save(output_dir / name, format="PNG")
logging.info(f"[✓] Favicons generated in {output_dir}")
def generate_favicon_ico(theme_vars, theme_dir, output_path):
logo_path = get_favicon_path(theme_vars, theme_dir)
if not logo_path:
logging.warning("[~] No favicon path defined, skipping .ico generation.")
return
try:
img = Image.open(logo_path).convert("RGBA")
output_path.parent.mkdir(parents=True, exist_ok=True)
img.save(output_path, format="ICO", sizes=[(16, 16), (32, 32), (48, 48)])
logging.info(f"[✓] favicon.ico generated at {output_path}")
except Exception as e:
logging.error(f"[✗] Failed to generate favicon.ico: {e}")
def get_favicon_path(theme_vars, theme_dir):
fav_path = theme_vars.get("favicon", {}).get("path")
if not fav_path:
return None
path = Path(fav_path)
if not path.is_absolute():
path = theme_dir / path
return path if path.exists() else None

34
src/py/utils.py Normal file
View File

@ -0,0 +1,34 @@
import yaml
import logging
from pathlib import Path
from shutil import copytree, rmtree, copyfile
def load_yaml(path):
if not path.exists():
logging.warning(f"[!] YAML file not found: {path}")
return {}
with open(path, "r", encoding="utf-8") as f:
return yaml.safe_load(f)
def load_theme_config(theme_name, themes_dir):
theme_dir = themes_dir / theme_name
theme_config_path = theme_dir / "theme.yaml"
if not theme_config_path.exists():
raise FileNotFoundError(f"[✗] Theme config not found: {theme_config_path}")
with open(theme_config_path, "r", encoding="utf-8") as f:
theme_vars = yaml.safe_load(f)
return theme_vars, theme_dir
def ensure_dir(path):
if path.exists():
rmtree(path)
path.mkdir(parents=True)
def copy_assets(js_dir, style_dir, build_dir):
for folder in [js_dir, style_dir]:
if folder.exists():
dest = build_dir / folder.name
copytree(folder, dest)
logging.info(f"[✓] Copied assets from {folder.name}")
else:
logging.warning(f"[~] Skipped missing folder: {folder.name}")

20
src/templates/footer.html Normal file
View File

@ -0,0 +1,20 @@
<!-- Footer -->
<div id="footer" class="content-wrapper navigation appear">
<div class="footer-content">
<ul class="social">
<li class="social-item appear">
<a href="{{ instagram_url }}"><i class="fa-brands fa-instagram"></i></a>
</ul>
<div class="nav-links">
<ul class="nav-list">
{{ menu_items }}
</ul>
</div>
<div class="inner bottom-link appear">
<p class="navigation-subtitle appear">{{ copyright }}</p>
<p class="navigation-bottom-link appear"><a href="{{ legal_link }}">{{ legal_label }}</a></p>
</div>
</div>
</div>
<button id="scrollToTop" class="scroll-up" aria-label="up"></button>
<script type="text/javascript" src="/js/lazy.js?{{ build_date }}" defer></script>

View File

@ -0,0 +1,4 @@
<!-- Gallery -->
<div id="gallery" class="gallery content-wrapper">
{{ gallery_images }}
</div>

44
src/templates/head.html Normal file
View File

@ -0,0 +1,44 @@
<head>
<!-- Meta -->
<title>{{ title }}</title>
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<link rel="icon" type="image/png" href="/img/favicon/favicon-32.png" sizes="32x32">
<link rel="icon" type="image/png" href="/img/favicon/favicon-96.png" sizes="96x96">
<link rel="icon" type="image/png" href="/img/favicon/favicon-128.png" sizes="128x128">
<link rel="icon" type="image/png" href="/img/favicon/favicon-192.png" sizes="192x192">
<!-- Android -->
<link rel="shortcut icon" sizes="196x196" href="/img/favicon/favicon-196.png">
<!-- iOS -->
<link rel="apple-touch-icon" href="/img/favicon/favicon-152.png" sizes="152x152">
<link rel="apple-touch-icon" href="/img/favicon/favicon-180.png" sizes="180x180">
<meta charset="utf-8">
<meta name='robots' content='index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1' />
<meta name="theme-color" content="{{ browser_color }}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="description" content="{{ description }}">
<meta name="keywords" content="{{ keywords }}">
<meta name="author" content="{{ author }}">
<link rel="canonical" href="{{ canonical }}"/>
<!-- Ressources -->
{{ google_fonts_link }}
{{ font_preloads }}
<link href="/style/style.css?{{ build_date }}" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="/style/colors.css?{{ build_date }}">
<link rel="stylesheet" href="/style/fonts.css?{{ build_date }}">
{{ theme_css }}
<!-- Social -->
<meta name="twitter:card" content="summary_large_image">
<meta property="og:image" content="{{ thumbnail }}">
<meta property="og:locale" content="en_US" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Home - {{ title }}" />
<meta property="og:description" content="{{ description }}" />
<meta property="og:url" content="{{ canonical }}" />
<meta property="og:site_name" content="{{ title }}" />
<!-- Scripts -->
<script src="https://kit.fontawesome.com/7c6bfe3c24.js" crossorigin="anonymous"></script>
<script type="text/javascript" src="/js/lumeex.js?{{ build_date }}" defer></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
</head>

29
src/templates/hero.html Normal file
View File

@ -0,0 +1,29 @@
<!-- Hero -->
<div id="hero">
<div class="content-wrapper appear">
<div class="section">
<div class="hero-background">
<div class="hero-title appear">
<div>
<h1>{{ title }}</h1>
<p>{{ subtitle }}</p>
</div>
</div>
</div>
<div class="hero-menu navigation">
<ul class="social">
<li class="social-item appear">
<a href="{{ instagram_url }}"><i class="fa-brands fa-instagram"></i></a>
</ul>
<div class="nav-links">
<ul class="nav-list">
{{ menu_items }}
</ul>
</div>
<div class="inner bottom-link appear">
<p class="navigation-bottom-link appear"><a href="#gallery"></a></p>
</div>
</div>
</div>
</div>
</div>

12
src/templates/legals.html Normal file
View File

@ -0,0 +1,12 @@
<div id="legals" class="content-wrapper appear">
<a href="/" class="back-button" aria-label="Go back to homepage"></a>
<div class="legals-content">
<h1>Legals</h1>
<h2>Hoster</h2>
<p><strong>Name:</strong> {{ hoster_name }}</p>
<p><strong>Adress:</strong> {{ hoster_adress }}</p>
<p><strong>Contact:</strong> {{ hoster_contact }}</p>
<h2>Intellectual Property</h2>
{{ intellectual_property }}
</div>
</div>