Architecte static.alpinux.org : assets binaires hors git

- Logo SVG (source texte) ajouté dans docs/assets/alpinux-logo.svg
- .gitignore : exclut *.png, *.ico, docs/assets/images/ (binaires → static.alpinux.org)
- overrides/partials/logo.html : logo depuis https://static.alpinux.org/logo/
- overrides/main.html : favicons depuis static.alpinux.org via {% block extrahead %}
- mkdocs.yml : logo → SVG, ajout pymdownx.emoji (icônes Material)
- home/index.html : page d'accueil alpinux.org (logo + favicon depuis static.alpinux.org, carte dynamic.alpinux.org)
- scripts/build-assets.py : génère PNG/favicon depuis le SVG source
- scripts/static.alpinux.org.vhost.conf : template vhost Apache

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Cédrix 2026-05-03 08:46:06 +02:00
parent 2fa0fe0da9
commit 565165cc70
8 changed files with 566 additions and 2 deletions

12
.gitignore vendored
View file

@ -1 +1,11 @@
site/\n.obsidian/\n__pycache__/
site/
__pycache__/
# Assets binaires — générés par scripts/build-assets.py
# Hébergés sur https://static.alpinux.org/logo/
docs/assets/*.png
docs/assets/*.ico
docs/assets/images/
# Obsidian (local uniquement)
.obsidian/

View file

@ -0,0 +1,52 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" font-family="'Segoe UI', system-ui, -apple-system, sans-serif">
<!--
Source SVG — Tux + Alpes + typographie Alpinux
Générer le PNG depuis ce fichier avec : scripts/build-assets.py
PNG et favicon sont hébergés sur https://static.alpinux.org/logo/
-->
<!-- ── Montagnes (fond, coin sup. droit) ─────────────────── -->
<polygon points="158,4 190,56 126,56" fill="#9dc87a" opacity=".8"/>
<polygon points="158,4 149,26 167,26" fill="#fff" opacity=".9"/>
<polygon points="146,14 184,64 108,64" fill="#c8e6a0" opacity=".65"/>
<polygon points="146,14 137,34 155,34" fill="#fff" opacity=".85"/>
<!-- ── Tux ────────────────────────────────────────────────── -->
<!-- corps -->
<ellipse cx="90" cy="110" rx="42" ry="50" fill="#1c1c1c"/>
<!-- tête -->
<ellipse cx="90" cy="56" rx="32" ry="30" fill="#1c1c1c"/>
<!-- ventre blanc -->
<ellipse cx="90" cy="114" rx="27" ry="34" fill="#f0f0f0"/>
<!-- face blanche -->
<ellipse cx="90" cy="56" rx="20" ry="18" fill="#f0f0f0"/>
<!-- yeux -->
<circle cx="80" cy="49" r="7.5" fill="#1c1c1c"/>
<circle cx="100" cy="49" r="7.5" fill="#1c1c1c"/>
<circle cx="80" cy="49" r="4.8" fill="#f0f0f0"/>
<circle cx="100" cy="49" r="4.8" fill="#f0f0f0"/>
<circle cx="81" cy="50" r="3.5" fill="#1c1c1c"/>
<circle cx="101" cy="50" r="3.5" fill="#1c1c1c"/>
<circle cx="82" cy="49" r="1.2" fill="#fff"/>
<circle cx="102" cy="49" r="1.2" fill="#fff"/>
<!-- bec -->
<path d="M82,71 Q90,83 98,71 Q90,79 82,71" fill="#e8820c"/>
<!-- ailes -->
<path d="M48,94 Q30,110 34,146 Q50,156 58,125 Q50,110 50,94 Z" fill="#1c1c1c"/>
<path d="M132,94 Q150,110 146,146 Q130,156 122,125 Q130,110 130,94 Z" fill="#1c1c1c"/>
<!-- pattes -->
<path d="M70,157 Q55,166 59,173 Q73,169 82,161 Z" fill="#e8820c"/>
<path d="M110,157 Q125,166 121,173 Q107,169 98,161 Z" fill="#e8820c"/>
<!-- ── Texte : A[l]p[inux] ─────────────────────────────────
Typographie : A (400) l (700) p (400) inux (700)
NB : text-anchor="middle" + tspan peut mal centrer selon le
moteur SVG. Pour un rendu parfait → scripts/build-assets.py -->
<text x="100" y="194" text-anchor="middle" font-size="32" fill="#0f4e8f" letter-spacing="-.5">
<tspan font-weight="400">A</tspan><tspan font-weight="700">l</tspan><tspan font-weight="400">p</tspan><tspan font-weight="700">inux</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

297
home/index.html Normal file
View file

@ -0,0 +1,297 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Alpinux — Le LUG de Savoie. Groupe d'utilisateurs de Linux et logiciels libres à Chambéry.">
<meta name="robots" content="index, follow">
<title>Alpinux — Le LUG de Savoie</title>
<!-- Favicons -->
<link rel="icon" type="image/x-icon" href="https://static.alpinux.org/logo/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="https://static.alpinux.org/logo/favicon-32.png">
<link rel="icon" type="image/png" sizes="96x96" href="https://static.alpinux.org/logo/favicon-96.png">
<link rel="apple-touch-icon" sizes="192x192" href="https://static.alpinux.org/logo/favicon-192.png">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
background: #f3f6fb;
color: #1a1a2e;
min-height: 100vh;
}
/* ── Header ────────────────────────────────────────────────── */
header {
background: #1a6bbf;
color: #fff;
padding: 3rem 1.5rem 2.5rem;
text-align: center;
}
.logo-wrap {
display: flex;
align-items: center;
justify-content: center;
gap: 1.2rem;
flex-wrap: wrap;
}
.logo-img {
width: 90px;
height: 90px;
border-radius: 12px;
}
.logo-text {
text-align: left;
}
.brand {
font-size: 2.8rem;
line-height: 1;
color: #fff;
letter-spacing: -.02em;
}
.brand .light { font-weight: 300; }
.brand .bold { font-weight: 800; }
.tagline {
font-size: 1rem;
font-weight: 300;
letter-spacing: .18em;
color: rgba(255,255,255,.8);
margin-top: .3rem;
text-transform: lowercase;
}
.hero-text {
max-width: 640px;
margin: 1.8rem auto 0;
font-size: 1.05rem;
color: rgba(255,255,255,.88);
line-height: 1.7;
}
/* ── Nav links ──────────────────────────────────────────────── */
nav {
background: #0f4e8f;
display: flex;
justify-content: center;
gap: .2rem;
flex-wrap: wrap;
padding: .4rem 1rem;
}
nav a {
color: rgba(255,255,255,.85);
text-decoration: none;
font-size: .88rem;
padding: .45rem .9rem;
border-radius: 4px;
transition: background .15s;
}
nav a:hover { background: rgba(255,255,255,.15); color: #fff; }
/* ── Main ───────────────────────────────────────────────────── */
main {
max-width: 960px;
margin: 0 auto;
padding: 3rem 1.5rem;
}
/* ── Events ─────────────────────────────────────────────────── */
.section-title {
font-size: 1.15rem;
font-weight: 700;
color: #0f4e8f;
margin-bottom: 1.2rem;
display: flex;
align-items: center;
gap: .6rem;
}
.section-title::after {
content: '';
flex: 1;
height: 2px;
background: #e8f1fb;
}
.events {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 1rem;
margin-bottom: 3rem;
}
.event-card {
background: #fff;
border-radius: 10px;
padding: 1.2rem 1.4rem;
box-shadow: 0 2px 10px rgba(26,107,191,.1);
border-left: 4px solid #1a6bbf;
}
.event-date {
font-size: .82rem;
font-weight: 700;
color: #1a6bbf;
letter-spacing: .06em;
text-transform: uppercase;
margin-bottom: .3rem;
}
.event-title { font-size: 1rem; font-weight: 600; }
.event-where { font-size: .88rem; color: #666; margin-top: .3rem; }
/* ── Services ───────────────────────────────────────────────── */
.services {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
.service-card {
background: #fff;
border-radius: 10px;
padding: 1.4rem;
box-shadow: 0 2px 10px rgba(26,107,191,.08);
text-align: center;
text-decoration: none;
color: #1a1a2e;
transition: transform .15s, box-shadow .15s;
display: block;
}
.service-card:hover {
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(26,107,191,.18);
color: #1a6bbf;
}
.service-icon {
font-size: 2rem;
margin-bottom: .6rem;
}
.service-name {
font-size: 1rem;
font-weight: 700;
margin-bottom: .3rem;
}
.service-desc {
font-size: .84rem;
color: #666;
line-height: 1.4;
}
/* ── Footer ─────────────────────────────────────────────────── */
footer {
background: #0f4e8f;
color: rgba(255,255,255,.7);
text-align: center;
padding: 1.5rem;
font-size: .85rem;
}
footer a { color: rgba(255,255,255,.85); text-decoration: none; }
footer a:hover { color: #fff; }
@media (max-width: 480px) {
.brand { font-size: 2.2rem; }
.logo-img { width: 70px; height: 70px; }
}
</style>
</head>
<body>
<header>
<div class="logo-wrap">
<img src="https://static.alpinux.org/logo/alpinux-logo.png" alt="Logo Alpinux" class="logo-img">
<div class="logo-text">
<div class="brand">
<span class="light">A</span><span class="bold">l</span><span class="light">p</span><span class="bold">inux</span>
</div>
<div class="tagline">le LUG de Savoie</div>
</div>
</div>
<p class="hero-text">
Association loi 1901 dédiée à <strong>Linux et aux logiciels libres</strong> en Savoie.
Réunions tous les 1<sup>er</sup> et 3<sup>e</sup> jeudis du mois à la <strong>Dynamo Chambéry</strong>.
</p>
</header>
<nav>
<a href="https://wiki.alpinux.org">Wiki</a>
<a href="https://portail.alpinux.org">Portail membres</a>
<a href="https://installparty.alpinux.org">Install Party</a>
<a href="https://gitea.alpinux.org">Gitea</a>
<a href="https://mamot.fr/@alpinux">Mastodon</a>
<a href="https://www.helloasso.com/associations/alpinux-le-lug-de-savoie">Adhérer</a>
</nav>
<main>
<h2 class="section-title">Prochains événements</h2>
<div class="events">
<div class="event-card">
<div class="event-date">1<sup>er</sup> &amp; 3<sup>e</sup> jeudis</div>
<div class="event-title">Réunion mensuelle</div>
<div class="event-where">📍 Dynamo Chambéry — 18h00</div>
</div>
<div class="event-card">
<div class="event-date">Voir le calendrier</div>
<div class="event-title">Install Party &amp; ateliers</div>
<div class="event-where">📅 <a href="https://installparty.alpinux.org" style="color:#1a6bbf;">installparty.alpinux.org</a></div>
</div>
</div>
<h2 class="section-title">Nos services</h2>
<div class="services">
<a href="https://wiki.alpinux.org" class="service-card">
<div class="service-icon">📖</div>
<div class="service-name">Wiki</div>
<div class="service-desc">Guides, tutoriels et présentations</div>
</a>
<a href="https://portail.alpinux.org" class="service-card">
<div class="service-icon">🔐</div>
<div class="service-name">Portail membres</div>
<div class="service-desc">Espace adhérent, Nextcloud, Dolibarr</div>
</a>
<a href="https://installparty.alpinux.org" class="service-card">
<div class="service-icon">🐧</div>
<div class="service-name">Install Party</div>
<div class="service-desc">Événements &amp; inscriptions</div>
</a>
<a href="https://gitea.alpinux.org" class="service-card">
<div class="service-icon">💻</div>
<div class="service-name">Gitea</div>
<div class="service-desc">Dépôts de code de l'association</div>
</a>
<a href="https://dynamic.alpinux.org" class="service-card">
<div class="service-icon">🎮</div>
<div class="service-name">Jeux &amp; quiz</div>
<div class="service-desc">Mini-jeux et questionnaires interactifs</div>
</a>
</div>
</main>
<footer>
<p>© Alpinux — Association loi 1901 &nbsp;·&nbsp; Chambéry, Savoie</p>
<p style="margin-top:.4rem;">
<a href="https://wiki.alpinux.org/alpinux/">À propos</a> &nbsp;·&nbsp;
<a href="https://wiki.alpinux.org/alpinux/faq/">FAQ</a> &nbsp;·&nbsp;
<a href="https://mamot.fr/@alpinux">Mastodon</a> &nbsp;·&nbsp;
<a href="https://www.helloasso.com/associations/alpinux-le-lug-de-savoie">Adhérer</a>
</p>
</footer>
</body>
</html>

View file

@ -23,7 +23,7 @@ theme:
- search.suggest
- content.code.copy
- content.code.select
logo: assets/alpinux-logo.png
logo: assets/alpinux-logo.svg
font:
text: Roboto
code: Roboto Mono
@ -48,6 +48,9 @@ markdown_extensions:
- toc:
permalink: true
- footnotes
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
extra:
social:

View file

@ -5,3 +5,10 @@
A<strong>l</strong>p<strong>inux</strong>
</span>
{% endblock %}
{% block extrahead %}
<link rel="icon" type="image/x-icon" href="https://static.alpinux.org/logo/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="https://static.alpinux.org/logo/favicon-32.png">
<link rel="icon" type="image/png" sizes="96x96" href="https://static.alpinux.org/logo/favicon-96.png">
<link rel="apple-touch-icon" sizes="192x192" href="https://static.alpinux.org/logo/favicon-192.png">
{% endblock %}

View file

@ -0,0 +1,3 @@
<img src="https://static.alpinux.org/logo/alpinux-logo.png"
alt="Alpinux"
class="md-logo__image">

155
scripts/build-assets.py Normal file
View file

@ -0,0 +1,155 @@
#!/usr/bin/env python3
"""
Génère les assets binaires (logo PNG + favicons) depuis la source SVG.
Dépendances : Pillow, chromium
Usage :
python3 scripts/build-assets.py [--out /chemin/de/sortie]
Les fichiers générés sont ensuite uploadés sur static.alpinux.org/logo/
"""
import argparse
import subprocess
import tempfile
import os
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
REPO = Path(__file__).resolve().parent.parent
SVG = REPO / "docs/assets/alpinux-logo.svg"
FONT_R = "/usr/share/fonts/truetype/msttcorefonts/arial.ttf"
FONT_B = "/usr/share/fonts/truetype/msttcorefonts/arialbd.ttf"
COLOR = (15, 78, 143) # #0f4e8f
def render_shapes(svg_path: Path, width: int, height: int) -> Image.Image:
"""Render SVG shapes via chromium headless (no text)."""
html = f"""<!DOCTYPE html><html>
<head><meta charset="utf-8">
<style>*{{margin:0;padding:0}}html,body{{width:{width}px;height:{height}px;overflow:hidden;background:transparent}}</style>
</head>
<body><img src="file://{svg_path}" width="{width}" height="{height}"></body>
</html>"""
with tempfile.NamedTemporaryFile(suffix=".html", mode="w", delete=False) as f:
f.write(html)
tmp_html = f.name
out_png = tmp_html.replace(".html", ".png")
subprocess.run([
"chromium", "--headless", "--disable-gpu", "--no-sandbox",
f"--screenshot={out_png}", f"--window-size={width},{height}",
f"file://{tmp_html}"
], capture_output=True)
img = Image.open(out_png).convert("RGBA")
os.unlink(tmp_html)
os.unlink(out_png)
return img
def add_text(canvas: Image.Image) -> Image.Image:
"""Composite Alpinux text with correct weights onto the canvas."""
draw = ImageDraw.Draw(canvas)
size = 30
f_reg = ImageFont.truetype(FONT_R, size)
f_bld = ImageFont.truetype(FONT_B, size)
parts = [("A", False), ("l", True), ("p", False), ("inux", True)]
widths = []
for char, bold in parts:
f = f_bld if bold else f_reg
bb = f.getbbox(char)
widths.append(bb[2] - bb[0])
total_w = sum(widths)
x = (canvas.width - total_w) // 2
y = 164 + (36 - size) // 2 + 1
for (char, bold), w in zip(parts, widths):
f = f_bld if bold else f_reg
bb = f.getbbox(char)
draw.text((x - bb[0], y - bb[1]), char, font=f, fill=COLOR)
x += w
return canvas
def build_logo(out_dir: Path):
"""Build 200×200 logo PNG."""
shapes = render_shapes(SVG, 200, 164)
canvas = Image.new("RGBA", (200, 200), (255, 255, 255, 255))
canvas.paste(shapes, (0, 0))
canvas = add_text(canvas)
path = out_dir / "alpinux-logo.png"
canvas.convert("RGB").save(path)
print(f" {path} (200×200)")
# 512px high-res version
shapes512 = render_shapes(SVG, 512, 421)
canvas512 = Image.new("RGBA", (512, 512), (255, 255, 255, 255))
canvas512.paste(shapes512, (0, 0))
# Scale text proportionally
draw = ImageDraw.Draw(canvas512)
size = 77
f_reg = ImageFont.truetype(FONT_R, size)
f_bld = ImageFont.truetype(FONT_B, size)
parts = [("A", False), ("l", True), ("p", False), ("inux", True)]
widths = [f_bld.getbbox(c)[2]-f_bld.getbbox(c)[0] if b else f_reg.getbbox(c)[2]-f_reg.getbbox(c)[0] for c,b in parts]
total_w = sum(widths)
x = (512 - total_w) // 2
y = 421 + (91 - size) // 2 + 2
for (char, bold), w in zip(parts, widths):
f = f_bld if bold else f_reg
bb = f.getbbox(char)
draw.text((x - bb[0], y - bb[1]), char, font=f, fill=COLOR)
x += w
path512 = out_dir / "alpinux-logo-512.png"
canvas512.convert("RGB").save(path512)
print(f" {path512} (512×512)")
def build_favicons(out_dir: Path):
"""Build favicon PNG set + .ico from the icon portion of the SVG."""
icon_src = render_shapes(SVG, 200, 164).crop((0, 0, 164, 164))
sizes = {
"favicon-16.png": 16,
"favicon-32.png": 32,
"favicon.png": 48,
"favicon-96.png": 96,
"favicon-192.png": 192,
}
imgs = {}
for name, sz in sizes.items():
img = icon_src.resize((sz, sz), Image.LANCZOS)
bg = Image.new("RGBA", (sz, sz), (255, 255, 255, 255))
bg.paste(img, (0, 0))
p = out_dir / name
bg.save(p)
imgs[sz] = bg
print(f" {p} ({sz}×{sz})")
ico = out_dir / "favicon.ico"
imgs[16].save(ico, format="ICO", sizes=[(16,16),(32,32),(48,48)],
append_images=[imgs[32], imgs[48]])
print(f" {ico} (multi-size: 16+32+48)")
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--out", default="/tmp/alpinux-static-assets",
help="Répertoire de sortie (défaut: /tmp/alpinux-static-assets)")
args = parser.parse_args()
out_dir = Path(args.out)
out_dir.mkdir(parents=True, exist_ok=True)
print("Génération des assets Alpinux...")
build_logo(out_dir)
build_favicons(out_dir)
print(f"\nFichiers dans : {out_dir}")
print("À uploader sur : static.alpinux.org/logo/")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,37 @@
# Apache vhost pour static.alpinux.org
# À créer via ISPConfig : Sites > Ajouter un site web
# Domaine : static.alpinux.org | DocumentRoot : /var/www/clients/clientX/webY/web
# Activer SSL Let's Encrypt dans ISPConfig
#
# Ou, si créé manuellement, coller ce fichier dans /etc/apache2/sites-enabled/
<VirtualHost *:80>
ServerName static.alpinux.org
Redirect permanent / https://static.alpinux.org/
</VirtualHost>
<VirtualHost *:443>
ServerName static.alpinux.org
DocumentRoot /var/www/clients/client1/web-static/web
# En-têtes CORS — permet au wiki et à la page d'accueil de charger les assets
Header always set Access-Control-Allow-Origin "*"
Header always set Cache-Control "public, max-age=31536000, immutable"
# Pas d'exécution PHP
php_admin_flag engine Off
<Directory /var/www/clients/client1/web-static/web>
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
</Directory>
# Logs
ErrorLog /var/log/apache2/static.alpinux.org-error.log
CustomLog /var/log/apache2/static.alpinux.org-access.log combined
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/static.alpinux.org/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/static.alpinux.org/privkey.pem
</VirtualHost>