diff --git a/README.md b/README.md index fb694c1..3d727c1 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ Tout ce qui s'affiche vient de ces fichiers. On n'a jamais besoin de toucher au | `src/data/certifications.json` | Licences et 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) | 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. @@ -72,6 +74,22 @@ unique. L'ordre se calcule tout seul, du plus récent au plus ancien. 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. + +### Langues + +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. ### Dates @@ -121,9 +139,10 @@ qui désigne le fichier et le champ. à 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 est gardé sur - l'appareil. Sans choix, le site suit le réglage du système. +- 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 @@ -132,11 +151,14 @@ src/ ├── data/ # réglages et contenu (JSON) ├── content/projets/ # un .md par projet ├── content.config.ts # vérification du contenu -├── lib/reglages.ts # vérification de site.json et profil.json +├── 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 -├── pages/ # accueil, page de projet, 404 +├── 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 ``` diff --git a/THEMES.md b/THEMES.md new file mode 100644 index 0000000..7566cea --- /dev/null +++ b/THEMES.md @@ -0,0 +1,150 @@ +# Thèmes du site + +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` | + +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 + +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. + +## Halloween + +Actif **chaque année du 1er octobre à 0 h au 31 octobre à 23 h 59**. Sobre, pour rester pro. + +Dans les deux versions : + +- 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. + +| | 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 | + +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. + +## Les réglages : `src/data/themes.json` + +```json +{ + "forcer": "", + + "themes": [ + { + "id": "halloween", + "nom": "Halloween", + "actif": true, + "debut": "10-01", + "fin": "10-31", + "accentClair": "#C2410C", + "accentSombre": "#FB923C" + } + ] +} +``` + +| 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. | +| `nom` | Son nom, pour s'y retrouver. | +| `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. | +| `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. + +### Exemples + +**Désactiver Halloween** : + +```json +"actif": false +``` + +**Changer la période** (ex. 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. + +**Désactiver tous les thèmes d'un coup**, sans toucher à chacun : + +```json +"forcer": "aucun" +``` + +**Garder Halloween en ligne hors saison** (ex. pour une démonstration) : + +```json +"forcer": "halloween" +``` + +## 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. + +## 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. + +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. + +## Ajouter un thème (pour le code) + +Un thème d'habillage, c'est quatre morceaux. Exemple pour un thème `noel` : + +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"`. + +Si deux thèmes actifs se chevauchent, le premier de la liste l'emporte. + +## 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/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/public/img/orelink-400.jpg b/public/img/orelink-400.jpg index 90644e2..bad97c4 100644 Binary files a/public/img/orelink-400.jpg and b/public/img/orelink-400.jpg differ diff --git a/public/img/orelink.png b/public/img/orelink.png deleted file mode 100644 index 0a7ad94..0000000 Binary files a/public/img/orelink.png and /dev/null differ diff --git a/public/logos/assomiagebordeaux.png b/public/logos/assomiagebordeaux.png new file mode 100644 index 0000000..e3d2b86 Binary files /dev/null and b/public/logos/assomiagebordeaux.png differ diff --git a/public/logos/lassoif.png b/public/logos/lassoif.png new file mode 100644 index 0000000..aab93f5 Binary files /dev/null and b/public/logos/lassoif.png differ diff --git a/public/logos/maydayfestival.png b/public/logos/maydayfestival.png new file mode 100644 index 0000000..4b60c0c Binary files /dev/null and b/public/logos/maydayfestival.png differ diff --git a/public/logos/miageconnection.png b/public/logos/miageconnection.png new file mode 100644 index 0000000..e228045 Binary files /dev/null and b/public/logos/miageconnection.png differ diff --git a/public/logos/miagistesalumnibordelais.png b/public/logos/miagistesalumnibordelais.png new file mode 100644 index 0000000..1c0b15d Binary files /dev/null and b/public/logos/miagistesalumnibordelais.png differ diff --git a/src/components/Brand.astro b/src/components/Brand.astro index 2a50d6f..cfb7660 100644 --- a/src/components/Brand.astro +++ b/src/components/Brand.astro @@ -1,12 +1,13 @@ --- // Le logo « ore_link » en snake_case : le séparateur en couleur d'accent (textes dans site.json → logo). -import { site } from '../lib/reglages'; +import { reglages } from '../lib/reglages'; +import { langueDe } from '../lib/i18n'; interface Props { size?: 'lg' | 'sm'; } const { size = 'sm' } = Astro.props; -const { avant, separateur, apres } = site.logo; +const { avant, separateur, apres } = reglages(langueDe(Astro.url)).site.logo; --- diff --git a/src/components/Description.astro b/src/components/Description.astro index 9a8f958..0aa8828 100644 --- a/src/components/Description.astro +++ b/src/components/Description.astro @@ -2,12 +2,14 @@ // Description d'une tuile, repliée à 3 lignes (la dernière s'estompe) avec « Voir plus » / « Voir moins ». // Une ligne vide sépare deux paragraphes. Sans JavaScript, le texte s'affiche en entier ; le bouton // n'apparaît que si le texte dépasse. -import { t } from '../lib/reglages'; +import { reglages } from '../lib/reglages'; +import { langueDe } from '../lib/i18n'; interface Props { text: string; } const { text } = Astro.props; +const { t } = reglages(langueDe(Astro.url)); const paragraphes = text.split(/\n\s*\n/).map((p) => p.trim()).filter(Boolean); --- diff --git a/src/components/Drapeau.astro b/src/components/Drapeau.astro new file mode 100644 index 0000000..e6619dc --- /dev/null +++ b/src/components/Drapeau.astro @@ -0,0 +1,34 @@ +--- +// Drapeau d'une langue, dessiné en SVG (les émojis drapeaux ne s'affichent pas sous Windows). +// Tous sont cadrés en 3:2 ; le drapeau britannique, en 2:1, est recadré au centre. +interface Props { + code: 'fr' | 'en' | 'es'; +} +const { code } = Astro.props; +// Un drapeau peut paraître plusieurs fois sur une page : identifiant propre à chaque exemplaire. +const clip = `drapeau-en-${Math.random().toString(36).slice(2, 8)}`; +--- + +{code === 'fr' && ( + +)} +{code === 'en' && ( + +)} +{code === 'es' && ( + +)} diff --git a/src/components/Duree.astro b/src/components/Duree.astro index c7be0aa..2036d05 100644 --- a/src/components/Duree.astro +++ b/src/components/Duree.astro @@ -2,6 +2,7 @@ // Durée « 2 ans 8 mois », calculée au build et tenue à jour dans le navigateur pour ce qui est en // cours (src/scripts/ui.ts), sans avoir à republier le site chaque mois. import { duree } from '../lib/dates'; +import { langueDe } from '../lib/i18n'; interface Props { debut: string; @@ -10,4 +11,4 @@ interface Props { const { debut, fin } = Astro.props; --- -{duree(debut, fin)} +{duree(debut, fin, langueDe(Astro.url))} diff --git a/src/components/Icons.astro b/src/components/Icons.astro index ca7cd2d..66387b4 100644 --- a/src/components/Icons.astro +++ b/src/components/Icons.astro @@ -7,6 +7,7 @@ + diff --git a/src/components/ProjetCard.astro b/src/components/ProjetCard.astro index d24ed76..68d7aa8 100644 --- a/src/components/ProjetCard.astro +++ b/src/components/ProjetCard.astro @@ -4,16 +4,19 @@ import type { CollectionEntry } from 'astro:content'; import Icon from './Icon.astro'; import Tags from './Tags.astro'; -import { STATUTS } from '../lib/projets'; +import { statuts } from '../lib/projets'; import { url } from '../lib/paths'; -import { t } from '../lib/reglages'; +import { reglages } from '../lib/reglages'; +import { langueDe } from '../lib/i18n'; interface Props { projet: CollectionEntry<'projets'>; } const { projet } = Astro.props; const p = projet.data; -const statut = STATUTS[p.statut]; +const langue = langueDe(Astro.url); +const { t } = reglages(langue); +const statut = statuts(t)[p.statut]; ---
@@ -25,7 +28,7 @@ const statut = STATUTS[p.statut];

