Skip to content

Commit ba811bb

Browse files
committed
Site: one page per language, SEO tags and sitemap
The single bilingual page hid one language with CSS and picked it from the browser language, so search engines only ever read the English text. Each language now has its own page (/ in French, /en/ in English), both generated from site-src/index.src.html by site-src/build.ps1. - canonical, hreflang, complete Open Graph / Twitter tags with a 1200x630 preview image per language, SoftwareApplication structured data, sitemap.xml - the language switch is a link (choice remembered, anchor kept); no redirect based on the browser language, a banner suggests the other page instead - ?v=<hash> on the stylesheets and scripts, descriptive h1 - Google Search Console verification tag docs/index.html and docs/en/index.html are generated: edit the source and run the script (see docs/README.md). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 8457d34 commit ba811bb

13 files changed

Lines changed: 3193 additions & 554 deletions

File tree

‎docs/README.md‎

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,111 @@
11
# Site MG4Control (GitHub Pages)
22

3-
Site statique de présentation de [MG4Control](https://github.com/SliDeeN/MG4Control), bilingue FR/EN,
4-
construit autour d'une maquette interactive de l'application (écran 1280 × 480) et d'un « véhicule
5-
virtuel ». La maquette reste collée en haut de l'écran ; cliquer une fonctionnalité dans la liste
6-
l'amène au bon écran et surligne l'élément concerné.
3+
Site statique de présentation de [MG4Control](https://github.com/SliDeeN/MG4Control), en français et
4+
en anglais, construit autour d'une maquette interactive de l'application (écran 1280 × 480) et d'un
5+
« véhicule virtuel ». La maquette reste collée en haut de l'écran ; cliquer une fonctionnalité dans la
6+
liste l'amène au bon écran et surligne l'élément concerné.
77

8-
Aucun outil de build : du HTML, du CSS et du JavaScript servis tels quels.
8+
Chaque langue a sa page : `https://slideen.github.io/MG4Control/` (français) et
9+
`https://slideen.github.io/MG4Control/en/` (anglais). Les deux sont **générées** depuis une seule
10+
source bilingue, `site-src/index.src.html`, par un script PowerShell (fourni avec Windows, aucune
11+
dépendance). Le CSS et le JavaScript sont servis tels quels.
12+
13+
> **Ne pas modifier `index.html` ni `en/index.html` à la main** : ils sont réécrits à chaque
14+
> génération. Le contenu se modifie dans `site-src/index.src.html`.
915
1016
## Structure
1117

1218
```
13-
index.html Page unique (contenu FR + EN, bascule par data-lang)
19+
../site-src/index.src.html SOURCE des deux pages (chaque texte en double : data-l="fr" et data-l="en")
20+
../site-src/build.ps1 Génère index.html, en/index.html, sitemap.xml (et les images d'aperçu)
21+
../site-src/og-card.html Modèle de l'image d'aperçu des liens partagés (1200 × 630)
22+
23+
index.html Page française (générée)
24+
en/index.html Page anglaise (générée)
25+
sitemap.xml Plan du site pour les moteurs de recherche (généré)
1426
assets/css/site.css Mise en page commune (thème clair/sombre, cartes, widgets)
1527
assets/css/explorer.css Explorateur : maquette collée, barre de thèmes, liste des fonctionnalités
1628
assets/css/sim.css Maquette de l'app : palette et dimensions de res/values*/colors.xml
1729
assets/js/strings.js Libellés de l'app en 7 langues, générés depuis res/values*/strings.xml
1830
assets/js/sim.js Simulateur : écrans, règles par firmware, raccourcis, automatisations, API
19-
assets/js/site.js Langue, thème, liens fonctionnalités → maquette, widgets
31+
assets/js/site.js Lien de langue, thème, liens fonctionnalités → maquette, widgets
2032
assets/js/explorer.js Maquette épinglée, barre de thèmes, filtre 2.6.7, retour aux explications
21-
assets/img/ Icônes (dérivées de ic_launcher-playstore.png)
33+
assets/img/ Icônes (dérivées de ic_launcher-playstore.png) et images d'aperçu og-fr / og-en
2234
tools/strings2js.ps1 Régénère strings.js depuis les ressources de l'app
2335
.nojekyll Désactive Jekyll sur GitHub Pages
2436
```
2537

38+
`site-src/` est à la racine du dépôt, hors de `docs/`, pour ne pas être publié : la source contient
39+
les deux langues et ferait doublon avec les pages.
40+
41+
## Générer les pages
42+
43+
Depuis la racine du dépôt :
44+
45+
```powershell
46+
powershell -ExecutionPolicy Bypass -File site-src/build.ps1
47+
```
48+
49+
À relancer après **toute** modification de `site-src/index.src.html`, d'un fichier CSS ou d'un
50+
fichier JavaScript. Le script :
51+
52+
- ne garde, dans chaque page, que les textes de sa langue (`data-l`) et les libellés d'accessibilité
53+
correspondants (`data-aria-fr` / `data-aria-en` → `aria-label`) ;
54+
- écrit le titre et la description de la page, lus dans la source (`data-title-*` sur `<html>`,
55+
`data-fr` / `data-en` sur la balise `description`) ;
56+
- remplace `<!-- @seo -->` par le bloc de référencement : adresse canonique, langues alternatives
57+
(`hreflang`), Open Graph et Twitter (aperçu des liens partagés), données structurées
58+
`SoftwareApplication` — la version y est celle de la pastille `vX.Y.Z` de l'accueil ;
59+
- ajoute `?v=<empreinte>` aux fichiers CSS et JavaScript, pour qu'un navigateur ne garde pas une
60+
ancienne copie en cache à côté d'une page plus récente ;
61+
- s'arrête avec un message si la source ne respecte pas ses règles (voir ci-dessous).
62+
63+
Images d'aperçu (`assets/img/og-fr.png`, `og-en.png`), à refaire quand l'écran d'accueil de la
64+
maquette change visiblement — il faut Edge ou Chrome sur la machine :
65+
66+
```powershell
67+
powershell -ExecutionPolicy Bypass -File site-src/build.ps1 -Cards
68+
```
69+
70+
### Règles de la source
71+
72+
- Un texte traduit s'écrit en deux éléments voisins, `data-l="fr"` puis `data-l="en"`, sur une balise
73+
`span`, `p`, `td`, `ul` ou `ol`. Un tel élément ne doit pas en contenir un autre du même nom
74+
(pas de `<span>` dans un `<span data-l>`).
75+
- Les liens vers les fichiers du site s'écrivent `assets/…` : le script ajoute `../` pour la page
76+
anglaise.
77+
- `build.ps1` reste en ASCII pur (PowerShell 5.1 lit les scripts sans BOM en ANSI) : aucun texte
78+
affiché n'y figure, tout est lu dans la source.
79+
80+
## Langue
81+
82+
Le bouton FR / EN est un lien vers l'autre page ; le choix est mémorisé (`localStorage`,
83+
`mg4site.lang`) et l'ancre conservée. Un visiteur qui a choisi une langue y est renvoyé d'office, de
84+
même qu'un ancien lien `?lang=fr|en`.
85+
86+
Il n'y a volontairement **aucune redirection d'après la langue du navigateur** : elle emporterait
87+
aussi les robots d'indexation, qui s'annoncent en anglais, et la page française ne serait plus lue.
88+
À la place, un bandeau propose l'autre page au visiteur dont la langue ne correspond pas, tant qu'il
89+
n'a rien choisi.
90+
91+
## Référencement
92+
93+
- `sitemap.xml` liste les deux pages ; il est à déclarer une fois dans Google Search Console
94+
(propriété « Préfixe de l'URL » `https://slideen.github.io/MG4Control/`).
95+
- Pas de `robots.txt` : il ne compte qu'à la racine de l'hôte (`slideen.github.io/`), hors de portée
96+
d'un site de projet. Sans lui, tout est autorisé.
97+
- Tester l'aperçu d'un lien partagé : coller l'adresse dans Discord, WhatsApp ou
98+
[opengraph.xyz](https://www.opengraph.xyz/).
99+
26100
## Publication (GitHub Pages)
27101

28102
Ce dossier `docs/` est servi par GitHub Pages depuis la branche `main`
29103
(**Settings → Pages** : Deploy from a branch, `main`, `/docs`). Toute modification poussée sur
30104
`main` est en ligne une à deux minutes plus tard à l'adresse `https://slideen.github.io/MG4Control/`
31105
(suivi dans l'onglet **Actions**, « pages build and deployment »).
32106

107+
Les pages générées sont versionnées avec leur source : pousser `site-src/` **et** `docs/`.
108+
33109
Le dossier `docs/` n'est pas lu par Gradle : il n'a aucun effet sur la compilation de l'APK.
34110

35111
## Tester en local
@@ -40,8 +116,9 @@ N'importe quel serveur statique convient, par exemple :
40116
python -m http.server 8000
41117
```
42118

43-
depuis le dossier `docs/`, puis ouvrir `http://localhost:8000`. (Ouvrir `index.html` directement en
44-
`file://` fonctionne aussi dans la plupart des navigateurs.)
119+
depuis le dossier `docs/`, puis ouvrir `http://localhost:8000` (page anglaise :
120+
`http://localhost:8000/en/`). Un serveur est nécessaire : ouvert directement en `file://`, le lien de
121+
langue mène à un dossier et non à sa page.
45122

46123
## Version documentée
47124

@@ -55,21 +132,24 @@ et réécrire l'encadré des nouveautés.
55132

56133
## Ajouter ou modifier une fonctionnalité
57134

58-
Chaque fonctionnalité est une carte `.pt` dans un thème `<section class="grp">` de `index.html` :
135+
Chaque fonctionnalité est une carte `.pt` dans un thème `<section class="grp">` de
136+
`site-src/index.src.html` :
59137

60138
- `data-hl-key` : l'élément de la maquette à surligner (attribut `data-hl` dans `sim.js`) ;
61139
- `data-go="écran:onglet"` ou `data-demo="nom"` : ce que la maquette affiche au clic ;
62140
- un résumé d'une ou deux phrases, puis le détail dans `<details class="more">`.
63141

64142
Un nouveau thème doit aussi avoir sa puce dans la barre `.x-chips` (`data-grp` = id du thème).
143+
Régénérer ensuite les pages.
65144

66145
## Mettre à jour les libellés de l'app
67146

68147
`assets/js/strings.js` est une copie des `strings.xml` de l'application (7 langues). Après une
69-
modification des traductions, le régénérer depuis la racine du dépôt :
148+
modification des traductions, le régénérer depuis la racine du dépôt, puis régénérer les pages :
70149

71150
```powershell
72151
powershell -ExecutionPolicy Bypass -File docs/tools/strings2js.ps1 -Res app/src/main/res -Out docs/assets/js/strings.js
152+
powershell -ExecutionPolicy Bypass -File site-src/build.ps1
73153
```
74154

75155
## Quand l'application évolue

‎docs/assets/css/site.css‎

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@
5050
color-scheme: dark;
5151
}
5252

53-
/* Langue : chaque bloc existe en FR et en EN, un seul est visible. */
54-
html[data-lang="fr"] [data-l="en"],
55-
html[data-lang="en"] [data-l="fr"] { display: none !important; }
56-
5753
*, *::before, *::after { box-sizing: border-box; }
5854
html { scroll-behavior: smooth; scroll-padding-top: 84px; -webkit-text-size-adjust: 100%; }
5955
body {
@@ -118,8 +114,24 @@ p { margin: 0 0 .9em; }
118114
@media (min-width: 980px) { .nav { display: flex; } }
119115
.head-tools { display: flex; align-items: center; gap: 8px; margin-left: auto; }
120116
.seg { display: inline-flex; border: 1px solid var(--line-2); border-radius: 9px; overflow: hidden; background: var(--surface); }
121-
.seg button { border: 0; background: transparent; color: var(--ink-2); font: 600 0.8125rem var(--font); padding: 6px 10px; cursor: pointer; }
122-
.seg button[aria-pressed="true"] { background: var(--ink); color: var(--bg); }
117+
/* Langue : deux liens, un par page (index.html et en/index.html) ; aria-current marque la page affichée. */
118+
.seg a { color: var(--ink-2); font: 600 0.8125rem var(--font); padding: 6px 10px; text-decoration: none; }
119+
.seg a:hover { color: var(--ink); }
120+
.seg a[aria-current="true"], .seg a[aria-current="true"]:hover { background: var(--ink); color: var(--bg); }
121+
/* Bandeau créé par site.js quand la langue du navigateur n'est pas celle de la page. */
122+
.lang-hint { background: var(--accent-soft); border-bottom: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); color: var(--ink); font-size: 0.875rem; }
123+
.lang-hint-in { display: flex; align-items: center; gap: 10px; min-height: 46px; padding-top: 6px; padding-bottom: 6px; }
124+
.lang-hint-txt { flex: 1 1 auto; min-width: 0; line-height: 1.35; }
125+
.lang-hint-go {
126+
background: var(--ink); color: var(--bg); border-radius: 8px; padding: 5px 12px;
127+
font-weight: 600; text-decoration: none; white-space: nowrap; flex: 0 0 auto;
128+
}
129+
.lang-hint-go:hover { color: var(--bg); background: color-mix(in srgb, var(--ink) 86%, var(--accent)); }
130+
.lang-hint-x {
131+
border: 0; background: transparent; color: var(--ink-2); width: 32px; height: 32px; border-radius: 8px;
132+
font: 400 1.375rem/1 var(--font); cursor: pointer; flex: 0 0 auto;
133+
}
134+
.lang-hint-x:hover { color: var(--ink); background: color-mix(in srgb, var(--ink) 8%, transparent); }
123135
.icon-btn {
124136
display: inline-grid; place-items: center; width: 36px; height: 36px; border-radius: 9px;
125137
border: 1px solid var(--line-2); background: var(--surface); color: var(--ink-2); cursor: pointer; text-decoration: none;
@@ -174,8 +186,10 @@ p { margin: 0 0 .9em; }
174186
color: var(--ink-2); background: var(--surface); border: 1px solid var(--line); border-radius: 999px; padding: 4px 12px 4px 6px;
175187
}
176188
.eyebrow .pill { background: var(--accent-soft); color: var(--accent-ink); border-radius: 999px; padding: 1px 8px; font-weight: 700; }
177-
.hero h1 { font-size: clamp(2.125rem, 6vw, 3.75rem); margin: 18px 0 14px; font-weight: 800; letter-spacing: -.035em; max-width: 16ch; }
189+
.hero h1 { font-size: clamp(2.125rem, 6vw, 3.75rem); margin: 18px 0 14px; font-weight: 800; letter-spacing: -.035em; }
178190
.hero h1 .four { color: var(--red); }
191+
/* Le titre dit aussi ce qu'est l'application : un moteur de recherche lit d'abord le h1. */
192+
.hero h1 .h1-sub { display: block; margin-top: 8px; font-size: clamp(1.25rem, 3vw, 1.75rem); font-weight: 700; letter-spacing: -.02em; line-height: 1.25; }
179193
.lead { font-size: clamp(1.0625rem, 2.1vw, 1.25rem); color: var(--ink-2); max-width: 62ch; }
180194
.cta { display: flex; flex-wrap: wrap; gap: 10px; margin: 22px 0 10px; }
181195
.hero-meta { display: flex; flex-wrap: wrap; gap: 8px 18px; color: var(--ink-3); font-size: 0.875rem; margin-top: 8px; }

‎docs/assets/img/og-en.png‎

154 KB
Loading

‎docs/assets/img/og-fr.png‎

155 KB
Loading

‎docs/assets/js/explorer.js‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@
6161
let savedY = 0;
6262
function syncFullLabel() {
6363
const t = isFull() ? L('Quitter le plein écran (Échap)', 'Leave full screen (Esc)') : L('Afficher la maquette en plein écran', 'Show the mockup full screen');
64-
full.setAttribute('data-aria-fr', isFull() ? 'Quitter le plein écran (Échap)' : 'Afficher la maquette en plein écran');
65-
full.setAttribute('data-aria-en', isFull() ? 'Leave full screen (Esc)' : 'Show the mockup full screen');
6664
full.setAttribute('aria-label', t); full.title = t;
6765
full.setAttribute('aria-pressed', isFull() ? 'true' : 'false');
6866
pin.title = pin.getAttribute('aria-label');
@@ -98,7 +96,6 @@
9896
document.addEventListener('fullscreenchange', onFsChange);
9997
document.addEventListener('webkitfullscreenchange', onFsChange);
10098
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && isFull() && !fsEl()) setFull(false); });
101-
document.querySelectorAll('[data-lang-btn]').forEach((b) => b.addEventListener('click', () => requestAnimationFrame(syncFullLabel)));
10299
syncFullLabel();
103100

