La page d'accueil vivait sous home/ dans alpinux.site.2026, sans webhook : elle était déposée à la main sur le serveur. Le dépôt avait fini par diverger — la version en ligne portait quatre mois de modifications jamais revenues dans git. Ce dépôt suit la règle des autres domaines : un domaine, un dépôt. Le site est dans site/, la chaîne de déploiement dans deploiement/ — clone sur le serveur, service d'écoute du webhook, relais Apache, et l'ACL qui donne le droit d'écrire dans le DocumentRoot. installer.sh pose l'ensemble en une fois, et se relance sans rien casser. Le rsync est sans --delete, volontairement : le DocumentRoot contient aussi le .htaccess qui relaie /public-calendars vers Nextcloud et les pages d'erreur d'ISPConfig. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ae3qPEPRrNkEY5LwcTTKc1
1229 lines
40 KiB
HTML
1229 lines
40 KiB
HTML
<!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. Réunions tous les 1er et 3e jeudis du mois.">
|
||
<meta name="robots" content="index, follow">
|
||
<title>Alpinux — Le LUG de Savoie</title>
|
||
|
||
<!-- Canonical -->
|
||
<link rel="canonical" href="https://alpinux.org/">
|
||
|
||
<!-- 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">
|
||
|
||
<!-- Open Graph -->
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:site_name" content="Alpinux">
|
||
<meta property="og:title" content="Alpinux — Le LUG de Savoie">
|
||
<meta property="og:description" content="Association dédiée à Linux et aux logiciels libres en Savoie. Réunions à la Dynamo Chambéry tous les 1er et 3e jeudis du mois.">
|
||
<meta property="og:url" content="https://alpinux.org/">
|
||
<meta property="og:image" content="https://static.alpinux.org/logo/alpinux-logo-512.png">
|
||
<meta property="og:locale" content="fr_FR">
|
||
|
||
<!-- Twitter Card -->
|
||
<meta name="twitter:card" content="summary">
|
||
<meta name="twitter:title" content="Alpinux — Le LUG de Savoie">
|
||
<meta name="twitter:description" content="Association dédiée à Linux et aux logiciels libres en Savoie.">
|
||
<meta name="twitter:image" content="https://static.alpinux.org/logo/alpinux-logo-512.png">
|
||
<meta name="twitter:site" content="@alpinux">
|
||
|
||
<!-- Structured data -->
|
||
<script type="application/ld+json">
|
||
{
|
||
"@context": "https://schema.org",
|
||
"@type": "Organization",
|
||
"name": "Alpinux",
|
||
"alternateName": "Alpinux — LUG de Savoie",
|
||
"url": "https://alpinux.org",
|
||
"logo": "https://static.alpinux.org/logo/alpinux-logo-512.png",
|
||
"description": "Association loi 1901 dédiée à Linux et aux logiciels libres en Savoie. Groupe d'utilisateurs Linux (LUG) basé à Chambéry.",
|
||
"foundingLocation": {
|
||
"@type": "Place",
|
||
"name": "Chambéry",
|
||
"address": {
|
||
"@type": "PostalAddress",
|
||
"addressLocality": "Chambéry",
|
||
"addressRegion": "Savoie",
|
||
"addressCountry": "FR"
|
||
}
|
||
},
|
||
"location": {
|
||
"@type": "Place",
|
||
"name": "Dynamo Chambéry",
|
||
"address": {
|
||
"@type": "PostalAddress",
|
||
"addressLocality": "Chambéry",
|
||
"addressRegion": "Savoie",
|
||
"addressCountry": "FR"
|
||
}
|
||
},
|
||
"sameAs": [
|
||
"https://mamot.fr/@alpinux",
|
||
"https://www.helloasso.com/associations/alpinux-le-lug-de-savoie",
|
||
"https://gitea.alpinux.org/alpinux.cedrica5l"
|
||
]
|
||
}
|
||
</script>
|
||
|
||
<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;
|
||
}
|
||
|
||
/* ── Live banner ────────────────────────────────────────────── */
|
||
#live-banner {
|
||
background: #e8f5e9;
|
||
border-bottom: 3px solid #43a047;
|
||
color: #1b5e20;
|
||
padding: .65rem 1.5rem;
|
||
text-align: center;
|
||
font-size: .92rem;
|
||
}
|
||
.live-dot {
|
||
display: inline-block;
|
||
width: 9px; height: 9px;
|
||
background: #43a047;
|
||
border-radius: 50%;
|
||
margin-right: .45rem;
|
||
vertical-align: middle;
|
||
animation: blink 1.4s ease-in-out infinite;
|
||
}
|
||
@keyframes blink { 0%,100% { opacity: 1; } 50% { opacity: .15; } }
|
||
|
||
/* ── 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;
|
||
}
|
||
|
||
/* ── Section titles ─────────────────────────────────────────── */
|
||
.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;
|
||
}
|
||
|
||
.subsection-title {
|
||
font-size: 1rem;
|
||
font-weight: 600;
|
||
color: #0f4e8f;
|
||
margin-bottom: .8rem;
|
||
}
|
||
|
||
/* ── Agenda two-column layout ───────────────────────────────── */
|
||
.agenda-layout {
|
||
display: grid;
|
||
grid-template-columns: auto 1fr;
|
||
gap: 2rem;
|
||
align-items: start;
|
||
margin-bottom: 3rem;
|
||
}
|
||
|
||
@media (max-width: 700px) {
|
||
.agenda-layout { grid-template-columns: 1fr; }
|
||
}
|
||
|
||
.upcoming-col { min-width: 0; }
|
||
|
||
.events-col {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: .8rem;
|
||
}
|
||
|
||
/* ── Planning grid ──────────────────────────────────────────── */
|
||
.planning-wrap {
|
||
background: #fff;
|
||
border-radius: 10px;
|
||
padding: 1.4rem;
|
||
box-shadow: 0 2px 10px rgba(26,107,191,.08);
|
||
margin-bottom: 2rem;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.planning-layout {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 6px;
|
||
}
|
||
|
||
.day-labels {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 3px;
|
||
margin-top: 22px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.day-labels span {
|
||
height: 14px;
|
||
line-height: 14px;
|
||
font-size: .68rem;
|
||
color: #bbb;
|
||
text-align: right;
|
||
min-width: 10px;
|
||
}
|
||
|
||
.planning-right {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 3px;
|
||
}
|
||
|
||
.planning-months {
|
||
display: flex;
|
||
gap: 3px;
|
||
height: 18px;
|
||
align-items: flex-end;
|
||
}
|
||
|
||
.planning-months span {
|
||
width: 14px;
|
||
font-size: .68rem;
|
||
color: #bbb;
|
||
white-space: nowrap;
|
||
overflow: visible;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
#planning-grid {
|
||
display: grid;
|
||
grid-template-rows: repeat(7, 14px);
|
||
gap: 3px;
|
||
grid-auto-flow: column;
|
||
grid-auto-columns: 14px;
|
||
}
|
||
|
||
.day-cell {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 2px;
|
||
background: #ebedf0;
|
||
}
|
||
|
||
.day-cell.past { opacity: .4; }
|
||
.day-cell.ev1 { background: #9fc5e8; cursor: pointer; }
|
||
.day-cell.ev2 { background: #1a6bbf; cursor: pointer; }
|
||
.day-cell.today { outline: 2px solid #f5a623; outline-offset: 1px; }
|
||
.day-cell[role=button]:hover { filter: brightness(1.14); }
|
||
.day-cell[role=button]:focus-visible { outline: 2px solid #f5a623; outline-offset: 1px; }
|
||
|
||
#planning-detail {
|
||
margin-top: 1rem;
|
||
padding: .75rem 1rem;
|
||
background: #f3f6fb;
|
||
border-radius: 6px;
|
||
border-left: 3px solid #1a6bbf;
|
||
font-size: .88rem;
|
||
}
|
||
|
||
#planning-detail ul { margin: .35rem 0 0 1.1rem; }
|
||
#planning-detail li { margin: .2rem 0; }
|
||
#planning-detail .ev-loc { color: #777; font-size: .83rem; }
|
||
|
||
.planning-legend {
|
||
margin-top: .9rem;
|
||
font-size: .75rem;
|
||
color: #bbb;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.lc {
|
||
display: inline-block;
|
||
width: 14px; height: 14px;
|
||
border-radius: 2px;
|
||
vertical-align: middle;
|
||
margin-right: .25rem;
|
||
}
|
||
|
||
.lc.ev1 { background: #9fc5e8; }
|
||
.lc.ev2 { background: #1a6bbf; }
|
||
.lc.today-swatch { background: #ebedf0; outline: 2px solid #f5a623; outline-offset: 1px; }
|
||
|
||
/* ── Event cards ────────────────────────────────────────────── */
|
||
.events {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||
gap: 1rem;
|
||
margin-bottom: 3rem;
|
||
}
|
||
|
||
/* upcoming list inside agenda-layout uses flex column */
|
||
#upcoming-list { display: flex; flex-direction: column; gap: .8rem; }
|
||
|
||
.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;
|
||
position: relative;
|
||
}
|
||
|
||
button.event-card {
|
||
text-align: left;
|
||
border: none;
|
||
width: 100%;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
transition: transform .15s, box-shadow .15s;
|
||
}
|
||
|
||
button.event-card:hover {
|
||
transform: translateY(-3px);
|
||
box-shadow: 0 6px 20px rgba(26,107,191,.18);
|
||
}
|
||
|
||
a.event-card {
|
||
text-decoration: none;
|
||
color: inherit;
|
||
transition: transform .15s, box-shadow .15s;
|
||
display: block;
|
||
}
|
||
|
||
a.event-card:hover {
|
||
transform: translateY(-3px);
|
||
box-shadow: 0 6px 20px rgba(26,107,191,.18);
|
||
}
|
||
|
||
.event-card--more {
|
||
border-left-color: #888;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
color: #555;
|
||
}
|
||
|
||
.event-card--more:hover { color: #1a6bbf; }
|
||
|
||
.event-icon { font-size: 1.8rem; margin-bottom: .4rem; }
|
||
|
||
/* ── Modal ──────────────────────────────────────────────────── */
|
||
.ev-modal {
|
||
position: fixed; inset: 0;
|
||
background: rgba(0,0,0,.45);
|
||
display: flex; align-items: center; justify-content: center;
|
||
z-index: 1000;
|
||
padding: 1rem;
|
||
}
|
||
|
||
.ev-modal[hidden] { display: none; }
|
||
|
||
.ev-modal-box {
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
padding: 2rem;
|
||
max-width: 480px;
|
||
width: 100%;
|
||
position: relative;
|
||
box-shadow: 0 8px 32px rgba(0,0,0,.25);
|
||
}
|
||
|
||
.ev-modal-close {
|
||
position: absolute; top: .8rem; right: 1rem;
|
||
background: none; border: none; font-size: 1.6rem;
|
||
cursor: pointer; color: #999; line-height: 1;
|
||
}
|
||
|
||
.ev-modal-close:hover { color: #333; }
|
||
|
||
.ev-modal-title {
|
||
font-size: 1.15rem;
|
||
font-weight: 700;
|
||
color: #0f4e8f;
|
||
margin-bottom: .8rem;
|
||
padding-right: 1.5rem;
|
||
}
|
||
|
||
.ev-modal-body { font-size: .92rem; line-height: 1.6; color: #444; }
|
||
|
||
.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; }
|
||
.ev-has-desc {
|
||
position: absolute;
|
||
top: .55rem; right: .6rem;
|
||
background: #1a6bbf;
|
||
color: #fff;
|
||
font-size: .72rem;
|
||
font-weight: 700;
|
||
font-style: normal;
|
||
line-height: 1;
|
||
padding: .2rem .4rem;
|
||
border-radius: 4px;
|
||
letter-spacing: .02em;
|
||
}
|
||
|
||
/* ── Today events ───────────────────────────────────────────── */
|
||
.event-card--today {
|
||
border-left-color: #f5a623;
|
||
background: #fffcf0;
|
||
}
|
||
|
||
.ev-today-badge {
|
||
display: inline-block;
|
||
background: #f5a623;
|
||
color: #fff;
|
||
font-size: .68rem;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: .06em;
|
||
padding: .15rem .5rem;
|
||
border-radius: 3px;
|
||
margin-bottom: .45rem;
|
||
}
|
||
|
||
.event-card--past {
|
||
opacity: .6;
|
||
}
|
||
|
||
/* ── Wiki : dernières mises à jour ──────────────────────────── */
|
||
#wiki-section { margin-bottom: 3rem; }
|
||
|
||
.wiki-list {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||
gap: 1rem;
|
||
}
|
||
|
||
.wiki-card {
|
||
background: #fff;
|
||
border-radius: 10px;
|
||
padding: 1.1rem 1.3rem;
|
||
box-shadow: 0 2px 10px rgba(26,107,191,.08);
|
||
border-left: 4px solid #9fc5e8;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
display: block;
|
||
position: relative;
|
||
transition: transform .15s, box-shadow .15s;
|
||
}
|
||
|
||
.wiki-card:hover {
|
||
transform: translateY(-3px);
|
||
box-shadow: 0 6px 20px rgba(26,107,191,.18);
|
||
}
|
||
|
||
.wiki-card:hover .wiki-title { color: #1a6bbf; }
|
||
|
||
.wiki-when {
|
||
font-size: .78rem;
|
||
font-weight: 700;
|
||
color: #7a8ba0;
|
||
letter-spacing: .06em;
|
||
text-transform: uppercase;
|
||
margin-bottom: .3rem;
|
||
}
|
||
|
||
.wiki-title {
|
||
font-size: 1rem;
|
||
font-weight: 600;
|
||
line-height: 1.35;
|
||
padding-right: 4.2rem;
|
||
}
|
||
|
||
.wiki-badge {
|
||
position: absolute;
|
||
top: .7rem; right: .7rem;
|
||
background: #f5a623;
|
||
color: #fff;
|
||
font-size: .68rem;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: .06em;
|
||
padding: .15rem .5rem;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.wiki-more {
|
||
margin-top: 1rem;
|
||
font-size: .88rem;
|
||
}
|
||
|
||
.wiki-more a { color: #1a6bbf; text-decoration: none; }
|
||
.wiki-more a:hover { text-decoration: underline; }
|
||
|
||
/* ── 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>
|
||
|
||
<div id="live-banner" hidden aria-live="polite" aria-atomic="true">
|
||
<span class="live-dot" aria-hidden="true"></span>
|
||
<strong>En ce moment :</strong> <span id="live-text"></span>
|
||
</div>
|
||
|
||
<header>
|
||
<div class="logo-wrap">
|
||
<img src="https://static.alpinux.org/logo/alpinux-logo.png"
|
||
alt="Logo Alpinux — LUG de Savoie"
|
||
class="logo-img"
|
||
width="90" height="90">
|
||
<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 aria-label="Navigation principale">
|
||
<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://www.helloasso.com/associations/alpinux-le-lug-de-savoie">Adhérer</a>
|
||
</nav>
|
||
|
||
<main>
|
||
|
||
<h2 class="section-title">Agenda</h2>
|
||
|
||
<div class="agenda-layout">
|
||
|
||
<div class="planning-wrap">
|
||
<div class="planning-layout">
|
||
<div class="day-labels" aria-hidden="true">
|
||
<span>L</span><span>M</span><span>M</span><span>J</span><span>V</span><span>S</span><span>D</span>
|
||
</div>
|
||
<div class="planning-right">
|
||
<div id="planning-months" class="planning-months" aria-hidden="true"></div>
|
||
<div id="planning-grid" class="planning-grid"
|
||
role="grid" aria-label="Planning des 16 prochaines semaines"></div>
|
||
</div>
|
||
</div>
|
||
<div id="planning-detail" hidden></div>
|
||
<p class="planning-legend" aria-hidden="true">
|
||
<span><span class="lc ev1"></span>1 événement</span>
|
||
<span><span class="lc ev2"></span>2+ événements</span>
|
||
<span><span class="lc today-swatch"></span>Aujourd'hui</span>
|
||
</p>
|
||
</div>
|
||
|
||
<div class="upcoming-col">
|
||
<div id="today-section" hidden>
|
||
<h3 class="subsection-title">Rendez-vous du jour</h3>
|
||
<div class="events-col" id="today-list" style="margin-bottom:1.4rem"></div>
|
||
</div>
|
||
<h3 class="subsection-title">Prochains rendez-vous</h3>
|
||
<div class="events-col" id="upcoming-list"></div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div id="ev-modal" class="ev-modal" hidden aria-modal="true" role="dialog" aria-labelledby="ev-modal-title">
|
||
<div class="ev-modal-box">
|
||
<button class="ev-modal-close" aria-label="Fermer">×</button>
|
||
<div id="ev-modal-title" class="ev-modal-title"></div>
|
||
<div id="ev-modal-body" class="ev-modal-body"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<section id="wiki-section" hidden>
|
||
<h2 class="section-title">Le wiki, fraîchement mis à jour</h2>
|
||
<div class="wiki-list" id="wiki-list"></div>
|
||
<p class="wiki-more">
|
||
<a href="https://wiki.alpinux.org">Parcourir tout le wiki →</a>
|
||
</p>
|
||
</section>
|
||
|
||
<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 & 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 & quiz</div>
|
||
<div class="service-desc">Mini-jeux et questionnaires interactifs</div>
|
||
</a>
|
||
</div>
|
||
|
||
</main>
|
||
|
||
<footer>
|
||
<p>© Alpinux — Association loi 1901 · Chambéry, Savoie</p>
|
||
<p style="margin-top:.4rem;">
|
||
<a href="https://wiki.alpinux.org/alpinux/">À propos</a> ·
|
||
<a href="https://wiki.alpinux.org/alpinux/faq/">FAQ</a> ·
|
||
<a href="https://www.helloasso.com/associations/alpinux-le-lug-de-savoie">Adhérer</a>
|
||
</p>
|
||
</footer>
|
||
|
||
<script>
|
||
(function () {
|
||
'use strict';
|
||
|
||
var CAL_URL = '/public-calendars/n5BWPYsxw7FCYozM';
|
||
var DISPLAY_WEEKS = 16;
|
||
var RRULE_DAY = { SU:0, MO:1, TU:2, WE:3, TH:4, FR:5, SA:6 };
|
||
|
||
/* ── ICS parsing ─────────────────────────────────────────────── */
|
||
|
||
function unfold(ics) {
|
||
return ics.replace(/\r\n[ \t]/g, '').replace(/\n[ \t]/g, '');
|
||
}
|
||
|
||
function parseICSDate(line) {
|
||
var ci = line.indexOf(':');
|
||
var left = line.slice(0, ci).toUpperCase();
|
||
var val = line.slice(ci + 1).trim();
|
||
var y = +val.slice(0,4), mo = +val.slice(4,6)-1, d = +val.slice(6,8);
|
||
if (left.indexOf('VALUE=DATE') >= 0 || /^\d{8}$/.test(val))
|
||
return { date: new Date(y, mo, d), allDay: true };
|
||
var h = +val.slice(9,11), mi = +val.slice(11,13), s = +val.slice(13,15);
|
||
var dt = val.slice(-1) === 'Z'
|
||
? new Date(Date.UTC(y, mo, d, h, mi, s))
|
||
: new Date(y, mo, d, h, mi, s);
|
||
return { date: dt, allDay: false };
|
||
}
|
||
|
||
function parseICS(text) {
|
||
var events = [], cur = null;
|
||
unfold(text).split(/\r?\n/).forEach(function(line) {
|
||
if (line === 'BEGIN:VEVENT') { cur = { exdates: [] }; return; }
|
||
if (line === 'END:VEVENT') { if (cur && cur.start) events.push(cur); cur = null; return; }
|
||
if (!cur || line.indexOf(':') < 0) return;
|
||
var ci = line.indexOf(':');
|
||
var key = line.slice(0, ci).split(';')[0].toUpperCase();
|
||
var val = line.slice(ci + 1);
|
||
if (key === 'SUMMARY') cur.title = val.replace(/\\,/g, ',').replace(/\\n/g, ' ').trim();
|
||
if (key === 'LOCATION') cur.location = val.replace(/\\,/g, ',').trim();
|
||
if (key === 'DESCRIPTION') cur.desc = val.replace(/\\,/g, ',').replace(/\\n/g, '\n').replace(/\\;/g, ';').trim();
|
||
if (key === 'DTSTART') { try { var ps = parseICSDate(line); cur.start = ps.date; cur.allDay = ps.allDay; } catch(e) {} }
|
||
if (key === 'DTEND') { try { cur.end = parseICSDate(line).date; } catch(e) {} }
|
||
if (key === 'RRULE') {
|
||
var r = {};
|
||
val.split(';').forEach(function(p) {
|
||
var eq = p.indexOf('=');
|
||
if (eq > 0) r[p.slice(0, eq)] = p.slice(eq + 1);
|
||
});
|
||
cur.rrule = r;
|
||
}
|
||
if (key === 'EXDATE') {
|
||
val.split(',').forEach(function(v) {
|
||
try { cur.exdates.push(parseICSDate(line.slice(0, ci+1) + v.trim()).date); } catch(e) {}
|
||
});
|
||
}
|
||
});
|
||
return events;
|
||
}
|
||
|
||
/* ── RRULE expansion ─────────────────────────────────────────── */
|
||
|
||
function localDateStr(d) {
|
||
return d.getFullYear() + '-' +
|
||
String(d.getMonth()+1).padStart(2,'0') + '-' +
|
||
String(d.getDate()).padStart(2,'0');
|
||
}
|
||
|
||
function parseRRuleUntil(s) {
|
||
var v = s.replace('Z','');
|
||
return new Date(+v.slice(0,4), +v.slice(4,6)-1, +v.slice(6,8));
|
||
}
|
||
|
||
function getNthWeekday(year, month, jsDay, n) {
|
||
var d;
|
||
if (n > 0) {
|
||
d = new Date(year, month, 1);
|
||
while (d.getDay() !== jsDay) d.setDate(d.getDate() + 1);
|
||
d.setDate(d.getDate() + (n-1) * 7);
|
||
return d.getMonth() === month ? d : null;
|
||
}
|
||
d = new Date(year, month+1, 0);
|
||
while (d.getDay() !== jsDay) d.setDate(d.getDate() - 1);
|
||
d.setDate(d.getDate() + (n+1) * 7);
|
||
return d.getMonth() === month ? d : null;
|
||
}
|
||
|
||
function expandEvent(ev, from, to) {
|
||
if (!ev.rrule) {
|
||
return (ev.start >= from && ev.start <= to)
|
||
? [{ title: ev.title, location: ev.location, desc: ev.desc, allDay: ev.allDay, start: ev.start, end: ev.end }]
|
||
: [];
|
||
}
|
||
var rule = ev.rrule;
|
||
var freq = rule.FREQ;
|
||
var ivl = +(rule.INTERVAL || 1);
|
||
var cnt = +(rule.COUNT || 9999);
|
||
var until = rule.UNTIL ? parseRRuleUntil(rule.UNTIL) : null;
|
||
var dur = ev.end ? (ev.end - ev.start) : 0;
|
||
var exSet = {};
|
||
(ev.exdates || []).forEach(function(d) { exSet[localDateStr(d)] = true; });
|
||
var results = [];
|
||
|
||
function emit(d) {
|
||
if (exSet[localDateStr(d)]) return;
|
||
if (d < from || d > to) return;
|
||
results.push({ title: ev.title, location: ev.location, desc: ev.desc, allDay: ev.allDay,
|
||
start: d, end: dur ? new Date(d.getTime() + dur) : null });
|
||
}
|
||
|
||
var cur, y, m, n, occ, pm, pos;
|
||
|
||
if (freq === 'MONTHLY') {
|
||
y = from.getFullYear(); m = from.getMonth() - ivl; n = 0;
|
||
while (n < cnt) {
|
||
m += ivl;
|
||
while (m > 11) { m -= 12; y++; }
|
||
if (new Date(y, m, 1) > to) break;
|
||
if (until && new Date(y, m, 1) > until) break;
|
||
occ = null;
|
||
if (rule.BYDAY) {
|
||
pm = /^(-?\d+)?([A-Z]{2})$/.exec(rule.BYDAY);
|
||
if (pm) {
|
||
pos = rule.BYSETPOS ? +rule.BYSETPOS : (pm[1] ? +pm[1] : 1);
|
||
occ = getNthWeekday(y, m, RRULE_DAY[pm[2]], pos);
|
||
}
|
||
} else {
|
||
occ = new Date(y, m, ev.start.getDate());
|
||
}
|
||
if (occ) {
|
||
occ.setHours(ev.start.getHours(), ev.start.getMinutes(), ev.start.getSeconds());
|
||
if (occ >= ev.start) emit(occ);
|
||
}
|
||
n++;
|
||
}
|
||
} else if (freq === 'WEEKLY') {
|
||
cur = new Date(ev.start); n = 0;
|
||
while (cur < from) cur.setDate(cur.getDate() + 7 * ivl);
|
||
cur.setDate(cur.getDate() - 7 * ivl);
|
||
while (n < cnt && cur <= to) {
|
||
if (until && cur > until) break;
|
||
emit(new Date(cur));
|
||
cur.setDate(cur.getDate() + 7 * ivl);
|
||
n++;
|
||
}
|
||
} else if (freq === 'YEARLY') {
|
||
cur = new Date(ev.start); n = 0;
|
||
while (cur.getFullYear() < from.getFullYear()) cur.setFullYear(cur.getFullYear() + ivl);
|
||
if (cur > from) cur.setFullYear(cur.getFullYear() - ivl);
|
||
while (n < cnt && cur <= to) {
|
||
if (until && cur > until) break;
|
||
emit(new Date(cur));
|
||
cur.setFullYear(cur.getFullYear() + ivl);
|
||
n++;
|
||
}
|
||
}
|
||
return results;
|
||
}
|
||
|
||
function expandAll(events, from, to) {
|
||
var out = [];
|
||
events.forEach(function(ev) {
|
||
expandEvent(ev, from, to).forEach(function(o) { out.push(o); });
|
||
});
|
||
return out;
|
||
}
|
||
|
||
/* ── Rendering ───────────────────────────────────────────────── */
|
||
|
||
function fmtDate(d, allDay) {
|
||
var s = d.toLocaleDateString('fr-FR', { weekday:'long', day:'numeric', month:'long' });
|
||
if (!allDay) s += ' à ' + d.toLocaleTimeString('fr-FR', { hour:'2-digit', minute:'2-digit' });
|
||
return s;
|
||
}
|
||
|
||
function fmtTime(d) {
|
||
return d.toLocaleTimeString('fr-FR', { hour:'2-digit', minute:'2-digit' });
|
||
}
|
||
|
||
function buildGrid(expanded) {
|
||
var now = new Date();
|
||
var todayKey = localDateStr(now);
|
||
|
||
var weekStart = new Date(now);
|
||
weekStart.setDate(now.getDate() - ((now.getDay() + 6) % 7));
|
||
weekStart.setHours(0, 0, 0, 0);
|
||
|
||
var evMap = {};
|
||
expanded.forEach(function(ev) {
|
||
var k = localDateStr(ev.start);
|
||
if (!evMap[k]) evMap[k] = [];
|
||
evMap[k].push(ev);
|
||
});
|
||
|
||
/* Month labels */
|
||
var monthsEl = document.getElementById('planning-months');
|
||
var lastMonth = -1;
|
||
monthsEl.innerHTML = '';
|
||
for (var w = 0; w < DISPLAY_WEEKS; w++) {
|
||
var md = new Date(weekStart);
|
||
md.setDate(weekStart.getDate() + w * 7);
|
||
var span = document.createElement('span');
|
||
if (md.getMonth() !== lastMonth) {
|
||
span.textContent = md.toLocaleDateString('fr-FR', { month: 'short' });
|
||
lastMonth = md.getMonth();
|
||
}
|
||
monthsEl.appendChild(span);
|
||
}
|
||
|
||
/* Grid cells */
|
||
var grid = document.getElementById('planning-grid');
|
||
var detail = document.getElementById('planning-detail');
|
||
grid.innerHTML = '';
|
||
|
||
for (var w = 0; w < DISPLAY_WEEKS; w++) {
|
||
for (var dd = 0; dd < 7; dd++) {
|
||
var cd = new Date(weekStart);
|
||
cd.setDate(weekStart.getDate() + w * 7 + dd);
|
||
var key = localDateStr(cd);
|
||
var dayEvs = evMap[key] || [];
|
||
var past = cd < now && key !== todayKey;
|
||
|
||
var cell = document.createElement('div');
|
||
cell.className = 'day-cell' +
|
||
(dayEvs.length === 1 ? ' ev1' : dayEvs.length >= 2 ? ' ev2' : '') +
|
||
(past ? ' past' : '') +
|
||
(key === todayKey ? ' today' : '');
|
||
|
||
if (dayEvs.length) {
|
||
cell.title = cd.toLocaleDateString('fr-FR', { weekday:'long', day:'numeric', month:'long' }) +
|
||
'\n' + dayEvs.map(function(e) { return '• ' + e.title; }).join('\n');
|
||
cell.setAttribute('role', 'button');
|
||
cell.setAttribute('tabindex', '0');
|
||
(function(dateCopy, evsCopy) {
|
||
cell.addEventListener('click', function() { showDetail(dateCopy, evsCopy, detail); });
|
||
cell.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Enter' || e.key === ' ') showDetail(dateCopy, evsCopy, detail);
|
||
});
|
||
})(new Date(cd), dayEvs.slice());
|
||
}
|
||
grid.appendChild(cell);
|
||
}
|
||
}
|
||
}
|
||
|
||
function showDetail(date, evs, el) {
|
||
el.hidden = false;
|
||
el.innerHTML = '<strong>' +
|
||
date.toLocaleDateString('fr-FR', { weekday:'long', day:'numeric', month:'long', year:'numeric' }) +
|
||
'</strong><ul>' +
|
||
evs.map(function(e) {
|
||
var time = e.allDay ? '' :
|
||
' <span class="ev-loc">' +
|
||
e.start.toLocaleTimeString('fr-FR', { hour:'2-digit', minute:'2-digit' }) + '</span>';
|
||
return '<li>' + e.title + time +
|
||
(e.desc ? ' <span class="ev-has-desc" title="Descriptif disponible">ℹ</span>' : '') +
|
||
(e.location ? '<span class="ev-loc"> — ' + e.location + '</span>' : '') +
|
||
'</li>';
|
||
}).join('') + '</ul>';
|
||
el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||
}
|
||
|
||
function openModal(ev) {
|
||
var modal = document.getElementById('ev-modal');
|
||
var title = document.getElementById('ev-modal-title');
|
||
var body = document.getElementById('ev-modal-body');
|
||
var opts = { weekday:'long', day:'numeric', month:'long', year:'numeric' };
|
||
var timeOpts = { hour:'2-digit', minute:'2-digit' };
|
||
var dateStr = ev.start.toLocaleDateString('fr-FR', opts);
|
||
if (!ev.allDay) {
|
||
dateStr += ' à ' + ev.start.toLocaleTimeString('fr-FR', timeOpts);
|
||
if (ev.end) dateStr += ' – ' + ev.end.toLocaleTimeString('fr-FR', timeOpts);
|
||
}
|
||
title.textContent = ev.title;
|
||
var descHtml = '';
|
||
if (ev.desc) {
|
||
descHtml = '<p style="margin-top:.8rem;color:#555;font-size:.88rem;white-space:pre-line">' +
|
||
ev.desc.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>') + '</p>';
|
||
}
|
||
body.innerHTML =
|
||
'<p>📅 ' + dateStr + '</p>' +
|
||
(ev.location ? '<p style="margin-top:.4rem">📍 ' + ev.location + '</p>' : '') +
|
||
descHtml;
|
||
modal.hidden = false;
|
||
document.getElementById('ev-modal').querySelector('.ev-modal-close').focus();
|
||
}
|
||
|
||
function buildTodayEvents(expanded) {
|
||
var now = new Date();
|
||
var todayKey = localDateStr(now);
|
||
var todayEvs = expanded
|
||
.filter(function(e) { return localDateStr(e.start) === todayKey; })
|
||
.sort(function(a, b) { return a.start - b.start; });
|
||
|
||
var section = document.getElementById('today-section');
|
||
if (!todayEvs.length) { section.hidden = true; return; }
|
||
|
||
section.hidden = false;
|
||
var container = document.getElementById('today-list');
|
||
container.innerHTML = '';
|
||
todayEvs.forEach(function(ev) {
|
||
var card = document.createElement('button');
|
||
card.className = 'event-card event-card--today';
|
||
var isPast = ev.end ? ev.end < now : (!ev.allDay && ev.start < now);
|
||
if (isPast) card.classList.add('event-card--past');
|
||
card.innerHTML =
|
||
'<span class="ev-today-badge">Aujourd\'hui</span>' +
|
||
(ev.desc ? '<span class="ev-has-desc" title="Descriptif disponible">ℹ</span>' : '') +
|
||
'<div class="event-date">' +
|
||
(ev.allDay ? 'Toute la journée'
|
||
: ev.start.toLocaleTimeString('fr-FR', { hour:'2-digit', minute:'2-digit' }) +
|
||
(ev.end ? ' – ' + ev.end.toLocaleTimeString('fr-FR', { hour:'2-digit', minute:'2-digit' }) : '')) +
|
||
'</div>' +
|
||
'<div class="event-title">' + ev.title + '</div>' +
|
||
(ev.location ? '<div class="event-where">📍 ' + ev.location + '</div>' : '');
|
||
card.addEventListener('click', function() { openModal(ev); });
|
||
container.appendChild(card);
|
||
});
|
||
}
|
||
|
||
function buildUpcoming(expanded) {
|
||
var now = new Date();
|
||
var todayKey = localDateStr(now);
|
||
var upcoming = expanded
|
||
.filter(function(e) { return e.start >= now && localDateStr(e.start) !== todayKey; })
|
||
.sort(function(a, b) { return a.start - b.start; })
|
||
.slice(0, 5);
|
||
|
||
var container = document.getElementById('upcoming-list');
|
||
container.innerHTML = '';
|
||
upcoming.forEach(function(ev) {
|
||
var card = document.createElement('button');
|
||
card.className = 'event-card';
|
||
card.innerHTML =
|
||
(ev.desc ? '<span class="ev-has-desc" title="Descriptif disponible">ℹ</span>' : '') +
|
||
'<div class="event-date">' + fmtDate(ev.start, ev.allDay) + '</div>' +
|
||
'<div class="event-title">' + ev.title + '</div>' +
|
||
(ev.location ? '<div class="event-where">📍 ' + ev.location + '</div>' : '');
|
||
card.addEventListener('click', function() { openModal(ev); });
|
||
container.appendChild(card);
|
||
});
|
||
|
||
var more = document.createElement('a');
|
||
more.className = 'event-card event-card--more';
|
||
more.href = 'https://wiki.alpinux.org/alpinux/evenements/';
|
||
more.innerHTML =
|
||
'<div class="event-icon">📅</div>' +
|
||
'<div class="event-title">Tous les événements</div>' +
|
||
'<div class="event-where">wiki.alpinux.org/alpinux/evenements</div>';
|
||
container.appendChild(more);
|
||
}
|
||
|
||
/* ── Modal close handlers ────────────────────────────────────── */
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
var modal = document.getElementById('ev-modal');
|
||
modal.querySelector('.ev-modal-close').addEventListener('click', function() {
|
||
modal.hidden = true;
|
||
});
|
||
modal.addEventListener('click', function(e) {
|
||
if (e.target === modal) modal.hidden = true;
|
||
});
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Escape') modal.hidden = true;
|
||
});
|
||
});
|
||
|
||
function updateBanner(allEvents) {
|
||
var now = new Date();
|
||
var from = new Date(now.getTime() - 86400000);
|
||
var to = new Date(now.getTime() + 86400000);
|
||
var active = expandAll(allEvents, from, to).filter(function(e) {
|
||
return e.end && e.start <= now && e.end >= now;
|
||
});
|
||
var banner = document.getElementById('live-banner');
|
||
var liveText = document.getElementById('live-text');
|
||
if (active.length) {
|
||
liveText.textContent = active.map(function(e) {
|
||
return e.title +
|
||
(e.location ? ' — ' + e.location : '') +
|
||
(e.end ? ' (jusqu’à ' + fmtTime(e.end) + ')' : '');
|
||
}).join(' | ');
|
||
banner.hidden = false;
|
||
} else {
|
||
banner.hidden = true;
|
||
}
|
||
}
|
||
|
||
/* ── Main ────────────────────────────────────────────────────── */
|
||
|
||
async function load() {
|
||
var allEvents = [];
|
||
try {
|
||
var r = await fetch(CAL_URL);
|
||
if (r.ok) {
|
||
var text = await r.text();
|
||
if (text.trim()) allEvents = parseICS(text);
|
||
}
|
||
} catch(_) {}
|
||
|
||
var now = new Date();
|
||
|
||
var weekStart = new Date(now);
|
||
weekStart.setDate(now.getDate() - ((now.getDay() + 6) % 7));
|
||
weekStart.setHours(0, 0, 0, 0);
|
||
|
||
var gridTo = new Date(weekStart);
|
||
gridTo.setDate(weekStart.getDate() + DISPLAY_WEEKS * 7);
|
||
|
||
var gridExpanded = expandAll(allEvents, weekStart, gridTo);
|
||
buildGrid(gridExpanded);
|
||
buildTodayEvents(gridExpanded);
|
||
buildUpcoming(gridExpanded);
|
||
updateBanner(allEvents);
|
||
|
||
setInterval(function() { updateBanner(allEvents); }, 60000);
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', load);
|
||
})();
|
||
</script>
|
||
|
||
<!-- ── Wiki : dernières pages créées ou modifiées ───────────────────── -->
|
||
<script>
|
||
(function () {
|
||
var URL_JSON = 'https://wiki.alpinux.org/derniers-articles.json';
|
||
var NB_MAX = 6; /* articles affichés sur l'accueil */
|
||
|
||
/* Le wiki date ses pages avec l'horodatage du commit qui a touché leur contenu :
|
||
un article déplacé ou renommé n'apparaît donc pas ici. */
|
||
|
||
function echappe(texte) {
|
||
var d = document.createElement('div');
|
||
d.textContent = texte;
|
||
return d.innerHTML;
|
||
}
|
||
|
||
function quand(iso) {
|
||
var d = new Date(iso);
|
||
if (isNaN(d)) return '';
|
||
var jour = new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
||
var auj = new Date();
|
||
auj.setHours(0, 0, 0, 0);
|
||
var jours = Math.round((auj - jour) / 86400000);
|
||
|
||
if (jours <= 0) return "aujourd'hui";
|
||
if (jours === 1) return 'hier';
|
||
if (jours < 7) return 'il y a ' + jours + ' jours';
|
||
if (jours < 14) return 'la semaine dernière';
|
||
|
||
var opts = { day: 'numeric', month: 'long' };
|
||
if (d.getFullYear() !== auj.getFullYear()) opts.year = 'numeric';
|
||
return 'le ' + d.toLocaleDateString('fr-FR', opts);
|
||
}
|
||
|
||
function carte(article) {
|
||
var a = document.createElement('a');
|
||
a.className = 'wiki-card';
|
||
a.href = article.url;
|
||
a.innerHTML =
|
||
(article.nouveau ? '<span class="wiki-badge">Nouveau</span>' : '') +
|
||
'<div class="wiki-when">' +
|
||
(article.nouveau ? 'publié ' : 'mis à jour ') + echappe(quand(article.date)) +
|
||
'</div>' +
|
||
'<div class="wiki-title">' + echappe(article.titre) + '</div>';
|
||
return a;
|
||
}
|
||
|
||
async function charger() {
|
||
var articles;
|
||
try {
|
||
var r = await fetch(URL_JSON, { cache: 'no-cache' });
|
||
if (!r.ok) return;
|
||
articles = (await r.json()).articles;
|
||
} catch (_) { return; } /* wiki injoignable : la section reste masquée */
|
||
|
||
if (!Array.isArray(articles) || !articles.length) return;
|
||
|
||
var liste = document.getElementById('wiki-list');
|
||
articles.slice(0, NB_MAX).forEach(function (article) {
|
||
if (article && article.url && article.titre) liste.appendChild(carte(article));
|
||
});
|
||
|
||
if (liste.children.length) document.getElementById('wiki-section').hidden = false;
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', charger);
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|