{p.resume}

- + {t.commentJeLaiFait} : {p.titre} diff --git a/src/components/Repli.astro b/src/components/Repli.astro new file mode 100644 index 0000000..f21ce32 --- /dev/null +++ b/src/components/Repli.astro @@ -0,0 +1,21 @@ +--- +// Liste d'une section, repliée à une hauteur maximale (hauteurSectionRepliee dans site.json) : le bas +// s'estompe, avec « Tout afficher (n) » / « Voir moins ». Sans JavaScript, tout s'affiche ; le bouton +// n'apparaît que si la liste dépasse nettement. +import { reglages } from '../lib/reglages'; +import { langueDe } from '../lib/i18n'; + +interface Props { + nombre: number; +} +const { nombre } = Astro.props; +const { site, t } = reglages(langueDe(Astro.url)); +const toutAfficher = `${t.toutAfficher} (${nombre})`; +--- + +
+
+ +
+ +
diff --git a/src/components/Tags.astro b/src/components/Tags.astro index 8620464..f6dabf8 100644 --- a/src/components/Tags.astro +++ b/src/components/Tags.astro @@ -1,9 +1,12 @@ --- +import { reglages } from '../lib/reglages'; +import { langueDe } from '../lib/i18n'; + interface Props { items: string[]; label?: string; } -const { items, label = 'Compétences' } = Astro.props; +const { items, label = reglages(langueDe(Astro.url)).t.competences } = Astro.props; --- { diff --git a/src/components/habillages/Halloween.astro b/src/components/habillages/Halloween.astro new file mode 100644 index 0000000..e14e0c8 --- /dev/null +++ b/src/components/habillages/Halloween.astro @@ -0,0 +1,76 @@ +--- +// Décor de l'habillage Halloween (voir THEMES.md), derrière le contenu et caché hors saison. +// Version jour (apparence claire) : toile d'araignée et son araignée qui monte et descend, feuilles +// d'automne qui tombent. Version nuit (apparence sombre) : lune, étoiles qui scintillent et +// chauves-souris, dont une qui traverse le ciel de temps en temps. + +// Feuilles : position de départ (--x, --top), puis chute de --dur secondes, décalée de --delay. +const feuilles = [ + { x: 8, top: 30, taille: 30, duree: 19, delai: -4, derive: 60, rotation: 300, couleur: '#C2410C' }, + { x: 27, top: 62, taille: 24, duree: 23, delai: -15, derive: -45, rotation: -260, couleur: '#B45309' }, + { x: 48, top: 18, taille: 27, duree: 21, delai: -9, derive: 50, rotation: 340, couleur: '#9A3412' }, + { x: 66, top: 74, taille: 22, duree: 25, delai: -2, derive: -60, rotation: -320, couleur: '#D97706' }, + { x: 81, top: 40, taille: 28, duree: 20, delai: -12, derive: 40, rotation: 280, couleur: '#C2410C' }, + { x: 93, top: 85, taille: 23, duree: 24, delai: -19, derive: -35, rotation: -300, couleur: '#B45309' }, +]; + +// Étoiles : position (en % de l'écran), taille et rythme de scintillement. +const etoiles = [ + { x: 12, y: 9, taille: 2, duree: 3.8 }, + { x: 31, y: 16, taille: 1.5, duree: 5.2 }, + { x: 46, y: 6, taille: 2, duree: 4.4 }, + { x: 58, y: 24, taille: 1.5, duree: 6 }, + { x: 71, y: 11, taille: 2.5, duree: 3.4 }, + { x: 88, y: 31, taille: 1.5, duree: 4.8 }, + { x: 22, y: 34, taille: 1.5, duree: 5.6 }, +]; +--- + + diff --git a/src/components/sections/Benevolat.astro b/src/components/sections/Benevolat.astro index 10320f4..212dd72 100644 --- a/src/components/sections/Benevolat.astro +++ b/src/components/sections/Benevolat.astro @@ -1,42 +1,47 @@ --- // Bénévolat : une tuile par engagement. -import { getCollection } from 'astro:content'; import SectionHead from '../SectionHead.astro'; +import Repli from '../Repli.astro'; import Description from '../Description.astro'; import OrgMark from '../OrgMark.astro'; import Duree from '../Duree.astro'; import { periode, plusRecentsDabord } from '../../lib/dates'; +import { liste } from '../../lib/contenu'; +import { langueDe } from '../../lib/i18n'; interface Props { titre: string; } const { titre } = Astro.props; -const benevolat = plusRecentsDabord((await getCollection('benevolat')).map((e) => e.data), (b) => b.debut); +const langue = langueDe(Astro.url); +const benevolat = plusRecentsDabord(await liste('benevolat', langue), (b) => b.debut); --- { benevolat.length > 0 && (
-
    - {benevolat.map((b) => ( -
  1. - -
    -
    -

    {b.role}

    - {periode(b.debut, b.fin)} + +
      + {benevolat.map((b) => ( +
    1. + +
      +
      +

      {b.role}

      + {periode(b.debut, b.fin, langue)} +
      +

      {b.organisation}

      + + {b.description && }
      -

      {b.organisation}

      - - {b.description && } -
    -
  2. - ))} -
+ + ))} + +
) } diff --git a/src/components/sections/Certifications.astro b/src/components/sections/Certifications.astro index 0b6291e..b109d73 100644 --- a/src/components/sections/Certifications.astro +++ b/src/components/sections/Certifications.astro @@ -1,18 +1,22 @@ --- // Licences et certifications : une tuile par certification. -import { getCollection } from 'astro:content'; import SectionHead from '../SectionHead.astro'; +import Repli from '../Repli.astro'; import OrgMark from '../OrgMark.astro'; import Tags from '../Tags.astro'; import Icon from '../Icon.astro'; import { dateLisible, ordre, plusRecentsDabord } from '../../lib/dates'; -import { t } from '../../lib/reglages'; +import { reglages } from '../../lib/reglages'; +import { liste } from '../../lib/contenu'; +import { langueDe } from '../../lib/i18n'; interface Props { titre: string; } const { titre } = Astro.props; -const certifications = plusRecentsDabord((await getCollection('certifications')).map((e) => e.data), (c) => c.obtention); +const langue = langueDe(Astro.url); +const { t } = reglages(langue); +const certifications = plusRecentsDabord(await liste('certifications', langue), (c) => c.obtention); const aujourdhui = new Date().toISOString().slice(0, 7); --- @@ -20,31 +24,33 @@ const aujourdhui = new Date().toISOString().slice(0, 7); certifications.length > 0 && (
-
    - {certifications.map((c) => ( -
  1. - -
    -
    -

    {c.nom}

    - {dateLisible(c.obtention)} -
    -

    {c.organisme}

    - - {c.identifiant && } - - {c.url && ( -
    - {t.afficherCertification} + +
      + {certifications.map((c) => ( +
    1. + +
      +
      +

      {c.nom}

      + {dateLisible(c.obtention, langue)}
      - )} -
      -
    2. - ))} -
    +

    {c.organisme}

    + + {c.identifiant && } + + {c.url && ( +
    + {t.afficherCertification} +
    + )} +
    +
  2. + ))} +
