diff --git a/AGENTS.md b/AGENTS.md index f26bbe9..d0bdc3d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ Consignes pour les agents de code (et les humains) qui travaillent sur ce dépôt. Le fonctionnement du site pour son propriétaire est dans [README.md](README.md) ; les thèmes -saisonniers dans [THEMES.md](THEMES.md). +(saisons, fêtes, événements) dans [THEMES.md](THEMES.md). ## Le projet @@ -42,10 +42,11 @@ ordinateur **et** à ~390 px de large (pas de défilement horizontal). | `src/pages/` | Routes seulement : `/`, `/en/`, `/projets/[slug]/`, `/en/projets/[slug]/`, 404. Elles rendent une vue. | | `src/components/sections/` | Une section de l'accueil par fichier. | | `src/layouts/Base.astro` | ``, scripts d'avant-rendu (thème, habillage), haut de page, pied de page. | -| `src/scripts/ui.ts` | Tout le JS navigateur : thème, menu des langues, replis « Voir plus », bulles, liens externes. | +| `src/scripts/ui.ts` | Tout le JS navigateur : thème, menu des langues, replis « Voir plus », bulles, liens externes, feux d'artifice du Nouvel An. | | `src/styles/tokens.css` | Jetons de design (couleurs clair/sombre, verre, rayons). | | `src/styles/global.css` | Mise en page et composants. | -| `src/styles/habillages/`, `src/components/habillages/` | Thèmes saisonniers (Halloween…). | +| `src/styles/habillages/`, `src/components/habillages/` | Habillages (saisons, fêtes) : un CSS et un composant chacun, `commun.css`, `Etoiles` et `Flocons` partagés. | +| `src/lib/decor.ts` | Positions pseudo-aléatoires (graine fixe) des éléments de décor. | | `public/logos/` | Logos d'organisations, carrés (400 × 400 de préférence). | ## Conventions @@ -89,9 +90,12 @@ Deux axes indépendants, détaillés dans [THEMES.md](THEMES.md) : - **Apparence** clair/sombre : système par défaut, choix au bouton gardé pour la visite (`sessionStorage`). -- **Habillage** saisonnier (`src/data/themes.json`) : choisi dans le navigateur selon la date - du visiteur ; chaque habillage a une version jour (clair) et nuit (sombre). - Prévisualisation : `?theme=halloween` ou `?theme=aucun`. +- **Habillage** (`src/data/themes.json`) : choisi dans le navigateur selon la date du + visiteur, par priorité **spéciaux > récurrents > saisons** (une saison est toujours active). + Chaque habillage a une version jour (clair) et nuit (sombre), à concevoir séparément. + Prévisualisation : `?theme=` ou `?theme=aucun`. +- Tout décor est `aria-hidden`, passe derrière le contenu, s'allège sur téléphone (`is-wide`) + et s'immobilise si l'appareil demande moins de mouvement. ## Git diff --git a/README.md b/README.md index 58ff687..d620a22 100644 --- a/README.md +++ b/README.md @@ -1,164 +1,224 @@ -# Portfolio · orelink.github.io +
-Site statique (Astro), une seule page qui défile, pensée autant pour l'ordinateur que -pour le téléphone. Publié sur GitHub Pages à chaque merge dans `main` (voir -« Branches et mise en ligne »). +# oreLINK · Portfolio -```bash -npm install -npm run dev # http://localhost:4321 -npm run build # génère dist/ (et vérifie tous les réglages) -npm run preview # sert dist/ -``` - -## Branches et mise en ligne +**Portfolio of Aurélien Bertrand, Data Engineer based in Bordeaux, France.** +A single-page static site in French and English, driven entirely by data files. -| Branche | Rôle | Qui peut y écrire | -|---|---|---| -| `dev` | développement : on y pousse son travail | tout le monde (push direct) | -| `main` | production : ce qui est en ligne | personne en direct, uniquement par PR depuis `dev` | -| `gh-pages` | le site construit (`dist/`), servi par GitHub Pages | uniquement l'action de déploiement | +[**orelink.github.io**](https://orelink.github.io) · [English version](https://orelink.github.io/en/) -Le circuit : +[![Deploy](https://github.com/oreLINK/orelink.github.io/actions/workflows/deploy.yml/badge.svg)](https://github.com/oreLINK/orelink.github.io/actions/workflows/deploy.yml) +[![CI](https://github.com/oreLINK/orelink.github.io/actions/workflows/ci.yml/badge.svg)](https://github.com/oreLINK/orelink.github.io/actions/workflows/ci.yml) +![Astro](https://img.shields.io/badge/Astro-5-BC52EE?logo=astro&logoColor=white) +![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178C6?logo=typescript&logoColor=white) -1. Travailler sur `dev`, pousser : la CI construit le site à chaque push. -2. Ouvrir une PR `dev` → `main`. Deux vérifications doivent passer pour pouvoir merger : - **Build** (le site se construit) et **Source dev** (la PR part bien de `dev`). -3. Merger la PR : `.github/workflows/deploy.yml` construit `main` et publie `dist/` dans - `gh-pages`. Le site est en ligne une à deux minutes plus tard. +
-Les règles sont des *rulesets* GitHub (Settings → Rules → Rulesets) : +--- -- **main** : PR obligatoire, vérifications Build et Source dev obligatoires, ni force push ni - suppression. Personne ne les contourne, administrateur compris. -- **gh-pages** : écriture réservée à la clé de déploiement « gh-pages deploy (GitHub - Actions) » (Settings → Deploy keys), dont la partie privée est le secret - `GH_PAGES_DEPLOY_KEY` de l'action. Ni force push ni suppression. +## Highlights + +- **Data-driven**: profile, experience, education, certifications, volunteering, labels and + themes all live in JSON files. No content is hard-coded. +- **Validated at build time**: every file is checked against a schema (zod). A malformed + date, an invalid color or a broken translation blocks deployment with a precise error. +- **Bilingual**: French at `/`, English at `/en/`, on the same page. Translation files hold + only the text; everything else comes from the French source. +- **Light and dark**: follows the device setting, with a toggle to switch. +- **Seasonal skins**: one season is always active (spring, summer, autumn, winter), with + holidays layered on top (Halloween, Christmas, New Year, Fête de la musique), each with a + day and a night version. See [THEMES.md](THEMES.md). +- **Lightweight**: HTML and CSS generated at build time, a single small browser script, + self-hosted fonts, no third-party calls. +- **Accessible**: keyboard navigation, screen-reader labels, checked contrast in both + themes, animations disabled when the device asks for reduced motion, readable without + JavaScript. +- **Mobile-first**, as much as desktop. + +## Tech stack + +| Purpose | Choice | +|---|---| +| Generator | [Astro 5](https://astro.build), fully static output | +| Language | TypeScript (strict) | +| Data | JSON and Markdown, validated with zod (Astro content collections) | +| Styling | Plain CSS: design tokens, frosted glass, View Transitions | +| Fonts | Plus Jakarta Sans, JetBrains Mono (`@fontsource`, self-hosted) | +| Hosting | GitHub Pages, deployed with GitHub Actions | -GitHub Pages sert la branche `gh-pages`, dossier `/` (Settings → Pages). Pour relancer une -mise en ligne sans changement : Actions → Deploy to gh-pages → Run workflow. +## Getting started -## Modifier le site : les fichiers de réglage +Requirements: **Node.js 22** and npm. -Tout ce qui s'affiche vient de ces fichiers. On n'a jamais besoin de toucher au code. +```bash +npm install +npm run dev # dev server: http://localhost:4321 +npm run build # builds dist/ and validates all data +npm run preview # serves dist/ to check the final output +``` -| Fichier | Ce qu'il règle | -|---|---| -| `src/data/profil.json` | Prénom, nom, photo, situation, disponibilité, lieu, e-mail, liens, « À propos » | -| `src/data/site.json` | Titre de l'onglet, description, couleur d'accent, **ordre et titres des sections**, tous les libellés (boutons, mentions…) | -| `src/data/experiences.json` | Expérience : une entreprise, un ou plusieurs postes | -| `src/data/formations.json` | Formation | -| `src/data/certifications.json` | Certifications | -| `src/data/benevolat.json` | Bénévolat | -| `src/content/projets/*.md` | Projets : un fichier par projet (tuile de l'accueil + page de doc) | -| `src/data/en/*.json` | Version anglaise (voir « Langues » plus bas) | -| `src/data/themes.json` | Thèmes d'habillage saisonniers (Halloween…) : voir [THEMES.md](THEMES.md) | +`npm run build` doubles as the test suite: if it passes, the content is valid and the site +is ready to ship. -Pour ajouter une entrée, ajouter un objet dans le tableau JSON concerné, avec un `id` -unique. L'ordre se calcule tout seul, du plus récent au plus ancien. +## Editing the content -### `site.json` +Everything on the page comes from these files; the code never needs to change. -- `sections` : l'ordre du tableau est l'ordre sur la page. `"afficher": false` masque une - section ; `titre` change son titre. Une section vide disparaît d'elle-même. -- `couleurs.accentClair` / `couleurs.accentSombre` : la couleur des boutons et des liens, - en thème clair et en thème sombre (`#RRGGBB`). -- `textes` : chaque libellé du site (« Me contacter », « Comment je l'ai fait », « En - ligne »…). +| File | Content | +|---|---| +| `src/data/profil.json` | Name, photo, job, location, spoken languages, email, links, "About" | +| `src/data/site.json` | Title and description, accent color, **section order and titles**, every UI label | +| `src/data/experiences.json` | Experience: one company, one or more positions | +| `src/data/formations.json` | Education | +| `src/data/certifications.json` | Certifications | +| `src/data/benevolat.json` | Volunteering | +| `src/content/projets/*.md` | Projects: one file per project (home page tile and detail page) | +| `src/data/en/*.json` | English translation (text only) | +| `src/data/themes.json` | Seasonal and holiday skins, see [THEMES.md](THEMES.md) | -### `profil.json` +To add an entry, add an object with a unique `id` to the relevant array. Display order is +computed automatically, from most recent to oldest. -- `photo` : déposer l'image dans `public/` (ex. `public/photo.jpg`) et écrire - `"photo": "photo.jpg"`. Vide, ce sont les initiales qui s'affichent. -- `statut.ton` : couleur de la pastille de disponibilité, `ok` (vert), `soon` (orange) - ou `none` (gris). Vider `statut.texte` masque la pastille. -- `liens.cv` : déposer `public/cv.pdf` et écrire `"cv": "cv.pdf"`. Un lien vide masque - son bouton. -- `langues` : un drapeau par langue à côté du lieu, avec `nom` et `niveau` dans une bulle - au survol ou au toucher. `code` vaut `fr`, `en` ou `es` (drapeaux dessinés dans - `src/components/Drapeau.astro`). Un tableau vide masque les drapeaux. +
+site.json settings -### Langues +- `sections`: array order is page order. `"afficher": false` hides a section; `titre` + changes its heading. An empty section disappears on its own. +- `hauteurSectionRepliee`: above this height (in pixels), a section collapses behind a + "Show all" button. +- `couleurs.accentClair` / `couleurs.accentSombre`: button and link color in light and dark + themes (`#RRGGBB`), used when no skin is active. +- `textes`: every label on the site ("Contact me", "See more"…). -Le site existe en français (à `/`, par défaut) et en anglais (à `/en/`) ; le bouton à -gauche du thème passe de l'un à l'autre en restant sur la même page. +
-Les fichiers de `src/data/en/` ne contiennent **que les textes à traduire** : le reste -(dates, logos, liens…) vient du fichier français du même nom. Une entrée se retrouve par -son `id` (expériences, formations, bénévolat, sections de `site.json`) ; les postes d'une -même entreprise et les langues de `profil.json` se suivent dans le même ordre qu'en -français. Un champ absent reste en français ; un champ ou un `id` inconnu fait échouer le -build avec un message qui le désigne. Les pages de projets (`src/content/projets/`) ne sont -écrites qu'en français. +
+profil.json settings -### Dates +- `photo`: put the image in `public/` (e.g. `public/img/photo.jpg`) and set + `"photo": "img/photo.jpg"`. Leave it empty to show initials instead. +- `statut`: availability badge; `ton` is `ok` (green), `soon` (orange) or `none` (grey). + An empty `statut.texte` hides the badge. +- `langues`: one flag per language next to the location, with `nom` and `niveau` shown in a + tooltip. `code` is `fr`, `en` or `es` (flags drawn in `src/components/Drapeau.astro`). +- `liens.cv`: put `public/cv.pdf` in place and set `"cv": "cv.pdf"`. An empty link hides its + button. -Format `"AAAA-MM"` (ex. `"2024-03"`), ou `"AAAA"` seul pour une formation. Pour un poste -ou un bénévolat en cours, mettre `"fin": null` : la page affiche « aujourd'hui », et la -durée (« 2 ans 8 mois ») se met à jour toute seule. +
-### Logos d'organisations +
+Dates, logos and projects -Champ `logo` facultatif (entreprise, école, organisme) : un chemin dans `public/` -(ex. `"logos/acme.png"`) ou une URL. Sans logo, les initiales s'affichent. +**Dates**: `"YYYY-MM"` format (e.g. `"2024-03"`), or `"YYYY"` alone for education. For an +ongoing position or commitment, use `"fin": null`: the page shows "Present" and the duration +("2 yrs 8 mos") updates itself. -### Projets +**Logos**: optional `logo` field, a path inside `public/` (e.g. `"logos/acme.png"`) or a URL. +Square images work best (400 × 400). Without a logo, initials are shown. -Un fichier `src/content/projets/.md` = une tuile sur l'accueil **et** une page -`/projets//` « Comment je l'ai réalisé » : +**Projects**: a `src/content/projets/.md` file creates a tile on the home page **and** +a `/projets//` "How I built it" page. ```yaml --- titre: Terminus -resume: Une ou deux phrases. -date: 2026-09 # début du projet -statut: en-ligne # en-ligne | en-cours | archive -role: Projet personnel -lien: https://… # le projet en ligne (facultatif) -depot: https://github.com/… # le code (facultatif) +resume: One or two sentences. +date: 2026-09 # project start +statut: en-ligne # en-ligne | en-cours | archive +role: Side project +lien: https://… # live project (optional) +depot: https://github.com/… # source code (optional) stack: [HTML, CSS] -ordre: 1 # facultatif : force l'ordre (le plus petit d'abord) -couleur: "#E8962E" # facultatif : couleur de la pastille du projet +ordre: 1 # optional: forces the order (lowest first) +couleur: "#E8962E" # optional: badge color --- -## Le besoin +## The need … ``` -Le corps est du Markdown classique. Les titres `##` forment le sommaire de la page. +The body is plain Markdown; `##` headings build the page's table of contents. + +
-### Vérification +
+English translation -Tous ces fichiers sont vérifiés au build. Une date mal écrite, une couleur invalide ou -un champ manquant fait échouer `npm run build` (donc la mise en ligne), avec un message -qui désigne le fichier et le champ. +Files in `src/data/en/` contain **only the text to translate**: everything else (dates, +logos, links…) comes from the French file with the same name. -## Design +- Entries are matched by `id` (experience, education, certifications, volunteering, + `site.json` sections). +- Lists without an `id` (positions within a company, profile languages) follow the French + order; plain text lists (skills) are replaced as a whole. +- A missing field stays in French; an unknown field or `id` fails the build. +- Project pages are written in French only; the surrounding interface is translated. -- Police : Plus Jakarta Sans, hébergée avec le site (`@fontsource-variable`), sans appel - à un service tiers. -- `src/styles/tokens.css` : couleurs (gris neutres, clair et sombre), verre, formes. -- `src/styles/global.css` : mise en page et composants. -- Le bouton en haut à droite passe du thème clair au sombre ; le choix vaut pour la visite - en cours. Par défaut, et à chaque nouvelle visite, le site suit le réglage du système. -- Les animations s'arrêtent si l'appareil demande moins de mouvement. -- Thèmes d'habillage saisonniers (Halloween en octobre) : voir [THEMES.md](THEMES.md). +
-## Structure +## Architecture ``` src/ -├── data/ # réglages et contenu (JSON) -├── content/projets/ # un .md par projet -├── content.config.ts # vérification du contenu -├── data/en/ # traductions anglaises (seulement les textes) -├── lib/reglages.ts # vérification de site.json et profil.json, par langue -├── lib/i18n.ts # langues, et pose des traductions sur le français -├── layouts/Base.astro # , haut de page, pied de page -├── components/sections/ # une section par fichier -├── components/ # tuile projet, logo d'organisation, étiquettes, icônes -├── vues/ # accueil et page de projet, communes aux deux langues -├── pages/ # adresses : / et /en/, projets, 404 -├── scripts/ui.ts # thème, apparition, durées -└── styles/ # jetons et composants +├── data/ # content and settings (JSON); data/en/: translations +├── content/projets/ # one Markdown file per project +├── content.config.ts # data schemas +├── lib/ # settings per language, translations, dates, links, decor +├── layouts/Base.astro # , pre-render scripts, header and footer +├── vues/ # home and project page, shared by both languages +├── pages/ # routes: /, /en/, /projets/…, /en/projets/…, 404 +├── components/ +│ ├── sections/ # one home page section per file +│ └── habillages/ # decor for the seasonal and holiday skins +├── scripts/ui.ts # all browser-side JavaScript +└── styles/ + ├── tokens.css # design tokens (light / dark) + ├── global.css # layout and components + └── habillages/ # one file per skin ``` + +The codebase (file names, identifiers, comments) is written in French; see +[AGENTS.md](AGENTS.md) for conventions. + +## Deployment + +| Branch | Role | Write access | +|---|---|---| +| `dev` | Development | Direct push | +| `main` | Production, what is live | Pull requests from `dev` only | +| `gh-pages` | Built site (`dist/`), served by GitHub Pages | Deployment workflow only | + +1. Work happens on `dev`; CI builds the site on every push. +2. A `dev` → `main` pull request must pass two checks: **Build** (the site builds) and + **Source dev** (the PR comes from `dev`). +3. On merge, [`deploy.yml`](.github/workflows/deploy.yml) builds `main` and publishes `dist/` + to `gh-pages`. The site is live one to two minutes later. + +
+Repository protection + +Rules are defined as GitHub *rulesets* (Settings → Rules → Rulesets): + +- **main**: pull request required, Build and Source dev checks required, no force push or + deletion. Nobody can bypass them, admins included. +- **gh-pages**: writes restricted to the "gh-pages deploy (GitHub Actions)" deploy key + (Settings → Deploy keys), whose private key is the workflow's `GH_PAGES_DEPLOY_KEY` + secret. No force push or deletion. + +GitHub Pages serves the `gh-pages` branch, `/` folder (Settings → Pages). To redeploy +without changes: Actions → Deploy to gh-pages → Run workflow. + +
+ +## Documentation + +| Document | Audience | +|---|---| +| [README.md](README.md) | Project overview and content editing | +| [THEMES.md](THEMES.md) | Light / dark appearance and skins: calendar, settings, adding a theme | +| [AGENTS.md](AGENTS.md) | Code conventions, for contributors and coding agents | + +## Author + +**Aurélien Bertrand** (oreLINK) · Data Engineer at Betclic Group, Bordeaux +[LinkedIn](https://www.linkedin.com/in/aurelienbertrand/) · [GitHub](https://github.com/oreLINK) diff --git a/THEMES.md b/THEMES.md index 7566cea..fa165e7 100644 --- a/THEMES.md +++ b/THEMES.md @@ -5,35 +5,62 @@ Le site a deux réglages visuels indépendants : | | Quoi | Qui décide | |---|---|---| | **Apparence** | Clair ou sombre | Le visiteur (bouton en haut à droite), sinon le réglage de son appareil | -| **Thème d'habillage** | Une ambiance saisonnière posée sur le site (Halloween…) | Le calendrier, d'après `src/data/themes.json` | +| **Thème d'habillage** | Une ambiance posée sur le site : saison, fête, grand événement | Le calendrier, d'après `src/data/themes.json` | Les deux se combinent : chaque thème d'habillage existe en **deux versions**, une version **jour** (apparence claire) et une version **nuit** (apparence sombre), chacune avec son décor et ses animations. Quand le visiteur passe du clair au sombre, le décor change avec un fondu. -## Le thème par défaut +## Trois niveaux de priorité -C'est le site tel qu'il est le reste de l'année : gris neutres, accent bleu (réglé dans -`src/data/site.json` → `couleurs`). Il s'affiche dès qu'aucun thème d'habillage n'est en -période, ou quand ils sont tous désactivés. Il n'a rien à régler ici. +| Niveau | Rôle | Dates | Priorité | +|---|---|---|---| +| **Spéciaux** | Grands événements ponctuels (Coupe du monde, Roland-Garros…) | Avec l'année : `AAAA-MM-JJ` | 1, l'emporte toujours | +| **Récurrents** | Fêtes qui reviennent chaque année | Chaque année : `MM-JJ` | 2 | +| **Saisons** | Fond permanent : les 4 couvrent toute l'année | Chaque année : `MM-JJ` | 3 | +| *Défaut* | Le site sans habillage | Seulement si aucun thème n'est en période (saisons désactivées) | dernier recours | -## Halloween +À une date donnée, le site prend **le premier thème actif en période**, en partant du haut. Par +exemple le 21 juin, la Fête de la musique passe avant l'Été ; le 15 octobre, Halloween passe +avant l'Automne. À l'intérieur d'un même niveau, le premier de la liste l'emporte. -Actif **chaque année du 1er octobre à 0 h au 31 octobre à 23 h 59**. Sobre, pour rester pro. +Le **thème par défaut** (gris neutres, accent bleu réglé dans `src/data/site.json` → +`couleurs`) ne s'affiche donc plus que si les saisons sont désactivées, ou avec +`"forcer": "aucun"`. -Dans les deux versions : +## Les thèmes -- l'accent passe au **orange citrouille** (boutons, liens, le « _ » du logo) ; -- le fond se réchauffe : un halo orange en haut à gauche, violet en bas à droite. +Le contenu, la mise en page et les textes ne changent jamais : le décor passe derrière. Les +animations s'arrêtent si l'appareil demande moins de mouvement (le décor reste affiché, +immobile), et les décors sont allégés sur téléphone. -| | Version jour (apparence claire) | Version nuit (apparence sombre) | -|---|---|---| -| Décor | Une **toile d'araignée** très pâle en haut à gauche, avec son **araignée** au bout d'un fil | Une **lune** en croissant en haut à droite, quelques **étoiles**, des **chauves-souris** | -| Animations | L'araignée monte et descend en se balançant ; des **feuilles d'automne** tombent lentement en tournoyant | La lune luit doucement, les étoiles scintillent, deux chauves-souris planent près de la lune en battant des ailes et une troisième **traverse le ciel** de temps en temps | -| Sur téléphone | 4 feuilles au lieu de 6 | 5 étoiles au lieu de 7, une chauve-souris qui plane au lieu de deux | +### Saisons (dates astronomiques) + +| Thème | Période | Jour (clair) | Nuit (sombre) | Accent | +|---|---|---|---|---| +| **Printemps** | 20 mars – 20 juin | Pétales de cerisier qui volent ; fond vert et rose | Lucioles qui clignotent, quelques étoiles | Vert | +| **Été** | 21 juin – 22 sept. | Halo de soleil, dunes de sable en bas | Voie lactée en diagonale, lueur chaude sur l'horizon, étoiles filantes | Turquoise | +| **Automne** | 23 sept. – 20 déc. | Feuilles qui tombent en tournoyant | Lune rousse, brume qui dérive, quelques feuilles sombres | Bordeaux | +| **Hiver** | 21 déc. – 19 mars | Neige et congères en bas | Aurore boréale qui ondule, étoiles, neige et congères | Bleu glacier | + +### Récurrents + +| Thème | Période | Jour (clair) | Nuit (sombre) | Accent | +|---|---|---|---|---| +| **Halloween** | 1er – 31 oct. | Toile d'araignée et son araignée au bout d'un fil, feuilles qui tombent | Lune en croissant, étoiles, chauves-souris (dont une qui traverse le ciel) | Orange citrouille | +| **Noël** | 1er – 26 déc. | Neige qui tombe sur un fond rouge et vert | Neige et ciel étoilé | Rouge | +| **Nouvel An** | 31 déc. – 2 janv. | Ballons dorés aux chiffres de la nouvelle année, confettis | Feux d'artifice : fusées, gerbes (pivoine, saule, anneau, crépitement) qui retombent | Doré | +| **Fête de la musique** | 21 juin | Notes qui s'envolent, égaliseur en bas | Notes et égaliseur en néon, deux projecteurs qui balaient la scène | Violet | + +L'année des ballons est calculée dans le navigateur : de juillet à décembre, c'est l'année +suivante ; de janvier à juin, l'année en cours. + +### Spéciaux -Les animations s'arrêtent si l'appareil demande moins de mouvement : le décor reste affiché, -immobile. Le contenu, la mise en page et les textes ne changent pas ; le décor passe derrière. +Aucun pour l'instant. Prévus : un habillage **Football** (Coupe du monde, Euro), **Rugby** +(Coupe du monde de rugby) et **Tennis** (Roland-Garros). Chaque événement ne sera qu'une +ligne dans `themes.json`, qui réutilise l'habillage de son sport (voir « Ajouter un +événement spécial »). ## Les réglages : `src/data/themes.json` @@ -41,50 +68,55 @@ immobile. Le contenu, la mise en page et les textes ne changent pas ; le décor { "forcer": "", - "themes": [ - { - "id": "halloween", - "nom": "Halloween", - "actif": true, - "debut": "10-01", - "fin": "10-31", - "accentClair": "#C2410C", - "accentSombre": "#FB923C" - } + "speciaux": [], + + "recurrents": [ + { "id": "halloween", "nom": "Halloween", "actif": true, "debut": "10-01", "fin": "10-31", "accentClair": "#C2410C", "accentSombre": "#FB923C" } + ], + + "saisons": [ + { "id": "printemps", "nom": "Printemps", "actif": true, "debut": "03-20", "fin": "06-20", "accentClair": "#2E7D46", "accentSombre": "#86D19C" } ] } ``` +(Extrait : le fichier contient les 4 récurrents et les 4 saisons.) + | Champ | Rôle | |---|---| -| `forcer` | `""` : chaque thème suit ses dates (normal). `"halloween"` : ce thème toute l'année. `"aucun"` : jamais de thème, le site reste au thème par défaut. | -| `id` | Le thème dessiné dans le code (aujourd'hui : `halloween`). Ne pas changer. | +| `forcer` | `""` : chaque thème suit ses dates (normal). L'`id` d'un thème (ex. `"halloween"`) : ce thème toute l'année. `"aucun"` : jamais d'habillage, le site reste au thème par défaut. | +| `id` | Saisons et récurrents : le thème dessiné dans le code, à ne pas changer. Spéciaux : un nom libre pour l'événement (ex. `"coupe-du-monde-2026"`). | | `nom` | Son nom, pour s'y retrouver. | +| `habillage` | Spéciaux seulement : le dessin à utiliser (ex. `"football"`). | | `actif` | `true` : il s'affiche pendant sa période. `false` : il est désactivé. | -| `debut` | Premier jour, au format `MM-JJ` (`"10-01"` = 1er octobre), **dès minuit**. | -| `fin` | Dernier jour, au format `MM-JJ`, **jusqu'à 23 h 59** inclus. | +| `debut` | Premier jour, **dès minuit**. `MM-JJ` (saisons, récurrents) ou `AAAA-MM-JJ` (spéciaux). | +| `fin` | Dernier jour, **jusqu'à 23 h 59** inclus, même format. | | `accentClair` / `accentSombre` | Couleur d'accent du thème, en apparence claire et sombre (`#RRGGBB`). | Ce fichier est vérifié au build : une date mal écrite (`"1-10"`, `"10-32"`), une couleur -invalide ou un `id` inconnu fait échouer `npm run build` avec un message qui désigne le champ. +invalide, un `id` en double ou un `forcer` inconnu fait échouer `npm run build` avec un +message qui désigne le champ. ### Exemples -**Désactiver Halloween** : +**Désactiver un thème** (ex. la Fête de la musique) : dans son entrée, ```json "actif": false ``` -**Changer la période** (ex. seulement la dernière semaine d'octobre) : +**Changer une période** (ex. Halloween seulement la dernière semaine d'octobre) : ```json "debut": "10-25", "fin": "10-31" ``` -Une période peut passer le 31 décembre : `"debut": "12-15"`, `"fin": "01-05"` couvre du -15 décembre au 5 janvier. +Une période annuelle peut passer le 31 décembre : l'Hiver (`"12-21"` → `"03-19"`) et le +Nouvel An (`"12-31"` → `"01-02"`) le font déjà. + +**Revenir au site sans habillage le reste de l'année** : désactiver les 4 saisons. Les fêtes +restent actives pendant leur période. **Désactiver tous les thèmes d'un coup**, sans toucher à chacun : @@ -92,59 +124,76 @@ Une période peut passer le 31 décembre : `"debut": "12-15"`, `"fin": "01-05"` "forcer": "aucun" ``` -**Garder Halloween en ligne hors saison** (ex. pour une démonstration) : +**Garder un thème en ligne hors période** (ex. pour une démonstration) : ```json -"forcer": "halloween" +"forcer": "nouvel-an" ``` ## Comment la date est choisie Le site est statique : le thème se décide **dans le navigateur du visiteur**, à l'ouverture -de la page, avec **sa date et son heure locales**. Il n'y a donc rien à republier le -1er octobre ni le 1er novembre : le site bascule tout seul. Le choix se fait avant -l'affichage, sans flash du thème par défaut. +de la page, avec **sa date et son heure locales**. Il n'y a donc rien à republier quand une +période commence ou se termine : le site bascule tout seul. Le choix se fait avant +l'affichage, sans flash d'un autre thème. ## Prévisualiser un thème Ajouter `?theme=` à l'adresse, à n'importe quelle date et sans rien modifier : -- `https://orelink.github.io/?theme=halloween` : le site en habillage Halloween ; -- `https://orelink.github.io/?theme=aucun` : le site au thème par défaut, même en octobre. +- `https://orelink.github.io/?theme=noel` : le site en habillage Noël (marche avec tous les + `id` : `printemps`, `ete`, `automne`, `hiver`, `halloween`, `noel`, `nouvel-an`, + `fete-musique`, ou l'`id` d'un événement spécial) ; +- `https://orelink.github.io/?theme=aucun` : le site au thème par défaut. -Fonctionne aussi en local (`npm run dev`, puis `http://localhost:4321/?theme=halloween`) et -sur les pages anglaises (`/en/?theme=halloween`). Cela ne vaut que pour la page ouverte. +Fonctionne aussi en local (`npm run dev`, puis `http://localhost:4321/?theme=noel`) et sur les +pages anglaises (`/en/?theme=noel`). Cela ne vaut que pour la page ouverte. Pour voir l'autre +version (jour ou nuit), utiliser le bouton d'apparence en haut à droite. -## Ajouter un thème (pour le code) +## Ajouter un événement spécial -Un thème d'habillage, c'est quatre morceaux. Exemple pour un thème `noel` : +Quand l'habillage de son sport existe déjà, une ligne suffit dans `speciaux` : -1. **Le déclarer** dans `src/lib/reglages.ts` : ajouter `'noel'` à `HABILLAGES`. -2. **Son décor** dans `src/components/habillages/Noel.astro`, sur le modèle de - `Halloween.astro` : un bloc `decor decor-noel` qui contient deux parties, - `decor-jour` (affichée en apparence claire) et `decor-nuit` (en apparence sombre). Le - site choisit tout seul la bonne partie ; le décor entier reste caché tant que le thème - n'est pas actif. L'ajouter dans le `.backdrop` de `src/layouts/Base.astro`, à côté de - ``. Penser à ce qui a du sens à chaque moment : pas de chauves-souris en - plein jour, pas de soleil la nuit… -3. **Ses couleurs et animations** dans `src/styles/habillages/noel.css`, sur le modèle de - `halloween.css` : les jetons du fond (`--light`, `--shade-1`, `--shade-2`) et du décor - (`--decor`) en clair et en sombre sous `:root[data-habillage="noel"]`, la règle - `[data-habillage="noel"] .decor-noel { display: block; }`, puis le style des éléments. - Mettre les animations dans `@media (prefers-reduced-motion: no-preference)`. L'importer - en tête de `src/styles/global.css`, à côté de `halloween.css`. -4. **Sa période** dans `src/data/themes.json` : une entrée avec `"id": "noel"`. +```json +{ "id": "coupe-du-monde-2026", "nom": "Coupe du monde 2026", "habillage": "football", "actif": true, "debut": "2026-06-11", "fin": "2026-07-19", "accentClair": "#15803D", "accentSombre": "#86EFAC" } +``` -Si deux thèmes actifs se chevauchent, le premier de la liste l'emporte. +Sinon, il faut d'abord dessiner l'habillage (section suivante). + +## Ajouter un habillage (pour le code) + +Un habillage, c'est quatre morceaux. Exemple pour `football` : + +1. **Le déclarer** dans `src/lib/reglages.ts` : ajouter `'football'` à `HABILLAGES`. +2. **Son décor** dans `src/components/habillages/Football.astro`, sur le modèle des autres : + un bloc `decor decor-football` qui contient deux parties, `decor-jour` (affichée en + apparence claire) et `decor-nuit` (en apparence sombre). Le site choisit tout seul la + bonne partie ; le décor entier reste caché tant que l'habillage n'est pas actif. L'ajouter + dans le `.backdrop` de `src/layouts/Base.astro`, avec les autres. Penser à ce qui a du sens + à chaque moment : pas de chauves-souris en plein jour, pas de soleil la nuit… + - Positions « au hasard » mais fixes : `hasard(graine)` de `src/lib/decor.ts`. + - Étoiles et neige toutes faites : `` et ``. + - Moins d'éléments sur téléphone : la classe `is-wide` les masque sous 36rem. +3. **Ses couleurs et animations** dans `src/styles/habillages/football.css`, sur le modèle des + autres : les jetons du fond (`--light`, `--shade-1`, `--shade-2`) en clair et en sombre + sous `:root[data-habillage="football"]`, la règle + `[data-habillage="football"] .decor-football { display: block; }`, puis le style des + éléments. Mettre les animations dans `@media (prefers-reduced-motion: no-preference)`. + L'importer en tête de `src/styles/global.css`, avec les autres. Les pièces communes + (scintillement, chutes, neige) sont dans `commun.css`. +4. **Sa période** dans `src/data/themes.json` : une entrée de saison ou de récurrent avec + `"id": "football"`, ou un événement spécial avec `"habillage": "football"`. ## Fichiers | Fichier | Contenu | |---|---| -| `src/data/themes.json` | Activation, dates et couleurs d'accent des thèmes | -| `src/lib/reglages.ts` | Liste des thèmes existants (`HABILLAGES`) et vérification de `themes.json` | -| `src/layouts/Base.astro` | Le petit script qui choisit le thème selon la date (dans ``) | -| `src/styles/habillages/halloween.css` | Fond, décor et animations d'Halloween | -| `src/components/habillages/Halloween.astro` | Décor jour (toile, araignée, feuilles) et nuit (lune, étoiles, chauves-souris) ; nombre, taille et rythme des feuilles et des étoiles en tête du fichier | +| `src/data/themes.json` | Activation, dates et couleurs d'accent de tous les thèmes | +| `src/lib/reglages.ts` | Liste des habillages existants (`HABILLAGES`) et vérification de `themes.json` | +| `src/layouts/Base.astro` | Le petit script qui choisit le thème selon la date et les priorités (dans ``) | +| `src/components/habillages/*.astro` | Le décor jour et nuit de chaque habillage, plus `Etoiles` et `Flocons` (partagés) | +| `src/styles/habillages/*.css` | Fond, décor et animations de chaque habillage ; `commun.css` pour les pièces partagées | +| `src/lib/decor.ts` | Positions pseudo-aléatoires (graine fixe) des éléments de décor | +| `src/scripts/ui.ts` | Feux d'artifice et année des ballons du Nouvel An | | `src/styles/global.css` | Bascule jour / nuit des décors (règles `.decor-jour` / `.decor-nuit`) | | `src/styles/tokens.css` | Le thème par défaut, en clair et en sombre | diff --git a/src/components/habillages/Automne.astro b/src/components/habillages/Automne.astro new file mode 100644 index 0000000..393bd4e --- /dev/null +++ b/src/components/habillages/Automne.astro @@ -0,0 +1,41 @@ +--- +// Décor Automne (voir THEMES.md), derrière le contenu et caché hors saison. +// Jour : feuilles qui tombent en tournoyant. Nuit : lune rousse, brume qui dérive, quelques +// feuilles sombres. +import { hasard, vars } from '../../lib/decor'; + +const h = hasard(5); +const feuilles = (nombre: number, larges: number, couleurs: string[]) => + Array.from({ length: nombre }, (_, i) => ({ + style: `${vars({ + x: `${h.entre(3, 95)}%`, + top: `${h.entre(5, 90)}vh`, + dur: `${h.entre(17, 26)}s`, + delay: `${h.entre(-26, 0)}s`, + drift: `${h.entre(-60, 60, 0)}px`, + spin: `${h.parmi([-1, 1]) * h.entre(240, 340, 0)}deg`, + })}; color: ${couleurs[i % couleurs.length]}`, + taille: h.entre(18, 30, 0), + large: i >= larges, + })); +const jour = feuilles(7, 4, ['#B45309', '#9F1239', '#9A3412', '#CA8A04']); +const nuit = feuilles(4, 3, ['#C2410C', '#BE123C']); +--- + + diff --git a/src/components/habillages/Ete.astro b/src/components/habillages/Ete.astro new file mode 100644 index 0000000..0e78cd1 --- /dev/null +++ b/src/components/habillages/Ete.astro @@ -0,0 +1,36 @@ +--- +// Décor Été (voir THEMES.md), derrière le contenu et caché hors saison. +// Jour : halo de soleil et dunes de sable. Nuit : Voie lactée en diagonale, lueur chaude sur +// l'horizon, étoiles et étoiles filantes. +import Etoiles from './Etoiles.astro'; +import { hasard, vars } from '../../lib/decor'; + +// La Voie lactée : de minuscules étoiles serrées autour d'une bande (une seule boîte, des ombres). +const h = hasard(3); +const poussiere = Array.from( + { length: 110 }, + () => `${h.entre(-62, 62)}vw ${h.normale(4.5).toFixed(1)}vh 0 ${h.parmi([0, 0, 0.4, 0.8])}px rgb(255 255 255 / ${h.entre(0.25, 0.8, 2)})`, +).join(', '); +const filantes = [ + { x: '12%', y: '9%', dur: '11s', delay: '-3s' }, + { x: '34%', y: '17%', dur: '16s', delay: '-9s' }, + { x: '58%', y: '6%', dur: '21s', delay: '-15s' }, +]; +--- + + diff --git a/src/components/habillages/Etoiles.astro b/src/components/habillages/Etoiles.astro new file mode 100644 index 0000000..cac7545 --- /dev/null +++ b/src/components/habillages/Etoiles.astro @@ -0,0 +1,30 @@ +--- +// Étoiles qui scintillent, pour les versions nuit des habillages. Au-delà de `larges`, elles ne +// s'affichent que sur grand écran ; `hauteur` : jusqu'où elles descendent (en % de l'écran). +import { hasard, vars } from '../../lib/decor'; + +interface Props { + nombre: number; + larges: number; + hauteur: number; + graine: number; +} +const { nombre, larges, hauteur, graine } = Astro.props; +const h = hasard(graine); +const etoiles = Array.from({ length: nombre }, () => ({ + x: h.entre(3, 97), + y: h.entre(3, hauteur), + taille: h.parmi([1.5, 2, 2.5]), + dur: h.entre(3, 6.5), + delay: h.entre(-6, 0), +})); +--- + +{ + etoiles.map((e, i) => ( + = larges }]} + style={`left: ${e.x}%; top: ${e.y}%; width: ${e.taille}px; ${vars({ dur: `${e.dur}s`, delay: `${e.delay}s` })}`} + /> + )) +} diff --git a/src/components/habillages/FeteMusique.astro b/src/components/habillages/FeteMusique.astro new file mode 100644 index 0000000..acc7a26 --- /dev/null +++ b/src/components/habillages/FeteMusique.astro @@ -0,0 +1,40 @@ +--- +// Décor Fête de la musique (voir THEMES.md), derrière le contenu et caché hors période. +// Jour : notes qui s'envolent, égaliseur en bas. Nuit : les mêmes en néon, et deux projecteurs +// qui balaient la scène. +import { hasard, vars } from '../../lib/decor'; + +const h = hasard(14); +const notes = () => + Array.from({ length: 10 }, (_, i) => ({ + signe: '♪♫♬♩'[i % 4], + large: i >= 6, + style: vars({ + x: `${h.entre(3, 95)}%`, + top: `${h.entre(15, 80)}%`, + s: `${h.entre(1.1, 2)}rem`, + dur: `${h.entre(12, 19)}s`, + delay: `${h.entre(-19, 0)}s`, + drift: `${h.entre(-40, 40, 0)}px`, + }), + })); +const barres = Array.from({ length: 36 }, (_, i) => ({ + large: i % 2 === 1, + style: vars({ h: `${h.entre(35, 100, 0)}%`, dur: `${h.entre(0.45, 1.1, 2)}s`, delay: `${h.entre(-1, 0, 2)}s` }), +})); +const jour = notes(); +const nuit = notes(); +--- + + diff --git a/src/components/habillages/Flocons.astro b/src/components/habillages/Flocons.astro new file mode 100644 index 0000000..9a2c916 --- /dev/null +++ b/src/components/habillages/Flocons.astro @@ -0,0 +1,30 @@ +--- +// Neige qui tombe (Hiver, Noël) ; la couleur des flocons (--flake) vient de l'habillage. Au-delà +// de `larges`, ils ne s'affichent que sur grand écran. +import { hasard, vars } from '../../lib/decor'; + +interface Props { + nombre: number; + larges: number; + graine: number; +} +const { nombre, larges, graine } = Astro.props; +const h = hasard(graine); +const flocons = Array.from({ length: nombre }, () => ({ + x: h.entre(2, 98), + top: h.entre(0, 95), + s: h.parmi([3, 4, 4, 5, 6]), + dur: h.entre(13, 24), + delay: h.entre(-24, 0), + drift: h.entre(-24, 24, 0), +})); +--- + +{ + flocons.map((f, i) => ( + = larges }]} + style={vars({ x: `${f.x}%`, top: `${f.top}vh`, s: `${f.s}px`, dur: `${f.dur}s`, delay: `${f.delay}s`, drift: `${f.drift}px` })} + /> + )) +} diff --git a/src/components/habillages/Hiver.astro b/src/components/habillages/Hiver.astro new file mode 100644 index 0000000..f9296cf --- /dev/null +++ b/src/components/habillages/Hiver.astro @@ -0,0 +1,25 @@ +--- +// Décor Hiver (voir THEMES.md), derrière le contenu et caché hors saison. +// Jour : neige et congères. Nuit : aurore boréale, étoiles, neige et congères. +import Etoiles from './Etoiles.astro'; +import Flocons from './Flocons.astro'; +--- + + diff --git a/src/components/habillages/Noel.astro b/src/components/habillages/Noel.astro new file mode 100644 index 0000000..7e36b85 --- /dev/null +++ b/src/components/habillages/Noel.astro @@ -0,0 +1,16 @@ +--- +// Décor Noël (voir THEMES.md), derrière le contenu et caché hors période. +// Jour : neige qui tombe sur un fond rouge et vert. Nuit : neige et ciel étoilé. +import Etoiles from './Etoiles.astro'; +import Flocons from './Flocons.astro'; +--- + + diff --git a/src/components/habillages/NouvelAn.astro b/src/components/habillages/NouvelAn.astro new file mode 100644 index 0000000..2dfabb1 --- /dev/null +++ b/src/components/habillages/NouvelAn.astro @@ -0,0 +1,53 @@ +--- +// Décor Nouvel An (voir THEMES.md), derrière le contenu et caché hors période. +// Jour : confettis qui tombent et ballons dorés aux chiffres de la nouvelle année (posés au build, +// puis tenus à jour par src/scripts/ui.ts). Nuit : étoiles et feux d'artifice (canvas, ui.ts). +import Etoiles from './Etoiles.astro'; +import { hasard, vars } from '../../lib/decor'; +import { anneeQuiCommence } from '../../lib/dates'; + +const h = hasard(12); +const couleurs = ['#EAB308', '#CBD5E1', '#F472B6', '#60A5FA', '#34D399']; +const confettis = Array.from({ length: 18 }, (_, i) => ({ + rond: i % 4 === 3, + large: i >= 10, + style: vars({ + x: `${h.entre(2, 98)}%`, + top: `${h.entre(0, 95)}vh`, + dur: `${h.entre(14, 22)}s`, + delay: `${h.entre(-22, 0)}s`, + drift: `${h.entre(-50, 50, 0)}px`, + spin: `${h.parmi([-1, 1]) * h.entre(160, 320, 0)}deg`, + c: couleurs[i % couleurs.length], + flip: `${h.entre(1.2, 2.6)}s`, + }), +})); +const chiffres = [...String(anneeQuiCommence())].map((c) => ({ c, style: vars({ dur: `${h.entre(2.6, 3.6)}s`, delay: `${h.entre(-3, 0)}s` }) })); +--- + + diff --git a/src/components/habillages/Printemps.astro b/src/components/habillages/Printemps.astro new file mode 100644 index 0000000..7678648 --- /dev/null +++ b/src/components/habillages/Printemps.astro @@ -0,0 +1,35 @@ +--- +// Décor Printemps (voir THEMES.md), derrière le contenu et caché hors saison. +// Jour : pétales de cerisier qui volent. Nuit : lucioles qui clignotent et quelques étoiles. +import Etoiles from './Etoiles.astro'; +import { hasard, vars } from '../../lib/decor'; + +const h = hasard(1); +const petales = Array.from({ length: 8 }, () => ({ + x: h.entre(2, 85), + top: h.entre(5, 90), + s: h.entre(8, 13, 0), + dur: h.entre(19, 27), + delay: h.entre(-27, 0), + drift: h.entre(40, 90, 0), + spin: h.parmi([-1, 1]) * h.entre(200, 320, 0), +})); +const lucioles = Array.from({ length: 12 }, () => ({ x: h.entre(4, 96), y: h.entre(45, 92), dur: h.entre(10, 16), delay: h.entre(-10, 0) })); +--- + + diff --git a/src/data/themes.json b/src/data/themes.json index 7ae8468..37f84e4 100644 --- a/src/data/themes.json +++ b/src/data/themes.json @@ -1,15 +1,19 @@ { "forcer": "", - "themes": [ - { - "id": "halloween", - "nom": "Halloween", - "actif": true, - "debut": "10-01", - "fin": "10-31", - "accentClair": "#C2410C", - "accentSombre": "#FB923C" - } + "speciaux": [], + + "recurrents": [ + { "id": "halloween", "nom": "Halloween", "actif": true, "debut": "10-01", "fin": "10-31", "accentClair": "#C2410C", "accentSombre": "#FB923C" }, + { "id": "noel", "nom": "Noël", "actif": true, "debut": "12-01", "fin": "12-26", "accentClair": "#B91C1C", "accentSombre": "#FCA5A5" }, + { "id": "nouvel-an", "nom": "Nouvel An", "actif": true, "debut": "12-31", "fin": "01-02", "accentClair": "#A16207", "accentSombre": "#FACC15" }, + { "id": "fete-musique", "nom": "Fête de la musique", "actif": true, "debut": "06-21", "fin": "06-21", "accentClair": "#7C3AED", "accentSombre": "#C4B5FD" } + ], + + "saisons": [ + { "id": "printemps", "nom": "Printemps", "actif": true, "debut": "03-20", "fin": "06-20", "accentClair": "#2E7D46", "accentSombre": "#86D19C" }, + { "id": "ete", "nom": "Été", "actif": true, "debut": "06-21", "fin": "09-22", "accentClair": "#0E7490", "accentSombre": "#5EC8D8" }, + { "id": "automne", "nom": "Automne", "actif": true, "debut": "09-23", "fin": "12-20", "accentClair": "#9F1239", "accentSombre": "#F08BA0" }, + { "id": "hiver", "nom": "Hiver", "actif": true, "debut": "12-21", "fin": "03-19", "accentClair": "#0369A1", "accentSombre": "#7DD3FC" } ] } diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro index 477cc35..2c23ecc 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -4,7 +4,14 @@ import Icons from '../components/Icons.astro'; import Icon from '../components/Icon.astro'; import Brand from '../components/Brand.astro'; import Drapeau from '../components/Drapeau.astro'; +import Printemps from '../components/habillages/Printemps.astro'; +import Ete from '../components/habillages/Ete.astro'; +import Automne from '../components/habillages/Automne.astro'; +import Hiver from '../components/habillages/Hiver.astro'; import Halloween from '../components/habillages/Halloween.astro'; +import Noel from '../components/habillages/Noel.astro'; +import NouvelAn from '../components/habillages/NouvelAn.astro'; +import FeteMusique from '../components/habillages/FeteMusique.astro'; import { url, asset } from '../lib/paths'; import { reglages, themes } from '../lib/reglages'; import { LANGUES, cheminSansLangue, langueDe, type Langue } from '../lib/i18n'; @@ -22,8 +29,14 @@ const canonical = new URL(Astro.url.pathname, Astro.site); // La même page dans chaque langue (sélecteur de langue et liens hreflang). const chemin = cheminSansLangue(Astro.url); const versions = (Object.keys(LANGUES) as Langue[]).map((code) => ({ code, ...LANGUES[code], href: url(chemin, code) })); -// Thèmes d'habillage (src/data/themes.json, voir THEMES.md), choisis dans le navigateur d'après la date. -const habillages = { forcer: themes.forcer, themes: themes.themes.map(({ nom, ...t }) => t) }; +// Thèmes d'habillage (src/data/themes.json, voir THEMES.md), choisis dans le navigateur d'après la date, +// par ordre de priorité : spéciaux, puis récurrents, puis saisons. +const sansNom = ({ nom, ...t }: T) => t; +const habillages = { + forcer: themes.forcer, + speciaux: themes.speciaux.map(sansNom), + annuels: [...themes.recurrents, ...themes.saisons].map((t) => ({ ...sansNom(t), habillage: t.id })), +}; const accents = `--accent-light: ${site.couleurs.accentClair}; --accent-dark: ${site.couleurs.accentSombre};`; --- @@ -58,8 +71,9 @@ const accents = `--accent-light: ${site.couleurs.accentClair}; --accent-dark: ${ } catch {} - {/* Thème d'habillage du moment, posé avant le premier rendu. `?theme=` (ou `?theme=aucun`) - dans l'adresse permet de le prévisualiser à toute date. */} + {/* Thème d'habillage du moment, posé avant le premier rendu : le premier thème actif en période + parmi les spéciaux (dates AAAA-MM-JJ), puis les récurrents et les saisons (MM-JJ, chaque année). + `?theme=` (ou `?theme=aucun`) dans l'adresse permet d'en prévisualiser un à toute date. */}