104101
// ── Firmware ───────────────────────────────────────────────────────────
@@ -219,9 +216,8 @@
219216
// Rien à proposer tant que la lecture n'a pas atteint la liste (introduction, maquette seule).
220217
if (!jumpTarget || !pinned() || !current) { jump.hidden = true; return; }
221218
const chip = bar.querySelector('[data-grp="' + jumpTarget.id + '"]');
222-
// Libellé lu dans la langue affichée (innerText mêlerait les deux quand la barre est masquée).
223219
const lang = root.getAttribute('data-lang') === 'en' ? 'en' : 'fr';
224-
const name = chip ? (chip.querySelector('[data-l="' + lang + '"]') || chip).textContent.trim() : '';
220+
const name = chip ? chip.textContent.trim() : '';
225221
jump.textContent = '↓ ' + (lang === 'en' ? 'About this screen: ' : 'Explications de cet écran : ') + name;
226222
jump.hidden = false;
227223
}
@@ -230,7 +226,6 @@
230226
if (onlyNew() && !jumpTarget.classList.contains('has-new')) setOnlyNew(false);
231227
scrollToEl(jumpTarget);
232228
});
233-
document.querySelectorAll('[data-lang-btn]').forEach((b) => b.addEventListener('click', () => requestAnimationFrame(updateJump)));
234229