+
) } diff --git a/src/components/sections/Experience.astro b/src/components/sections/Experience.astro index b7ed458..0d711ea 100644 --- a/src/components/sections/Experience.astro +++ b/src/components/sections/Experience.astro @@ -1,22 +1,25 @@ --- // Expérience : une tuile par entreprise ; plusieurs postes dans la même entreprise forment une frise. -import { getCollection } from 'astro:content'; import SectionHead from '../SectionHead.astro'; +import Repli from '../Repli.astro'; import Description from '../Description.astro'; import OrgMark from '../OrgMark.astro'; import Duree from '../Duree.astro'; import Tags from '../Tags.astro'; import { periode, plusRecentsDabord } from '../../lib/dates'; +import { liste } from '../../lib/contenu'; +import { langueDe } from '../../lib/i18n'; interface Props { titre: string; } const { titre } = Astro.props; +const langue = langueDe(Astro.url); const experiences = plusRecentsDabord( - (await getCollection('experiences')).map((e) => ({ - ...e.data, - postes: plusRecentsDabord(e.data.postes, (p) => p.debut), + (await liste('experiences', langue)).map((e) => ({ + ...e, + postes: plusRecentsDabord(e.postes, (p) => p.debut), })), (e) => e.postes[0].debut, ); @@ -27,59 +30,61 @@ const lieu = (p: { lieu?: string; mode?: string }) => [p.lieu, p.mode].filter(Bo experiences.length > 0 && (
-
    - {experiences.map((e) => { - const p0 = e.postes[0]; - const debut = e.postes.at(-1)!.debut; - const fin = e.postes.some((p) => p.fin === null) ? null : plusRecentsDabord(e.postes, (p) => p.fin)[0].fin; - return ( -
  1. - - {e.postes.length === 1 ? ( -
    -
    -

    {p0.titre}

    - {periode(p0.debut, p0.fin)} + +
      + {experiences.map((e) => { + const p0 = e.postes[0]; + const debut = e.postes.at(-1)!.debut; + const fin = e.postes.some((p) => p.fin === null) ? null : plusRecentsDabord(e.postes, (p) => p.fin)[0].fin; + return ( +
    1. + + {e.postes.length === 1 ? ( +
      +
      +

      {p0.titre}

      + {periode(p0.debut, p0.fin, langue)} +
      +

      {[e.entreprise, p0.contrat].filter(Boolean).join(' · ')}

      + + {p0.description && } +
      -

      {[e.entreprise, p0.contrat].filter(Boolean).join(' · ')}

      - - {p0.description && } - -
    - ) : ( -
    -
    -

    {e.entreprise}

    - -
    -
      - {e.postes.map((p) => ( -
    1. -
      -
      -

      {p.titre}

      - {periode(p.debut, p.fin)} + ) : ( +
      +
      +

      {e.entreprise}

      + +
      +
        + {e.postes.map((p) => ( +
      1. +
        +
        +

        {p.titre}

        + {periode(p.debut, p.fin, langue)} +
        + + {p.description && } +
        - - {p.description && } - -
      -
    2. - ))} -
    -
    - )} -
  2. - ); - })} -
+ + ))} + +
+ )} + + ); + })} + + ) } diff --git a/src/components/sections/Formation.astro b/src/components/sections/Formation.astro index 0339d51..b1c7fa5 100644 --- a/src/components/sections/Formation.astro +++ b/src/components/sections/Formation.astro @@ -1,42 +1,48 @@ --- // Formation : une tuile par diplôme. -import { getCollection } from 'astro:content'; import SectionHead from '../SectionHead.astro'; +import Repli from '../Repli.astro'; import Description from '../Description.astro'; import OrgMark from '../OrgMark.astro'; import Tags from '../Tags.astro'; import { periode, plusRecentsDabord } from '../../lib/dates'; -import { t } from '../../lib/reglages'; +import { reglages } from '../../lib/reglages'; +import { liste } from '../../lib/contenu'; +import { langueDe } from '../../lib/i18n'; interface Props { titre: string; } const { titre } = Astro.props; -const formations = plusRecentsDabord((await getCollection('formations')).map((e) => e.data), (f) => f.fin ?? f.debut); +const langue = langueDe(Astro.url); +const { t } = reglages(langue); +const formations = plusRecentsDabord(await liste('formations', langue), (f) => f.fin ?? f.debut); --- { formations.length > 0 && (
-
    - {formations.map((f) => ( -
  1. - -
    -
    -

    {f.etablissement}

    - {(f.debut || f.fin) && {periode(f.debut, f.fin)}} + +
      + {formations.map((f) => ( +
    1. + +
      +
      +

      {f.etablissement}

      + {(f.debut || f.fin) && {periode(f.debut, f.fin, langue)}} +
      +

      {[f.diplome, f.domaine].filter(Boolean).join(', ')}

      + {f.note && } + {f.activites && } + {f.description && } +
      -

      {[f.diplome, f.domaine].filter(Boolean).join(', ')}

      - {f.note && } - {f.activites && } - {f.description && } - -
    -
  2. - ))} -
+ + ))} + +
) } diff --git a/src/components/sections/Projets.astro b/src/components/sections/Projets.astro index 95c9cad..185e76e 100644 --- a/src/components/sections/Projets.astro +++ b/src/components/sections/Projets.astro @@ -2,6 +2,7 @@ // Projets : une tuile par projet, en grille sur grand écran. import { getCollection } from 'astro:content'; import SectionHead from '../SectionHead.astro'; +import Repli from '../Repli.astro'; import ProjetCard from '../ProjetCard.astro'; import { trierProjets } from '../../lib/projets'; @@ -16,13 +17,15 @@ const projets = trierProjets(await getCollection('projets')); projets.length > 0 && (
-
    - {projets.map((p) => ( -
  • - -
  • - ))} -
