diff --git a/.github/actions/build-portal/action.yml b/.github/actions/build-portal/action.yml
new file mode 100644
index 0000000..4c7db93
--- /dev/null
+++ b/.github/actions/build-portal/action.yml
@@ -0,0 +1,55 @@
+name: Construction et contrôle du portail
+description: >-
+ Étapes communes au déploiement et à la vérification des pull requests : construction stricte du
+ site, contrôle d'accessibilité des pages, export des PDF et validation PDF/UA-1. Le site construit
+ reste dans ./site pour les étapes suivantes du workflow appelant.
+
+runs:
+ using: composite
+ steps:
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.12"
+ cache: pip
+
+ - name: Install dependencies
+ shell: bash
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r requirements.txt -r requirements-pdf.txt
+ sudo apt-get update
+ sudo apt-get install -y libpango-1.0-0 libpangoft2-1.0-0 poppler-utils
+
+ - name: Build documentation
+ shell: bash
+ run: mkdocs build --strict
+
+ # Accessibilité des pages HTML (WCAG 2 AA, moteur axe) : Chrome du runner, sans téléchargement
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: "22"
+
+ - name: Check accessibility (pa11y, axe)
+ shell: bash
+ env:
+ PUPPETEER_SKIP_DOWNLOAD: "true"
+ CHROME: /usr/bin/google-chrome
+ run: |
+ npm install --no-save --no-audit --no-fund pa11y@9
+ node scripts/check_a11y.js site
+
+ - name: Export PDF
+ shell: bash
+ run: python scripts/export_pdf.py
+
+ - name: Cache veraPDF
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/verapdf
+ key: verapdf-1.30.2
+
+ - name: Validate PDF/UA-1
+ shell: bash
+ run: bash scripts/check_pdf_ua.sh
diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml
index a6c944e..c7526e5 100644
--- a/.github/workflows/check-pr.yml
+++ b/.github/workflows/check-pr.yml
@@ -1,6 +1,6 @@
name: Vérification des pull requests (build, accessibilité et PDF)
-# Mêmes étapes de construction que le déploiement, sans déploiement ni secret :
+# Mêmes étapes de construction que le déploiement (.github/actions/build-portal), sans déploiement ni secret :
# le site et les PDF du portail (livre blanc, statuts, règlement intérieur, charte) sont validés
# avant fusion, et les PDF joints en artefact pour relecture.
@@ -24,47 +24,8 @@ jobs:
with:
persist-credentials: false
- - name: Set up Python
- uses: actions/setup-python@v5
- with:
- python-version: "3.12"
- cache: pip
-
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install -r requirements.txt -r requirements-pdf.txt
- sudo apt-get update
- sudo apt-get install -y libpango-1.0-0 libpangoft2-1.0-0 poppler-utils
-
- - name: Build documentation
- run: mkdocs build --strict
-
- # Accessibilité des pages HTML (WCAG 2 AA, moteur axe) : Chrome du runner, sans téléchargement
- - name: Set up Node.js
- uses: actions/setup-node@v4
- with:
- node-version: "22"
-
- - name: Check accessibility (pa11y, axe)
- env:
- PUPPETEER_SKIP_DOWNLOAD: "true"
- CHROME: /usr/bin/google-chrome
- run: |
- npm install --no-save --no-audit --no-fund pa11y@9
- node scripts/check_a11y.js site
-
- - name: Export PDF
- run: python scripts/export_pdf.py
-
- - name: Cache veraPDF
- uses: actions/cache@v4
- with:
- path: ~/.cache/verapdf
- key: verapdf-1.30.2
-
- - name: Validate PDF/UA-1
- run: bash scripts/check_pdf_ua.sh
+ - name: Build and check the portal
+ uses: ./.github/actions/build-portal
# Polices embarquées : Inter pour le texte ; toute autre police signale un glyphe absent d'Inter
- name: List embedded fonts
diff --git a/.github/workflows/deploy-o2switch.yml b/.github/workflows/deploy-o2switch.yml
index 922a784..caa8aad 100644
--- a/.github/workflows/deploy-o2switch.yml
+++ b/.github/workflows/deploy-o2switch.yml
@@ -21,33 +21,12 @@ jobs:
with:
persist-credentials: false
- - name: Set up Python
- uses: actions/setup-python@v5
- with:
- python-version: "3.12"
- cache: pip
-
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install -r requirements.txt -r requirements-pdf.txt
- sudo apt-get update
- sudo apt-get install -y libpango-1.0-0 libpangoft2-1.0-0 lftp
-
- - name: Build documentation
- run: mkdocs build --strict
-
- - name: Export whitepaper PDF
- run: python scripts/export_pdf.py
-
- - name: Cache veraPDF
- uses: actions/cache@v4
- with:
- path: ~/.cache/verapdf
- key: verapdf-1.30.2
+ # Construction stricte, accessibilité, PDF et PDF/UA-1 : mêmes contrôles que les pull requests
+ - name: Build and check the portal
+ uses: ./.github/actions/build-portal
- - name: Validate PDF/UA-1
- run: bash scripts/check_pdf_ua.sh
+ - name: Install lftp
+ run: sudo apt-get install -y lftp
- name: Add Apache configuration
run: cp deploy/o2switch/htaccess site/.htaccess
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..b0b14ce
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,48 @@
+name: Release (PDF et brouillon Zenodo)
+
+# À la publication d'une release GitHub (étiquette vX.Y, numéro de version du livre blanc) :
+# - reconstruit et contrôle le portail à cette étiquette, puis joint à la release les cinq PDF
+# (livre blanc FR et EN, projet de statuts, règlement intérieur, charte d'éthique) ;
+# - si le secret ZENODO_TOKEN existe, dépose le livre blanc sur Zenodo en brouillon
+# (scripts/zenodo_draft.py) : le DOI n'est attribué qu'à la publication manuelle depuis Zenodo.
+# Ne pas activer en plus l'intégration GitHub de Zenodo, qui créerait un second dépôt par release.
+
+on:
+ release:
+ types: [published]
+
+permissions:
+ contents: write
+
+jobs:
+ release:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.release.tag_name }}
+ persist-credentials: false
+
+ - name: Build and check the portal
+ uses: ./.github/actions/build-portal
+
+ - name: Attach PDF to the release
+ env:
+ GH_TOKEN: ${{ github.token }}
+ TAG: ${{ github.event.release.tag_name }}
+ run: gh release upload "$TAG" $(find site -name 'otspi-*.pdf' | sort) --clobber --repo "$GITHUB_REPOSITORY"
+
+ - name: Zenodo draft (white paper)
+ env:
+ ZENODO_TOKEN: ${{ secrets.ZENODO_TOKEN }}
+ ZENODO_URL: ${{ vars.ZENODO_URL }}
+ ZENODO_DEPOSITION_ID: ${{ vars.ZENODO_DEPOSITION_ID }}
+ TAG: ${{ github.event.release.tag_name }}
+ run: |
+ if [ -z "$ZENODO_TOKEN" ]; then
+ echo "Secret ZENODO_TOKEN absent : pas de dépôt Zenodo." | tee -a "$GITHUB_STEP_SUMMARY"
+ exit 0
+ fi
+ python scripts/zenodo_draft.py --version "${TAG#v}" \
+ site/livre-blanc/otspi-livre-blanc.pdf site/white-paper/otspi-white-paper.pdf
diff --git a/README.md b/README.md
index c26b7b1..032bb85 100644
--- a/README.md
+++ b/README.md
@@ -42,38 +42,76 @@ La gouvernance d'OTSPI applique une séparation stricte des devoirs conformémen
## 📁 Panoplie Documentaire Complète
```
-├── docs/
-│ ├── index.md # Page d'accueil du portail web about.otspi.org
-│ ├── CNAME # Configuration du domaine personnalisé (about.otspi.org)
+├── docs/ # Contenu du portail about.otspi.org
+│ ├── index.md # Page d'accueil (bandeau dans overrides/home.html)
+│ ├── livre-blanc/ # Livre blanc (FR, fait foi) et historique des versions
+│ ├── white-paper/ # Traduction anglaise du livre blanc
│ ├── statuts/
-│ │ └── statuts-association.md # Statuts constitutifs adoptés (OTSPI, Lyon, 20 septembre 2026)
+│ │ └── statuts-association.md # Projet de statuts, soumis à l'AG constitutive (non encore adopté)
│ ├── reglement-interieur/
-│ │ └── reglement-interieur.md # RI complet : cursus Officiers, MFA FIPS/ANSSI, Key Custodians, dépenses
-│ ├── administratif/
-│ │ ├── pv-ag-constitutive-modele.md # PV d'AG Constitutive (élections, mandats fiscaux L.80 CB, RC Pro)
-│ │ ├── rescrit-fiscal-mecenat.md # Demande formelle de rescrit fiscal DGFIP (Méthode 4P)
-│ │ └── declaration-prefecture.md # Guide des formalités préfecture (RNA, JOAFE, SIRET)
-│ ├── cadrage/
-│ │ ├── index.md # Présentation du socle d'audit initial TSP / PKI
-│ │ ├── cp-cps-cadre.md # Cadre général CP/CPS conforme RFC 3647 & ETSI EN 319 411
-│ │ ├── pssi.md # PSSI (ISO 27001 & ETSI EN 319 401, RSSI, incidents)
-│ │ └── termination-plan.md # Plan de fin d'activité (archivage 10-30 ans, séquestre financier)
+│ │ └── reglement-interieur.md # RI : cursus Officiers, MFA FIPS/ANSSI, Key Custodians, dépenses
│ ├── gouvernance/
-│ ├── charte-ethique.md # Charte d'éthique, de déontologie et de gestion désintéressée
-│ │ └── comite-technique.md # Articulation CPC (PMA), TSC (ingénierie logicielle) & RFCs
+│ │ ├── charte-ethique.md # Charte d'éthique, de déontologie et de transparence publique
+│ │ └── comite-technique.md # Articulation CPC (PMA), TSC (ingénierie logicielle) et RFC
+│ ├── administratif/ # PV d'AG constitutive (modèle), rescrit fiscal, guide préfecture…
+│ ├── cadrage/ # Socle d'audit TSP / PKI : CP/CPS, PSSI, fin d'activité, OID, devis
│ ├── adhesion/
-│ │ └── bulletin-adhesion.md # Formulaires d'adhésion (sympathisants, titulaires, bienfaiteurs)
-│ └── reunions/
-│ └── index.md # Registre public des réunions et procès-verbaux (caviardage RGPD)
-├── mkdocs.yml # Configuration Material for MkDocs (about.otspi.org)
-├── .github/workflows/
-│ └── deploy-o2switch.yml # Déploiement automatique chez o2switch (FTPS)
-├── LICENSE # Licence Creative Commons Attribution 4.0 International
-└── README.md # Documentation générale du dépôt
+│ │ └── bulletin-adhesion.md # Formulaires d'adhésion (sympathisants, titulaires, bienfaiteurs)
+│ ├── reunions/ # Registre des réunions, transparence des hébergements (FR / EN)
+│ ├── assets/ # Logos, police Inter (OFL), image de partage
+│ ├── stylesheets/ # Charte du portail (extra.css), impression et PDF (print.css)
+│ └── javascripts/ # Accessibilité, mesure d'audience sans cookie
+├── overrides/ # Gabarits Material : accueil, 404, métadonnées de partage
+├── hooks/page_lang.py # Langue de la balise selon la page
+├── scripts/ # Outils : voir ci-dessous
+├── data/qtsp-count.json # Décompte des QTSP cité par le livre blanc (annexe C)
+├── deploy/o2switch/ # Configuration Apache (.htaccess) et notes de déploiement
+├── .github/workflows/ # Déploiement, vérification des PR, contrôles périodiques
+├── mkdocs.yml # Configuration Material for MkDocs
+└── LICENSE # Creative Commons Attribution 4.0 International
```
---
+## 🛠️ Outils et contribution
+
+Le portail est construit avec [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/). Toute pull request vers `main` est vérifiée par `.github/workflows/check-pr.yml` (construction stricte, accessibilité, PDF) ; la fusion déclenche le déploiement (`deploy-o2switch.yml`), qui repasse les mêmes contrôles. Les deux workflows partagent l'action `.github/actions/build-portal`.
+
+**Construire et prévisualiser le site**
+
+```sh
+pip install -r requirements.txt
+mkdocs serve # http://127.0.0.1:8000, rechargé à chaque modification
+mkdocs build --strict # échoue sur tout lien ou ancre interne cassé
+```
+
+**Scripts** (`scripts/`)
+
+| Script | Rôle | Prérequis |
+|---|---|---|
+| `export_pdf.py` | PDF balisés du livre blanc (FR, EN), du projet de statuts, du règlement intérieur et de la charte, dans `site/` | `pip install -r requirements-pdf.txt` (WeasyPrint, Pango) |
+| `check_pdf_ua.sh` | Validation PDF/UA-1 de ces PDF avec veraPDF (téléchargé et vérifié au premier lancement) | Java |
+| `check_a11y.js` | Accessibilité WCAG 2 AA (pa11y, moteur axe) de toutes les pages du site construit | Node.js, `npm install --no-save pa11y@9`, Chrome |
+| `build_diagrams.py` | Régénère les schémas SVG du livre blanc et l'organigramme de l'accueil | — |
+| `build_og_image.py` | Régénère l'image de partage par défaut (`docs/assets/og/og-portail.png`) | Chrome |
+| `zenodo_draft.py` | Brouillon Zenodo du livre blanc à la publication d'une release (`release.yml`) ; le DOI n'est attribué qu'à la publication manuelle sur Zenodo | secret `ZENODO_TOKEN` |
+| `count_qtsp.py` | Décompte des QTSP des listes de confiance de l'EEE (`data/qtsp-count.json`) | — |
+| `check_links.py` | Liens externes cassés (contrôle mensuel) | — |
+| `check_sites.sh` | Codes HTTP, redirections et certificats des sites OTSPI (contrôle quotidien) | curl, openssl |
+
+Chaîne complète, comme en CI :
+
+```sh
+mkdocs build --strict
+python scripts/export_pdf.py
+bash scripts/check_pdf_ua.sh
+npm install --no-save pa11y@9 && node scripts/check_a11y.js site
+```
+
+Les schémas et l'image de partage sont générés puis versionnés : après modification de leur source, relancer le script correspondant et commiter le résultat.
+
+---
+
## 📬 Contact
- **Contact unique (Adhésions, Questions générales, Sécurité)** : `contact@otspi.org`
diff --git a/deploy/o2switch/README.md b/deploy/o2switch/README.md
index a327d13..1fce292 100644
--- a/deploy/o2switch/README.md
+++ b/deploy/o2switch/README.md
@@ -5,7 +5,7 @@ Ce dossier décrit l'hébergement du portail `about.otspi.org` chez o2switch (so
## Ce qui est en place
- `htaccess` : redirection HTTPS, domaine canonique, page 404, en-têtes de sécurité, compression et durées de cache. Il est copié à la racine du site au déploiement.
-- `.github/workflows/deploy-o2switch.yml` : construit le site, exporte et valide les PDF (PDF/UA-1), ajoute le `.htaccess`, puis envoie tout en FTPS (TLS obligatoire, certificat vérifié).
+- `.github/workflows/deploy-o2switch.yml` : construit et contrôle le site avec l'action commune `.github/actions/build-portal` (construction stricte, accessibilité, export et validation PDF/UA-1 des PDF, mêmes contrôles que les pull requests), ajoute le `.htaccess`, puis envoie tout en FTPS (TLS obligatoire, certificat vérifié).
- Un compte FTP dédié, cantonné au seul répertoire du site. Il est renseigné dans les secrets `O2_FTP_USERNAME` et `O2_FTP_PASSWORD` du dépôt ; l'hôte est dans la variable `O2_FTP_HOST`.
## Bascule (faite le 26 septembre 2026)
diff --git a/docs/participer/index.md b/docs/participer/index.md
new file mode 100644
index 0000000..b87ae70
--- /dev/null
+++ b/docs/participer/index.md
@@ -0,0 +1,67 @@
+---
+title: "Participer à OTSPI"
+description: "Comment contribuer à OTSPI pendant sa constitution : consultation sur le livre blanc, manifeste, partenariats, code, documents de gouvernance et signalement de vulnérabilités."
+---
+
+# Participer
+
+OTSPI est **en cours de constitution** : l'association n'est pas encore déclarée et ne recueille pas d'adhésions. Il est pourtant déjà possible de contribuer au projet, publiquement et sans inscription préalable.
+
+
+
+- :material-comment-text-multiple-outline:{ .lg .middle } __Commenter le livre blanc__
+
+ ---
+
+ Le livre blanc est en consultation publique : remarques, objections et propositions sont reçues sur GitHub Discussions, où elles restent lisibles par tous.
+
+ [:octicons-arrow-right-24: Donner votre avis](https://github.com/otspi/organisation/discussions/new?category=ideas)
+ [:octicons-arrow-right-24: Lire le livre blanc](../livre-blanc/index.md)
+
+- :material-draw-pen:{ .lg .middle } __Signer le manifeste__
+
+ ---
+
+ Le Manifeste pour une identité numérique libre et ouverte rassemble celles et ceux qui soutiennent des services de confiance conçus comme un commun numérique.
+
+ [:octicons-arrow-right-24: Lire et signer le manifeste](https://www.otspi.org/manifeste.html)
+
+- :material-handshake-outline:{ .lg .middle } __Devenir partenaire__
+
+ ---
+
+ Hébergeurs, auditeurs, laboratoires, collectivités, financeurs : les besoins et les postes de dépense sont décrits dans le livre blanc.
+
+ [:octicons-arrow-right-24: Proposer un partenariat](mailto:contact@otspi.org?subject=Partenariat%20OTSPI)
+ [:octicons-arrow-right-24: Postes de dépense (livre blanc, § 5.5)](../livre-blanc/index.md#55-structure-de-couts-et-garanties)
+
+- :material-code-braces:{ .lg .middle } __Contribuer au code__
+
+ ---
+
+ Le moteur de services de confiance et le démonstrateur sont publiés sous licence libre (EUPL 1.2) ; un pilote d'horodatage RFC 3161 fonctionne déjà, en environnement d'essai non qualifié.
+
+ [:octicons-arrow-right-24: Moteur (Rust)](https://github.com/otspi/open-eidas)
+ [:octicons-arrow-right-24: Démonstrateur](https://github.com/otspi/demo)
+ [:octicons-arrow-right-24: Essayer le pilote](https://demo.open-eidas.eu/)
+
+- :material-file-document-edit-outline:{ .lg .middle } __Améliorer les documents__
+
+ ---
+
+ Statuts, règlement intérieur, charte et socle de conformité sont versionnés sur GitHub : chaque page du portail propose un lien d'édition, et toute modification passe par une proposition publique.
+
+ [:octicons-arrow-right-24: Dépôt des documents](https://github.com/otspi/organisation)
+ [:octicons-arrow-right-24: Historique du projet de statuts](../statuts/historique.md)
+
+- :material-shield-alert-outline:{ .lg .middle } __Signaler une vulnérabilité__
+
+ ---
+
+ Les signalements de sécurité suivent une divulgation coordonnée : écrire à `contact@otspi.org` avec `[Sécurité]` en objet, ou passer par les avis de sécurité privés de GitHub.
+
+ [:octicons-arrow-right-24: Avis de sécurité privés](https://github.com/otspi/organisation/security/advisories)
+
+
+
+L'actualité du projet est publiée sur le [site de l'association](https://www.otspi.org/actualites.html), avec un flux Atom.
diff --git a/docs/statuts/historique.md b/docs/statuts/historique.md
new file mode 100644
index 0000000..2d11f33
--- /dev/null
+++ b/docs/statuts/historique.md
@@ -0,0 +1,15 @@
+# Historique du projet de statuts
+
+**Ce qui a changé dans le projet, et quand**
+
+Les statuts d'OTSPI sont un **projet**, soumis au vote de l'assemblée générale constitutive, qui n'a pas encore eu lieu. Cette page retrace les modifications de fond apportées au projet depuis sa première rédaction ; le [dépôt du portail](https://github.com/otspi/organisation/commits/main/docs/statuts/statuts-association.md) conserve l'historique complet, ligne à ligne.
+
+| Date | Modification |
+|---|---|
+| 6 septembre 2026 | Premier projet, sous le nom « Open eIDAS » : association d'intérêt général à but non lucratif, consacrée à des infrastructures de confiance numérique ouvertes. Le même jour : conformité aux critères de l'intérêt général (articles 200 et 238 bis du CGI) et aux principes des associations reconnues d'utilité publique ; bénévolat strict des dirigeants, avec la possibilité d'employer un personnel technique rémunéré sans rôle dans la gouvernance ; contribution au prix coûtant pour les usages massifs, recettes intégralement réinvesties. |
+| 20 septembre 2026 | Refonte sous le nom « Open Trusted Service Provider Initiative » (OTSPI), avec le règlement intérieur et les documents de gouvernance. Création du statut de membre bienfaiteur, sans droit de vote. |
+| 21 septembre 2026 | Déchéance d'un membre titulaire portée de deux à trois absences consécutives à l'assemblée générale ordinaire (article 5 ter). |
+| 25 septembre 2026 | Texte présenté comme un projet non encore adopté ; la date de signature est laissée à compléter le jour de l'assemblée générale constitutive. Ajout de la vacance initiale du CPC (article 8 bis, alinéa 5 bis) : tant que le comité n'est pas constitué, ses compétences exclusives ne peuvent être exercées par aucun autre organe. |
+| 27 septembre 2026 | Mise en forme, sans modification du texte : listes rétablies. Version PDF balisée, téléchargeable depuis la page du projet de statuts. |
+
+Le [règlement intérieur](../reglement-interieur/reglement-interieur.md) et la [charte d'éthique](../gouvernance/charte-ethique.md) suivent le projet de statuts ; leurs modifications sont consultables dans le même dépôt.
diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css
index 3ecfb54..5337f1e 100644
--- a/docs/stylesheets/extra.css
+++ b/docs/stylesheets/extra.css
@@ -102,6 +102,109 @@
--md-footer-bg-color--dark: #070b16;
}
+/* --------------------------------------------------------------------------
+ Thème clair : en-tête blanc, comme www.otspi.org (le thème sombre garde le bleu nuit)
+ -------------------------------------------------------------------------- */
+.md-logo img.otspi-logo--dark,
+[data-md-color-scheme="slate"] .md-logo img.otspi-logo--light {
+ display: none;
+}
+
+[data-md-color-scheme="slate"] .md-logo img.otspi-logo--dark {
+ display: block;
+}
+
+[data-md-color-scheme="default"] .md-header,
+[data-md-color-scheme="default"] .md-tabs {
+ background-color: #ffffff;
+ color: var(--otspi-navy);
+}
+
+[data-md-color-scheme="default"] .md-header {
+ border-bottom: 0;
+}
+
+[data-md-color-scheme="default"] .md-tabs {
+ border-bottom: 1px solid #e2e8f0;
+}
+
+[data-md-color-scheme="default"] .md-tabs__item--active .md-tabs__link {
+ color: var(--otspi-navy);
+ box-shadow: inset 0 -2px 0 var(--otspi-primary);
+}
+
+[data-md-color-scheme="default"] .md-header--shadow {
+ box-shadow: 0 2px 10px rgba(15, 23, 42, 0.08);
+}
+
+/* Champ de recherche lisible sur fond blanc */
+@media screen and (min-width: 60em) {
+ [data-md-color-scheme="default"] .md-search__form {
+ background-color: #f1f5f9;
+ }
+
+ [data-md-color-scheme="default"] .md-search__input,
+ [data-md-color-scheme="default"] .md-search__icon {
+ color: var(--otspi-navy);
+ }
+
+ [data-md-color-scheme="default"] .md-search__input::placeholder {
+ color: #475569;
+ }
+}
+
+/* Titre du menu latéral (mobile), assorti à l'en-tête */
+[data-md-color-scheme="default"] .md-nav--primary .md-nav__title[for="__drawer"] {
+ background-color: #ffffff;
+ color: var(--otspi-navy);
+ border-bottom: 1px solid #e2e8f0;
+}
+
+/* Appel à l'action commun avec la vitrine (overrides/partials/source.html) */
+.otspi-cta {
+ display: inline-block;
+ flex: none;
+ margin-right: 0.8rem;
+ padding: 0.4em 0.9em;
+ border-radius: 8px;
+ background-color: #ffcc00;
+ color: var(--otspi-navy);
+ font-size: 0.65rem;
+ font-weight: 700;
+ line-height: 1.4;
+ white-space: nowrap;
+}
+
+.otspi-cta:hover,
+.otspi-cta:focus-visible {
+ background-color: #ffd83d;
+ color: var(--otspi-navy);
+}
+
+/* En-tête (écran large, où Material l'affiche) : bouton puis lien vers le dépôt */
+@media screen and (min-width: 60em) {
+ .md-header__source {
+ display: flex;
+ align-items: center;
+ width: auto;
+ max-width: none;
+ }
+}
+
+/* Menu latéral (mobile, où Material l'affiche) : bouton au-dessus du lien vers le dépôt */
+@media screen and (max-width: 76.2344em) {
+ .md-nav__source {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0.6rem;
+ }
+
+ .md-nav__source .otspi-cta {
+ margin-right: 0;
+ }
+}
+
/* --------------------------------------------------------------------------
Titres : hiérarchie nette, sans le gris clair et la graisse légère de Material
-------------------------------------------------------------------------- */
diff --git a/mkdocs.yml b/mkdocs.yml
index 3fbb9f9..f5355a5 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -9,6 +9,11 @@ edit_uri: edit/main/docs/
copyright: Copyright © 2026 OTSPI — Contenu sous licence Creative Commons Attribution 4.0 International (CC-BY-4.0).
+# Liens internes : un lien vers une ancre absente (titre renommé, par exemple) fait échouer
+# le build strict, comme un lien vers une page absente.
+validation:
+ anchors: warn
+
hooks:
- hooks/page_lang.py
@@ -68,8 +73,11 @@ extra_css:
- stylesheets/print.css
plugins:
+ # Recherche en français et en anglais (livre blanc et page des hébergements existent dans les deux langues)
- search:
- lang: fr
+ lang:
+ - fr
+ - en
markdown_extensions:
- admonition
@@ -118,6 +126,7 @@ nav:
- Historique des versions: livre-blanc/historique.md
- Statuts et gouvernance:
- Projet de statuts: statuts/statuts-association.md
+ - Historique du projet de statuts: statuts/historique.md
- Charte d'éthique: gouvernance/charte-ethique.md
- Organisation technique et CPC: gouvernance/comite-technique.md
- Règlement intérieur:
@@ -139,3 +148,6 @@ nav:
- Hébergements: reunions/hebergements.md
- Hosting transparency (EN): reunions/hosting.md
- Registre des réunions et PV: reunions/index.md
+ - Participer:
+ - participer/index.md
+ - Site de l'association: https://www.otspi.org/
diff --git a/overrides/partials/logo.html b/overrides/partials/logo.html
new file mode 100644
index 0000000..f9066b9
--- /dev/null
+++ b/overrides/partials/logo.html
@@ -0,0 +1,7 @@
+{#-
+ Logo de l'en-tête : anneau bleu nuit sur l'en-tête blanc du thème clair, anneau blanc sur
+ l'en-tête bleu nuit du thème sombre (voir extra.css). Le lien parent porte déjà le nom du site :
+ les images sont décoratives.
+-#}
+
+
diff --git a/overrides/partials/source.html b/overrides/partials/source.html
index 38a62d4..7b44062 100644
--- a/overrides/partials/source.html
+++ b/overrides/partials/source.html
@@ -1,7 +1,9 @@
{#-
- Lien vers le dépôt, sans les statistiques GitHub : Material les récupère depuis
- api.github.com à chaque visite, ce qui contacte un service tiers. Le lien seul est conservé.
+ Appel à l'action commun avec www.otspi.org, puis lien vers le dépôt, sans les statistiques
+ GitHub : Material les récupère depuis api.github.com à chaque visite, ce qui contacte un service
+ tiers. Le partiel sert à la fois à l'en-tête (écran large) et au menu latéral (mobile).
-#}
+Signer le manifeste
{% set icon = config.theme.icon.repo or "fontawesome/brands/git-alt" %}
diff --git a/scripts/zenodo_draft.py b/scripts/zenodo_draft.py
new file mode 100644
index 0000000..b07ab51
--- /dev/null
+++ b/scripts/zenodo_draft.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: EUPL-1.2
+"""Prépare sur Zenodo un dépôt en brouillon du livre blanc (PDF français et anglais) pour une version donnée.
+
+Le dépôt reste un brouillon : le DOI n'est attribué, définitivement, que lorsqu'une personne relit puis
+publie le dépôt depuis Zenodo. Les métadonnées viennent de .zenodo.json ; la version et la date de
+publication sont ajoutées. Appelé par .github/workflows/release.yml à la publication d'une release.
+
+Variables d'environnement :
+ ZENODO_TOKEN jeton personnel Zenodo (portées deposit:write et deposit:actions) ;
+ ZENODO_URL https://zenodo.org par défaut (https://sandbox.zenodo.org pour un essai) ;
+ ZENODO_DEPOSITION_ID identifiant d'un dépôt déjà publié : le brouillon en devient une nouvelle
+ version (même DOI de concept) au lieu d'un dépôt distinct.
+
+Usage :
+ python3 scripts/zenodo_draft.py --version 0.9 fichier.pdf [...]
+ python3 scripts/zenodo_draft.py --version 0.9 --dry-run fichier.pdf [...] # affiche sans envoyer
+
+Aucune dépendance en dehors de la bibliothèque standard.
+"""
+
+import argparse
+import datetime
+import json
+import os
+import sys
+import urllib.request
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parent.parent
+
+
+def api(method, url, token, payload=None, data=None, content_type="application/json"):
+ body = json.dumps(payload).encode("utf-8") if payload is not None else data
+ request = urllib.request.Request(url, data=body, method=method)
+ request.add_header("Authorization", f"Bearer {token}")
+ if body is not None:
+ request.add_header("Content-Type", content_type)
+ with urllib.request.urlopen(request, timeout=300) as response:
+ raw = response.read()
+ return json.loads(raw) if raw else None
+
+
+def metadata(version):
+ meta = json.loads((ROOT / ".zenodo.json").read_text(encoding="utf-8"))
+ meta["version"] = version
+ meta["publication_date"] = datetime.date.today().isoformat()
+ return meta
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
+ parser.add_argument("--version", required=True, help="numéro de version (par exemple 0.9)")
+ parser.add_argument("--dry-run", action="store_true", help="afficher les métadonnées et les fichiers sans rien envoyer")
+ parser.add_argument("files", nargs="+", type=Path, help="fichiers à déposer")
+ args = parser.parse_args()
+
+ missing = [str(f) for f in args.files if not f.is_file()]
+ if missing:
+ sys.exit(f"Fichiers introuvables : {', '.join(missing)}")
+ meta = metadata(args.version)
+ if args.dry_run:
+ print(json.dumps({"metadata": meta, "files": [f.name for f in args.files]}, ensure_ascii=False, indent=2))
+ return
+
+ token = os.environ.get("ZENODO_TOKEN")
+ if not token:
+ sys.exit("ZENODO_TOKEN n'est pas défini.")
+ base = os.environ.get("ZENODO_URL", "https://zenodo.org").rstrip("/")
+ previous = os.environ.get("ZENODO_DEPOSITION_ID")
+
+ if previous:
+ # Nouvelle version d'un dépôt publié : Zenodo recopie ses fichiers, qu'on remplace
+ created = api("POST", f"{base}/api/deposit/depositions/{previous}/actions/newversion", token)
+ draft = api("GET", created["links"]["latest_draft"], token)
+ for old in draft.get("files", []):
+ api("DELETE", old["links"]["self"], token)
+ else:
+ draft = api("POST", f"{base}/api/deposit/depositions", token, payload={})
+
+ for path in args.files:
+ api("PUT", f'{draft["links"]["bucket"]}/{path.name}', token, data=path.read_bytes(), content_type="application/octet-stream")
+ draft = api("PUT", f'{base}/api/deposit/depositions/{draft["id"]}', token, payload={"metadata": meta})
+
+ link = draft["links"]["html"]
+ print(f"Brouillon Zenodo prêt (non publié, aucun DOI attribué) : {link}")
+ summary = os.environ.get("GITHUB_STEP_SUMMARY")
+ if summary:
+ with open(summary, "a", encoding="utf-8") as out:
+ out.write(f"Brouillon Zenodo de la version {args.version} : {link}\n\n"
+ "À relire puis publier depuis Zenodo pour attribuer le DOI.\n")
+
+
+if __name__ == "__main__":
+ main()