235230
// Après chaque rendu de la maquette : bouton de retour aux explications, et sur
236231
// mobile (maquette plus large que l'écran) on fait glisser vers l'élément surligné.

‎docs/assets/js/sim.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2474,7 +2474,7 @@
24742474
el.innerHTML =
24752475
'<button class="dock-pill" type="button" aria-expanded="false"><span class="dock-car" aria-hidden="true">🚗</span><span class="dock-sum"></span><span class="dock-chev" aria-hidden="true">▴</span></button>' +
24762476
'<div class="dock-panel" hidden>' +
2477-
'<div class="dock-head"><strong data-l10n="dock-title"></strong><button type="button" class="dock-x" aria-label="Fermer">✕</button></div>' +
2477+
'<div class="dock-head"><strong data-l10n="dock-title"></strong><button type="button" class="dock-x" aria-label="' + L('Fermer', 'Close') + '">✕</button></div>' +
24782478
'<div class="dock-grid">' +
24792479
'<label class="dock-f"><span data-l10n="dock-fw"></span><select data-d="fw">' + FIRMWARES.map((f) => '<option value="' + f + '">' + f + '</option>').join('') + '</select></label>' +
24802480
'<label class="dock-f"><span><span data-l10n="dock-speed"></span> <b data-o="speed"></b></span><input type="range" min="0" max="130" step="5" data-d="speed"></label>' +

0 commit comments

Comments
 (0)