+ +
    + {projets.map((p) => ( +
  • + +
  • + ))} +
+
) } diff --git a/src/data/benevolat.json b/src/data/benevolat.json index 6c9752b..06277d1 100644 --- a/src/data/benevolat.json +++ b/src/data/benevolat.json @@ -1,11 +1,102 @@ [ { - "id": "association-exemple", - "role": "Bénévole data", - "organisation": "Association Exemple", - "cause": "Environnement", - "debut": "2023-01", - "fin": null, - "description": "Nettoyage et structuration de données pour une ONG, puis tableau de bord de suivi d'impact." + "id": "mab-membre-actif", + "role": "Membre actif", + "organisation": "Miagistes Alumni Bordelais", + "logo": "logos/miagistesalumnibordelais.png", + "cause": "Arts et culture", + "debut": "2024-10", + "fin": "2026-09", + "description": "Association des Miagistes Alumni Bordelais depuis 2022\n• Création d'un large éventail de supports visuels pour l'association (publications et stories pour les réseaux sociaux, photos, brochures, modèles d'e-mails, PDF) sur plusieurs canaux (Facebook, Instagram, LinkedIn, Linktree)\n• Organisation et promotion d'afterworks de networking à Bordeaux, réunissant 30 à 60 participants par événement" + }, + { + "id": "mab-vp-communication", + "role": "Vice-président Communication", + "organisation": "Miagistes Alumni Bordelais", + "logo": "logos/miagistesalumnibordelais.png", + "cause": "Arts et culture", + "debut": "2022-10", + "fin": "2024-09", + "description": "Association des Miagistes Alumni Bordelais depuis 2022\n• Cofondation de l'association en 2022 pour renforcer la communauté des anciens de la MIAGE de Bordeaux\n• Conception de l'identité visuelle de l'association et élaboration de chartes graphiques annuelles et propres à chaque événement\n• Réalisation de plus de 150 supports de communication (publications et stories pour les réseaux sociaux, photos, brochures, modèles d'e-mails, PDF)\n• Visibilité en ligne accrue grâce à une croissance régulière de la communauté (+250 abonnés Instagram, +200 abonnés LinkedIn la première année)\n• Planification et mise en œuvre de stratégies de communication annuelles, avec des campagnes adaptées à chaque événement\n• Organisation et promotion de plus de 20 afterworks de networking à Bordeaux, réunissant 30 à 60 participants par événement" + }, + { + "id": "amb-membre-actif", + "role": "Membre actif", + "organisation": "AMB - Asso MIAGE Bordeaux", + "logo": "logos/assomiagebordeaux.png", + "cause": "Arts et culture", + "debut": "2022-06", + "fin": "2023-05", + "description": "Association des étudiants de la MIAGE de l'Université de Bordeaux depuis 1984\n• Participation à la préparation de divers événements étudiants\n• Contribution à l'organisation et à l'accompagnement sur place pendant les événements" + }, + { + "id": "amb-vice-president", + "role": "Vice-président", + "organisation": "AMB - Asso MIAGE Bordeaux", + "logo": "logos/assomiagebordeaux.png", + "cause": "Arts et culture", + "debut": "2021-06", + "fin": "2022-05", + "description": "Association des étudiants de la MIAGE de l'Université de Bordeaux depuis 1984\n• Contribution à l'organisation de grands événements étudiants, dont des afterworks (~170 participants), le week-end d'intégration (~100 participants) et le week-end ski (~110 participants)\n• Bénévole dans l'équipe d'organisation de la 12e édition du Gala MIAGE Bordeaux aux Bassins des Lumières (~400 participants)\n• Participation à la rédaction de documents de capitalisation pour aider les futurs bureaux à organiser les événements récurrents (soirée d'intégration, WEI, WES, etc.)" + }, + { + "id": "amb-president", + "role": "Président", + "organisation": "AMB - Asso MIAGE Bordeaux", + "logo": "logos/assomiagebordeaux.png", + "cause": "Arts et culture", + "debut": "2020-06", + "fin": "2021-05", + "description": "Association des étudiants de la MIAGE de l'Université de Bordeaux depuis 1984\n• Contribution à l'organisation de grands événements étudiants malgré la pandémie de COVID-19, dont des afterworks (~150 participants) et des rencontres en ligne sur Discord (~50 participants)\n• Maintien d'une forte cohésion entre les promotions MIAGE ainsi qu'avec les générations d'anciens\n• Engagement actif dans la vie étudiante locale et régionale au sein de la fédération territoriale ATENA (assemblées générales, votes, soutien aux initiatives)\n• Soutien à la promotion de la formation MIAGE, de ses traditions et de ses projets au sein de la fédération nationale MIAGE Connection (assemblées générales, votes, initiatives)" + }, + { + "id": "mayday-festival", + "role": "Staff bénévole", + "organisation": "Mayday Festival", + "logo": "logos/maydayfestival.png", + "cause": "Arts et culture", + "debut": "2021-10", + "fin": "2021-10", + "description": "Festival de musique créé en 2016 à Talence, organisé en association depuis 2018.\n• Surveillance et sécurisation du périmètre du festival pour garantir la sécurité des festivaliers\n• Accueil des festivaliers : réponse à leurs questions et orientation sur le site\n• Renfort au bar pendant les heures de pointe pour absorber une forte affluence" + }, + { + "id": "miage-connection-vp-reseau", + "role": "Vice-président Réseau", + "organisation": "MIAGE Connection", + "logo": "logos/miageconnection.png", + "cause": "Droits civiques et action sociale", + "debut": "2021-03", + "fin": "2021-10", + "description": "Fédération nationale des étudiants en MIAGE en France depuis 2005\n• Maintien du lien entre les associations membres (BDE, Junior-Entreprises, associations d'anciens) pendant la crise du Covid-19\n• Collecte et partage des activités des associations membres via la plateforme en ligne MIAGE Weather, favorisant la visibilité et l'échange d'idées et d'actualités sur les événements\n• Facilitation de la collaboration avec le pôle Formation pour accompagner les membres dans leur préparation académique\n• Accompagnement du renouvellement des bureaux étudiants pour assurer la continuité des activités essentielles à la MIAGE" + }, + { + "id": "miage-connection-vp-international", + "role": "Vice-président International", + "organisation": "MIAGE Connection", + "logo": "logos/miageconnection.png", + "cause": "Formation", + "debut": "2021-03", + "fin": "2021-10", + "description": "Fédération nationale des étudiants en MIAGE en France depuis 2005\n• Maintien du lien avec les communautés MIAGE et les réseaux équivalents à l'étranger (Luxembourg, Belgique, Canada, États-Unis, etc.)\n• Rédaction d'un guide d'accompagnement pour aider les étudiants à s'installer à l'étranger : démarches administratives, conseils pratiques, logement et recommandations recueillies auprès d'anciens déjà expatriés" + }, + { + "id": "ub-elu-etudiant", + "role": "Élu étudiant", + "organisation": "Université de Bordeaux", + "logo": "logos/universitedebordeaux.png", + "cause": "Droits civiques et action sociale", + "debut": "2020-12", + "fin": "2022-08", + "description": "• Représentation des étudiants dans les conseils centraux de l'université (notamment le Conseil d'administration et le Conseil académique), avec participation aux décisions pédagogiques, financières et administratives\n• Siège aux conseils de collège et de composante, avec contribution à la conception et à la validation de l'offre de formation, des modalités d'évaluation, des critères d'admission et de la politique de vie étudiante\n• Collaboration avec les vice-présidents de campus sur le bien-être étudiant, la vie de campus et la vie associative, en tant qu'intermédiaire entre les étudiants, l'administration et les associations étudiantes" + }, + { + "id": "abeii-tresorier", + "role": "Trésorier", + "organisation": "L'ABEII", + "logo": "logos/lassoif.png", + "cause": "Services sociaux", + "debut": "2018-04", + "fin": "2019-03", + "description": "Association bordelaise des étudiants en informatique de l'Université de Bordeaux depuis 2012\n• Planification et coordination d'événements étudiants de 50 à plus de 150 participants, avec des budgets allant jusqu'à 3 000 €\n• Supervision du fonctionnement quotidien du foyer étudiant, fréquenté par plus de 200 étudiants\n• Gestion des stocks, des prix et des ventes de snacks et de boissons pour assurer des recettes régulières\n• Élaboration et suivi des budgets pour garantir la pérennité financière de l'association" } ] diff --git a/src/data/en/benevolat.json b/src/data/en/benevolat.json new file mode 100644 index 0000000..15ac13a --- /dev/null +++ b/src/data/en/benevolat.json @@ -0,0 +1,62 @@ +[ + { + "id": "mab-membre-actif", + "role": "Active Member", + "cause": "Arts and Culture", + "description": "Association of Miagistes Alumni Bordelais since 2022\n• Created a wide range of visual assets for the association (social media posts, stories, photos, brochures, email templates, PDFs) across multiple channels (Facebook, Instagram, LinkedIn, Linktree)\n• Organized and promoted networking Afterworks in Bordeaux, bringing together 30–60 participants per event" + }, + { + "id": "mab-vp-communication", + "role": "Vice President Communications", + "cause": "Arts and Culture", + "description": "Association of Miagistes Alumni Bordelais since 2022\n• Co-founded the association in 2022 to strengthen the Bordeaux MIAGE alumni community\n• Designed the association’s visual identity and developed annual and event-specific brand guidelines\n• Produced 150+ communication materials (social media posts, stories, photos, brochures, email templates, PDFs)\n• Increased online visibility with steady community growth (+250 Instagram followers, +200 LinkedIn followers in the first year)\n• Planned and executed annual communication strategies, with tailored campaigns for each event\n• Organized and promoted 20+ networking Afterworks in Bordeaux, bringing together 30–60 participants per event" + }, + { + "id": "amb-membre-actif", + "role": "Active Member", + "cause": "Arts and Culture", + "description": "Association of MIAGE Students at the University of Bordeaux since 1984\n• Assisted in the preparation of various student events\n• Contributed to on-site organization and support during events" + }, + { + "id": "amb-vice-president", + "role": "Vice President", + "cause": "Arts and Culture", + "description": "Association of MIAGE Students at the University of Bordeaux since 1984\n• Contributed to the organization of major student events, including Afterworks (~170 participants), Integration Weekend (~100 participants), and Ski Weekend (~110 participants)\n• Volunteered as a staff member for the 12th edition of the MIAGE Bordeaux Gala at the Bassins des Lumières (~400 participants)\n• Assisted in drafting capitalization documents to support future student boards in organizing recurring events (Integration Night, WEI, WES, etc.)" + }, + { + "id": "amb-president", + "role": "President", + "cause": "Arts and Culture", + "description": "Association of MIAGE Students at the University of Bordeaux since 1984\n• Contributed to the organization of major student events despite the COVID-19 pandemic, including Afterworks (~150 participants) and online Discord gatherings (~50 participants)\n• Helped maintain strong cohesion across MIAGE cohorts as well as with alumni generations\n• Actively engaged in local and regional student life through the territorial federation ATENA (general assemblies, voting, and support of initiatives)\n• Supported the promotion of the MIAGE program, its traditions, and projects through the national federation MIAGE Connection (general assemblies, voting, and initiatives)" + }, + { + "id": "mayday-festival", + "role": "Volunteer Staff", + "cause": "Arts and Culture", + "description": "Music festival created in 2016 in Talence (FR), organized as an association since 2018.\n• Monitored and secured the festival perimeter to ensure attendee safety\n• Assisted festivalgoers by answering questions and providing on-site guidance\n• Supported bar service during peak hours to manage high-volume demand" + }, + { + "id": "miage-connection-vp-reseau", + "role": "Vice President Network", + "cause": "Civil Rights and Social Action", + "description": "National Federation of MIAGE Students in France since 2005\n• Maintained connections between member organizations (student unions, junior enterprises, alumni groups) during the Covid-19 crisis\n• Collected and shared activities from member associations through the online MIAGE Weather platform, enabling visibility and exchange of event ideas and updates\n• Facilitated collaboration with the Training Division to support members’ academic preparation\n• Assisted in the renewal of student boards to ensure continuity of activities essential to the MIAGE program" + }, + { + "id": "miage-connection-vp-international", + "role": "Vice President International", + "cause": "Education", + "description": "National Federation of MIAGE Students in France since 2005\n• Maintained connections with MIAGE communities and equivalent networks abroad (Luxembourg, Belgium, Canada, United States, etc.)\n• Authored a support guide to help students settle abroad, covering legal procedures, practical advice, housing tips, and recommendations collected from alumni already living overseas" + }, + { + "id": "ub-elu-etudiant", + "role": "Student Elected Representative", + "cause": "Civil Rights and Social Action", + "description": "• Represented the student body in central university bodies (such as the Board of Administration and the Academic Council), participating in decision-making on academic, financial, and administrative matters\n• Served on Faculty/Component Councils, contributing to the design and validation of course offerings, evaluation methods, admission criteria, and student life policy\n• Collaborated with Vice-Presidents of Campus to address student welfare, campus life, and associative activities, acting as a liaison between students, administration, and student associations" + }, + { + "id": "abeii-tresorier", + "role": "Treasurer", + "cause": "Social Services", + "description": "Bordeaux Association of IT Students at the University of Bordeaux since 2012\n• Planned and coordinated student events for 50–150+ participants, managing budgets of up to €3K\n• Oversaw daily operations of the student common room serving 200+ students\n• Managed inventory, pricing, and snack/drink sales to ensure consistent revenue flow\n• Created and monitored budgets to secure the association’s financial sustainability" + } +] diff --git a/src/data/en/experiences.json b/src/data/en/experiences.json new file mode 100644 index 0000000..ccbcc04 --- /dev/null +++ b/src/data/en/experiences.json @@ -0,0 +1,76 @@ +[ + { + "id": "betclic-group", + "postes": [ + { + "contrat": "Full-time", + "mode": "Hybrid", + "description": "Data Engineer in a Data team working across sports betting, casino, poker and horse racing.\n\nI work on data modeling, building transformations with dbt and SQL, and delivering data products and dashboards to business teams. I also contribute to the continuous improvement of the Data platform, to reducing technical debt and to getting ready for the challenges of major international sporting events.\nI also use generative AI tools to speed up development, analysis and the use of data.", + "competences": [ + "SQL", + "dbt", + "Snowflake", + "Tableau", + "Omni", + "Generative AI (ChatGPT, Claude, Codex, Cortex, Rovo)" + ] + } + ] + }, + { + "id": "thelio", + "postes": [ + { + "contrat": "Full-time", + "mode": "Hybrid", + "description": "Contributed to several strategic Data Engineering and Data Analytics projects for two leading international groups:\n\nClient – Global leader in animal health\n• Designed dashboards (Salesforce Analytics Studio, Power BI) for sales tracking and data quality: far fewer manual checks and faster response from the teams.\n• Developed audit management solutions in 50+ countries, with automatic translation into 30+ languages and advanced security (RLS).\n• Synchronized and standardized multi-source sales data for 20+ subsidiaries, ensuring a consistent international CRM rollout.\n• Automated and standardized KPIs with Databricks (medallion architecture), cutting manual extractions and saving business teams dozens of hours per month.\n\nClient – Technology leader for the fashion industry:\n• Redesigned a contract management model with incremental pipelines (delta logic, medallion architecture), reducing processing times over time and making the data more reliable.\n• Set up centralized Power BI reporting (star schema, DAX), adopted by operational teams, replacing fragmented Excel files and halving manual reporting effort.\n\nOverall impact: more reliable and available data, lower maintenance costs, faster decision-making (from several days to a few hours), and wider adoption of BI solutions by business teams.\n\nCompany life: actively involved in the company's culture by organizing events and collective initiatives." + }, + { + "contrat": "Apprenticeship", + "description": "Contributed to several strategic projects to modernize and secure data flows for leading organizations in the public sector and tourism.\n\nClient – Major French local authority\n• Set up a unified synchronization framework between two Data Warehouses, improving data consistency and the reliability of BI analyses. Designed scalable flows (Talend, SQL, Oracle) that anticipate structural changes to ensure long-term scalability.\n\nClient – Leading residential tourism group in France\n• Brought in-house and industrialized Talend ETL pipelines, replacing legacy PHP flows, for better control and simpler maintenance.\n\nOverall impact: more reliable and consistent data, lower operating costs, and BI and business teams able to make use of their data faster.\n\nCompany life: actively involved in the company's culture by organizing events and collective initiatives.", + "competences": [ + "Data Engineering", + "Extract, Transform, Load (ETL)" + ] + } + ] + }, + { + "id": "legalyspace", + "postes": [ + { + "titre": "Full Stack Web Developer", + "contrat": "Apprenticeship", + "mode": "On-site", + "description": "Contributed to several strategic full stack development projects for a fast-growing legaltech company in Bordeaux.\n\n• Designed and implemented key features (Java EE, PL/SQL, HTML5, CSS, JavaScript), including a customer tracking page, an internal dashboard and the integration of third-party services (Tradeshift), improving both security and productivity.\n• Built a Java library generating XML files compliant with the UBL 2.1 standard (JAXB), documented and versioned on GitHub, automating the production of tax documents and easing their integration into invoicing systems.\n\nOverall impact: a better customer experience, streamlined internal processes and stronger compliance with international standards, supporting the platform's growth and reliability.", + "competences": [ + "SQL", + "User Interface Design" + ] + } + ] + }, + { + "id": "sopra-steria", + "postes": [ + { + "titre": "Web Application Developer", + "contrat": "Internship", + "mode": "On-site", + "description": "Contributed to a strategic project centralizing accounting tools for a major public body.\n\n• Developed a web application (Java EE, SQL, HTML5, CSS, JavaScript) with new features, including “Organizations” management and session saving, contributing to a unified tool for affiliated practitioners nationwide.\n\nOverall impact: a faster and more reliable monthly closing of accounts thanks to a centralized, high-performance tool." + } + ] + }, + { + "id": "millesima", + "postes": [ + { + "titre": "Web Application Developer", + "contrat": "Internship", + "lieu": "Bordeaux Area, France", + "mode": "On-site", + "description": "Contributed to a strategic full stack development project for an international fine wine event.\n\n• Designed and deployed an interactive web application (Java EE, HTML5, CSS, JavaScript) with a registration form, 360° animations, an admin area, a JSON REST API and GDPR-compliant security, used on touch kiosks and iPads by several hundred attendees at the 2019 Bordeaux Grands Crus Weekend.\n\nOverall impact: a stable, secure and engaging experience that made the client's booth more attractive and boosted brand visibility at a major international event." + } + ] + } +] diff --git a/src/data/en/formations.json b/src/data/en/formations.json new file mode 100644 index 0000000..3c82ea8 --- /dev/null +++ b/src/data/en/formations.json @@ -0,0 +1,36 @@ +[ + { + "id": "master-miage", + "diplome": "Master's degree", + "note": "Grade: Graduated", + "activites": "Student union (Asso MIAGE Bordeaux): President (1 year) then Vice President (1 year); College Council (Peixotto & Bordes campuses): Student elected representative (1 year 9 months).", + "description": "The MIAGE master's degree (Computer Methods Applied to Business Management) trains business executives who are experts in information systems engineering and management.\nThis five-year program combines a dual skill set in digital sciences (programming, mathematical logic…) and management sciences (finance, accounting, marketing, project management…)." + }, + { + "id": "licence-miage", + "diplome": "Bachelor's degree (3rd year), MIAGE track", + "domaine": "Computer Science", + "note": "Grade: Graduated", + "description": "The third year of the bachelor's MIAGE track (Computer Methods Applied to Business Management) combines a dual skill set in digital sciences (programming, mathematical logic…) and management sciences (finance, accounting, marketing, project management…).\nThis three-year program is required to enter a MIAGE master's degree." + }, + { + "id": "dut-info", + "diplome": "DUT (two-year technical degree)", + "note": "Grade: Graduated", + "activites": "Student union (L'ABEII): Treasurer (1 year)", + "description": "The DUT in Computer Science trains general-purpose computer scientists through solid theoretical and practical training." + }, + { + "id": "licence-maths", + "diplome": "Bachelor's degree", + "note": "Grade: First year completed.", + "description": "The bachelor's degree in mathematics gives students solid knowledge of mathematics, covering the basics of algebra, geometry, analysis and probability." + }, + { + "id": "bac-s", + "diplome": "Baccalauréat (high school diploma)", + "domaine": "Science", + "note": "Grade: Graduated", + "description": "Mathematics option." + } +] diff --git a/src/data/en/profil.json b/src/data/en/profil.json new file mode 100644 index 0000000..8371a9a --- /dev/null +++ b/src/data/en/profil.json @@ -0,0 +1,8 @@ +{ + "langues": [ + { "nom": "French", "niveau": "Native" }, + { "nom": "English", "niveau": "Professional working proficiency" }, + { "nom": "Spanish", "niveau": "Elementary proficiency" } + ], + "aPropos": "At Betclic, I make data talk: modeling, dbt and SQL transformations, data products and dashboards for business teams, with a little help from generative AI. On the side, I code a lot for myself: an everyday need, a blocker that bugs me or just an idea worth trying, and it turns into a project. I like trying things, getting it wrong, finding the fix… and starting again." +} diff --git a/src/data/en/site.json b/src/data/en/site.json new file mode 100644 index 0000000..44e11c5 --- /dev/null +++ b/src/data/en/site.json @@ -0,0 +1,57 @@ +{ + "meta": { + "titre": "oreLINK · Aurélien Bertrand, Data Engineer", + "description": "Portfolio of Aurélien Bertrand (oreLINK), Data Engineer at Betclic Group in Bordeaux: experience, projects, education, certifications and volunteering." + }, + + "sections": [ + { "id": "experience", "titre": "Experience" }, + { "id": "projets", "titre": "Projects" }, + { "id": "formation", "titre": "Education" }, + { "id": "certifications", "titre": "Licenses & certifications" }, + { "id": "benevolat", "titre": "Volunteering" } + ], + + "textes": { + "meContacter": "Contact me", + "aPropos": "About", + "activites": "Activities and societies", + "voirPlus": "See more", + "voirMoins": "See less", + "toutAfficher": "Show all", + "delivreeEn": "Issued", + "expireEn": "Expires", + "expireeEn": "Expired", + "identifiant": "Credential ID", + "afficherCertification": "Show credential", + "commentJeLaiFait": "How I built it", + "commentJeLaiRealise": "How I built it", + "voirLeProjet": "View project", + "codeSource": "Source code", + "retour": "Back to portfolio", + "statut": "Status", + "commenceEn": "Started", + "role": "Role", + "technologies": "Technologies", + "sommaire": "Contents", + "precedent": "Previous", + "suivant": "Next", + "statuts": { + "en-ligne": "Live", + "en-cours": "In progress", + "archive": "Archived" + }, + "themeClair": "Switch to light theme", + "themeSombre": "Switch to dark theme", + "allerAuContenu": "Skip to content", + "codeDuSite": "Site source", + "introuvableTitre": "Page not found", + "introuvableTexte": "This page doesn't exist, or no longer does.", + "retourAccueil": "Back to home", + "chez": "at", + "competences": "Skills", + "liensExternes": "External links", + "autresProjets": "Other projects", + "changerDeLangue": "Change language" + } +} diff --git a/src/data/profil.json b/src/data/profil.json index 33e4437..2dce5b7 100644 --- a/src/data/profil.json +++ b/src/data/profil.json @@ -14,11 +14,16 @@ "ton": "none" }, "localisation": "Bordeaux, France", + "langues": [ + { "code": "fr", "nom": "Français", "niveau": "Langue natale" }, + { "code": "en", "nom": "Anglais", "niveau": "Capacité professionnelle" }, + { "code": "es", "nom": "Espagnol", "niveau": "Notions de base" } + ], "email": "orelienbertrand@gmail.com", "liens": { "linkedin": "https://www.linkedin.com/in/aurelienbertrand/", "github": "https://github.com/oreLINK", "cv": "" }, - "aPropos": "Data Engineer avec un pied côté Analytics Engineering : de la source brute jusqu'à des tables propres, testées et documentées. En dehors du travail, je construis des petits sites statiques autour de mes passions : la montagne, les transports, les langues." + "aPropos": "Chez Betclic, je fais parler la donnée : modélisation, transformations dbt et SQL, produits data et tableaux de bord pour les équipes métiers, avec un coup de main de l'IA générative. À côté, je code beaucoup pour moi : un besoin du quotidien, un blocage qui m'agace ou juste une idée à tester, et ça devient un projet. J'aime tenter des choses, me planter, trouver la solution… et recommencer." } diff --git a/src/data/site.json b/src/data/site.json index 3ac03ff..c69cb0d 100644 --- a/src/data/site.json +++ b/src/data/site.json @@ -20,16 +20,18 @@ { "id": "projets", "titre": "Projets", "afficher": false }, { "id": "formation", "titre": "Formation", "afficher": true }, { "id": "certifications", "titre": "Licences et certifications", "afficher": false }, - { "id": "benevolat", "titre": "Bénévolat", "afficher": false } + { "id": "benevolat", "titre": "Bénévolat", "afficher": true } ], + "hauteurSectionRepliee": 500, + "textes": { "meContacter": "Me contacter", "aPropos": "À propos", - "aujourdhui": "aujourd’hui", "activites": "Activités et associations", "voirPlus": "Voir plus", "voirMoins": "Voir moins", + "toutAfficher": "Tout afficher", "delivreeEn": "Délivrée en", "expireEn": "Expire en", "expireeEn": "Expirée en", @@ -58,7 +60,12 @@ "codeDuSite": "Code du site", "introuvableTitre": "Page introuvable", "introuvableTexte": "Cette page n'existe pas, ou plus.", - "retourAccueil": "Retour à l'accueil" + "retourAccueil": "Retour à l'accueil", + "chez": "chez", + "competences": "Compétences", + "liensExternes": "Liens externes", + "autresProjets": "Autres projets", + "changerDeLangue": "Changer de langue" }, "piedDePage": { diff --git a/src/data/themes.json b/src/data/themes.json new file mode 100644 index 0000000..7ae8468 --- /dev/null +++ b/src/data/themes.json @@ -0,0 +1,15 @@ +{ + "forcer": "", + + "themes": [ + { + "id": "halloween", + "nom": "Halloween", + "actif": true, + "debut": "10-01", + "fin": "10-31", + "accentClair": "#C2410C", + "accentSombre": "#FB923C" + } + ] +} diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro index a59e233..6c33b7b 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -3,8 +3,11 @@ import '../styles/global.css'; 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 Halloween from '../components/habillages/Halloween.astro'; import { url, asset } from '../lib/paths'; -import { site, profil, t } from '../lib/reglages'; +import { reglages, themes } from '../lib/reglages'; +import { LANGUES, cheminSansLangue, langueDe, type Langue } from '../lib/i18n'; interface Props { title?: string; @@ -12,20 +15,28 @@ interface Props { /** Lien de retour affiché en haut à gauche (pages de projet). */ back?: { href: string; label: string }; } +const langue = langueDe(Astro.url); +const { site, profil, t } = reglages(langue); const { title = site.meta.titre, description = site.meta.description, back } = Astro.props; 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) }; const annee = new Date().getFullYear(); const accents = `--accent-light: ${site.couleurs.accentClair}; --accent-dark: ${site.couleurs.accentSombre};`; --- - + {title} + {versions.map((v) => )} @@ -37,18 +48,51 @@ const accents = `--accent-light: ${site.couleurs.accentClair}; --accent-dark: ${ - {/* Avant le premier rendu : thème choisi, pour éviter un flash de la mauvaise couleur. */} + {/* Avant le premier rendu : thème choisi pendant la visite, pour éviter un flash de la mauvaise + couleur. Sans choix, le thème est celui du système (voir ui.ts). */} + + {/* 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. */} + - +
@@ -56,24 +100,42 @@ const accents = `--accent-light: ${site.couleurs.accentClair}; --accent-dark: ${ { back && ( ) } - +
+ + + +