docs+fix: nettoyage post-migration et conformité svelte-autofixer
- CONTRIBUTING.md et SEO.md réécrits pour la structure SvelteKit
- README : section documents de référence (charte, recette, AGENTS.md, guides Svelte 5)
- Liens internes via resolve()/asset() de $app/paths, externes en rel="external"
- Seo.svelte : JSON-LD construits dans le script (</script> échappé)
- Titres de sections : {' '} remplacé par  
- Renommage svelte_core_bestpractices.md
- Passe svelte-autofixer : 0 issue restante hors {@html} first-party assumés
This commit is contained in:
+65
-100
@@ -2,11 +2,15 @@
|
||||
|
||||
Merci de votre intérêt pour contribuer au site de l'ORGANISATION KA INTERNATIONALE !
|
||||
|
||||
Le site est une application **SvelteKit + Svelte 5 (runes) + TypeScript**, entièrement pré-rendue en statique. Avant de contribuer, lisez `charte-oki-design-system.md` (marque) et `svelte_core_bestpractices.md` (conventions Svelte 5).
|
||||
|
||||
## Ajouter du contenu
|
||||
|
||||
Le contenu éditorial vit dans les bundles i18n : `src/lib/i18n/fr/*.json` et `src/lib/i18n/en/*.json` — **toujours modifier les deux locales**.
|
||||
|
||||
### Ajouter un service hébergé
|
||||
|
||||
Éditez `src/_data/i18n/fr/services.json` (et la version `en/`) :
|
||||
Éditez `src/lib/i18n/fr/services.json` (et `en/`) :
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -18,122 +22,85 @@ Merci de votre intérêt pour contribuer au site de l'ORGANISATION KA INTERNATIO
|
||||
}
|
||||
```
|
||||
|
||||
Le service apparaîtra automatiquement dans la grille de fallback du village (`HostingVillage.svelte`). Pour qu'il ait aussi son bâtiment dans la scène SVG, voir « Modifier un composant ».
|
||||
|
||||
### Ajouter un projet web
|
||||
|
||||
Éditez `src/_data/i18n/fr/projects.json` (et la version `en/`) :
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "NOM.PROJET",
|
||||
"icon": "📱",
|
||||
"domain": "nom.projet",
|
||||
"description": "Description du projet",
|
||||
"url": "https://nom.projet/"
|
||||
}
|
||||
```
|
||||
Éditez `src/lib/i18n/fr/projects.json` (et `en/`). Le champ `icon` attend un emoji legacy qui sera remplacé au rendu par un picto du sprite — voir le mapping dans `src/lib/components/sections/pictos.ts` et choisissez-en un qui y figure.
|
||||
|
||||
### Ajouter un client ou partenaire
|
||||
|
||||
Pour les logos, privilégiez une image locale dans `src/assets/images/` et utilisez le filtre `| cdn` dans les templates. Les URLs distantes fonctionnent mais ne passent pas par le CDN.
|
||||
Placez le logo dans `src/lib/assets/images/` (et `clients/` pour les clients), puis importez-le dans le composant de section concerné via vite-imagetools :
|
||||
|
||||
```ts
|
||||
import logo from '$lib/assets/images/mon-logo.png?format=avif;webp;png&w=320;640&as=picture';
|
||||
```
|
||||
|
||||
et affichez-le avec `<ResponsiveImage picture={logo} alt="…" />`. Ne jamais référencer une image distante : tout asset est self-hébergé (doctrine souveraineté + CSP).
|
||||
|
||||
### Ajouter une valeur
|
||||
|
||||
Éditez `src/_data/i18n/fr/values.json` (et la version `en/`) :
|
||||
|
||||
```json
|
||||
{
|
||||
"icon": "🌟",
|
||||
"title": "Titre",
|
||||
"description": "Description de la valeur"
|
||||
}
|
||||
```
|
||||
Éditez `src/lib/i18n/fr/values.json` (et `en/`). Le champ `icon` suit le même mapping que les projets (`pictos.ts`).
|
||||
|
||||
### Modifier la navigation
|
||||
|
||||
Éditez `src/_data/i18n/fr/navigation.json` (et la version `en/`) :
|
||||
Éditez `src/lib/i18n/fr/navigation.json` (et `en/`) :
|
||||
|
||||
```json
|
||||
{
|
||||
"text": "Texte du lien",
|
||||
"url": "#section",
|
||||
"external": false
|
||||
}
|
||||
{ "text": "Texte du lien", "url": "/#ancre", "external": false }
|
||||
```
|
||||
|
||||
Pour un lien externe, mettez `"external": true`.
|
||||
Pour un lien externe, mettez `"external": true`. Les URLs de la version EN doivent pointer sous `/en/`.
|
||||
|
||||
### Modifier les informations de contact
|
||||
|
||||
Les liens de contact sont regroupés par section dans `src/_data/i18n/fr/contact.json` (et la version `en/`). L'icône affichée est déterminée automatiquement à partir du texte du lien (`whatsapp`, `telegram`, `discord`, `labola`, `kontak`/`mail`).
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "💬 Communauté",
|
||||
"links": [
|
||||
{ "text": "Discord", "url": "https://discord.gg/DPvUCA8dD" }
|
||||
]
|
||||
}
|
||||
```
|
||||
Les liens de contact sont regroupés dans `src/lib/i18n/fr/contact.json` (et `en/`). L'icône de chaque lien est déduite de son libellé (`whatsapp`, `telegram`, `discord`, `labola`, `kontak`/`mail`). Les emojis en tête des titres de groupes sont convertis en pictos du sprite (`pictos.ts`).
|
||||
|
||||
## Modifier les styles
|
||||
|
||||
Les styles sont dans `src/assets/css/styles.css`.
|
||||
|
||||
### Variables CSS principales
|
||||
|
||||
```css
|
||||
--or-oki: #E8A625;
|
||||
--noir-oki: #000000;
|
||||
--vert-oki: #00A854;
|
||||
--rouge-oki: #ED1C4C;
|
||||
--blanc-creme: #F7F7F2;
|
||||
--noir-profond: #1A1A1A;
|
||||
--gris-sombre: #2A2A2A;
|
||||
--or-clair: #F5BD4F;
|
||||
--vert-clair: #00C569;
|
||||
--rouge-clair: #FF2D5F;
|
||||
```
|
||||
- **Tokens (couleurs, typo, espacements, motion)** : `src/lib/styles/oki-tokens.css` — ne jamais recopier une valeur de la charte à la main, utiliser les variables.
|
||||
- **Primitives partagées** (boutons, cartes, tags, flag-bar, gate reduced-motion) : `src/lib/styles/base.css`.
|
||||
- **Styles d'une section** : dans le `<style>` scopé de son composant (`src/lib/components/sections/`).
|
||||
- Durées et courbes d'animation : toujours via les tokens (`--dur-tanbou`, `--ease-ka`…), jamais en dur. On n'anime que `transform` et `opacity`.
|
||||
- Le thème clair est géré par surcharge des tokens (`html.light-theme`) — ne pas écrire de sélecteur `html.light-theme` par composant.
|
||||
|
||||
## Créer une nouvelle page
|
||||
|
||||
1. Créez un fichier `.njk` dans `src/` :
|
||||
1. Créez `src/routes/ma-page/+page.svelte` (et sa contrepartie `src/routes/en/my-page/+page.svelte` si bilingue) :
|
||||
|
||||
```njk
|
||||
---
|
||||
layout: layouts/base.njk
|
||||
title: Titre de la page
|
||||
description: Description pour le SEO
|
||||
---
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import { getBundle } from '$lib/i18n';
|
||||
|
||||
<section>
|
||||
<h1>{{ title }}</h1>
|
||||
<p>Contenu de votre page</p>
|
||||
const t = getBundle('fr');
|
||||
</script>
|
||||
|
||||
<Seo title="Titre de la page" description="Description pour le SEO" path="/ma-page/" locale="fr" />
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1>Titre de la page</h1>
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
2. Ajoutez le lien dans `src/_data/i18n/fr/navigation.json` (et `en/`) si nécessaire.
|
||||
2. Ajoutez la correspondance FR↔EN dans `PATH_PAIRS` (`src/lib/i18n/index.ts`) pour l'hreflang et la bascule de langue.
|
||||
3. Ajoutez l'URL dans `static/sitemap.xml` et, si nécessaire, dans `navigation.json`.
|
||||
4. Vérifiez : `npm run check && npm run build`.
|
||||
|
||||
3. Mettez à jour `src/sitemap.xml`.
|
||||
## Modifier un composant
|
||||
|
||||
4. Reconstruisez le site : `npm run build`.
|
||||
- Sections de l'accueil : `src/lib/components/sections/`
|
||||
- Layout : `Nav.svelte`, `Footer.svelte`, `Seo.svelte`
|
||||
- Motion : `src/lib/components/motion/` (KineticText, ScrollProgressBar)
|
||||
- Icônes : sprite `static/icons.svg` (set OKI, charte §4) et `src/lib/components/icons/BrandIcon.svelte` (logos de marques)
|
||||
|
||||
## Modifier un partial
|
||||
Après toute modification d'un composant, passez l'autofixer (voir `svelte-5_code_writer.md`) :
|
||||
|
||||
Les partials sont dans `src/_includes/partials/` :
|
||||
|
||||
- `nav.njk` - Navigation
|
||||
- `hero.njk` - Section hero (accueil)
|
||||
- `services.njk` - Services hébergés
|
||||
- `projects.njk` - Projets web
|
||||
- `mission.njk` - Mission et valeurs
|
||||
- `hosting.njk` - Services d'hébergement
|
||||
- `support.njk` - Section dons
|
||||
- `clients.njk` - Clients / organisations ayant fait confiance
|
||||
- `partners.njk` - Partenaires
|
||||
- `faq.njk` - Questions fréquentes
|
||||
- `engagement.njk` - Appels à l'action
|
||||
- `contact.njk` - Informations de contact
|
||||
- `footer.njk` - Pied de page
|
||||
```bash
|
||||
npx @sveltejs/mcp svelte-autofixer ./src/lib/components/sections/MonComposant.svelte
|
||||
```
|
||||
|
||||
## Workflow de développement
|
||||
|
||||
@@ -146,12 +113,12 @@ Les partials sont dans `src/_includes/partials/` :
|
||||
|
||||
3. Testez localement :
|
||||
```bash
|
||||
npm start
|
||||
npm run dev
|
||||
```
|
||||
|
||||
4. Buildez pour vérifier :
|
||||
4. Vérifiez types, accessibilité et build :
|
||||
```bash
|
||||
npm run build
|
||||
npm run check && npm run build && npm run preview
|
||||
```
|
||||
|
||||
5. Commitez vos changements :
|
||||
@@ -167,24 +134,22 @@ Les partials sont dans `src/_includes/partials/` :
|
||||
|
||||
## Standards de code
|
||||
|
||||
### HTML/Nunjucks
|
||||
- Indentation : 4 espaces
|
||||
- Toujours fermer les balises
|
||||
- Attributs entre guillemets doubles
|
||||
### Svelte / TypeScript
|
||||
- Svelte 5 runes uniquement (`$state`, `$derived`, `$props`…) — voir `svelte_core_bestpractices.md`
|
||||
- TypeScript strict, props typées
|
||||
- `onclick={...}` (jamais `on:click`), blocs `{#each}` à clé unique
|
||||
- `svelte-autofixer` propre sur tout composant modifié, `npm run check` à zéro warning
|
||||
|
||||
### CSS
|
||||
- Indentation : 4 espaces
|
||||
- Utiliser les variables CSS quand possible
|
||||
- Mobile-first pour le responsive
|
||||
- Indentation : tabulations (convention du projet)
|
||||
- Variables des tokens quand possible, mobile-first
|
||||
- Angles nets : `border-radius` ≤ `var(--radius-md)` (6 px)
|
||||
|
||||
### JavaScript
|
||||
- Indentation : 4 espaces
|
||||
- Vanilla JS (pas de framework)
|
||||
- Commentaires en français
|
||||
- Gestion des erreurs
|
||||
### JavaScript (fichiers statiques)
|
||||
- Vanilla JS, commentaires en français, aucun script inline dans le HTML (CSP `script-src 'self'`)
|
||||
|
||||
### Sécurité
|
||||
- Ne pas ajouter de scripts inline sans ajuster la CSP dans `src/.htaccess`.
|
||||
- Ne pas ajouter de ressource externe (font, CDN, script) : tout se self-héberge. Si une exception est indispensable, ajuster la CSP dans `static/.htaccess` **et** `static/_headers`.
|
||||
- Conserver les headers de sécurité lors d'une modification du serveur.
|
||||
|
||||
## Questions ?
|
||||
|
||||
@@ -61,9 +61,16 @@ FR par défaut, EN sous `/en/`. Les bundles JSON de `src/lib/i18n/{fr,en}/` alim
|
||||
|
||||
Headers de sécurité (CSP stricte `default-src 'self'`, HSTS, `X-Frame-Options: DENY`, `nosniff`, `Referrer-Policy`, `Permissions-Policy`) livrés en deux formats : `static/.htaccess` (Apache/o2switch) et `static/_headers` (Cloudflare Pages). Voir **DEPLOY.md**.
|
||||
|
||||
## Charte
|
||||
## Charte et documents de référence
|
||||
|
||||
Couleurs, typographie, motion (cadences gwoka), iconographie SVG (zéro emoji en interface) : voir `charte-oki-design-system.md`. Thème sombre par défaut, thème clair en opt-in persisté (`localStorage['oki-theme']`).
|
||||
- **`charte-oki-design-system.md`** — marque canonique : couleurs, typographie, motion (cadences gwoka), iconographie SVG (zéro emoji en interface), voix. Prime pour tout ce qui est marque.
|
||||
- **`recette-sveltekit-playbook-agent.md`** — méthode technique : principes, budgets performance, règles motion, DoD (sa version longue : `recette-sveltekit-15-sites-awwwards.md`).
|
||||
- **`AGENTS.md`** — point d'entrée pour les agents de code : impose l'usage de l'outillage Svelte MCP.
|
||||
- **`svelte-5_code_writer.md`** — commandes `@sveltejs/mcp` (doc Svelte 5 + `svelte-autofixer`, à passer sur tout composant modifié).
|
||||
- **`svelte_core_bestpractices.md`** — conventions runes Svelte 5 ($state/$derived/$effect, events, snippets, styles).
|
||||
- **`DEPLOY.md`** — déploiement o2switch (Apache) et Cloudflare Pages.
|
||||
|
||||
Thème sombre par défaut, thème clair en opt-in persisté (`localStorage['oki-theme']`).
|
||||
|
||||
## Licence
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Guide SEO - o-k-i.net
|
||||
|
||||
Ce document décrit l'optimisation SEO mise en place sur le site o-k-i.net V2.
|
||||
Ce document décrit l'optimisation SEO du site o-k-i.net (V3, SvelteKit statique).
|
||||
|
||||
## 📋 Fichiers SEO
|
||||
|
||||
### 1. robots.txt
|
||||
**Emplacement :** `src/robots.txt`
|
||||
**Emplacement :** `static/robots.txt` (servi à `/robots.txt`)
|
||||
|
||||
Permet aux moteurs de recherche d'indexer tout le contenu du site et indique l'emplacement du sitemap.
|
||||
|
||||
@@ -16,174 +16,116 @@ Sitemap: https://o-k-i.net/sitemap.xml
|
||||
```
|
||||
|
||||
### 2. sitemap.xml
|
||||
**Emplacement :** `src/sitemap.xml`
|
||||
**Emplacement :** `static/sitemap.xml` (servi à `/sitemap.xml`)
|
||||
|
||||
Liste toutes les pages du site avec leurs priorités et fréquences de mise à jour.
|
||||
Liste les 8 pages du site avec priorités et fréquences :
|
||||
|
||||
**Pages incluses :**
|
||||
- Page d'accueil (priorité 1.0, hebdomadaire)
|
||||
- Mentions légales (priorité 0.3, mensuelle)
|
||||
- `/` et `/en/` (priorité 1.0, hebdomadaire)
|
||||
- `/dons/` et `/en/donate/` (0.6, mensuelle)
|
||||
- `/presse/` et `/en/press/` (0.5, mensuelle)
|
||||
- `/mentions-legales/` et `/en/legal-notice/` (0.3, mensuelle)
|
||||
|
||||
**À mettre à jour :** Lors de l'ajout de nouvelles pages.
|
||||
**À mettre à jour :** lors de l'ajout de nouvelles pages.
|
||||
|
||||
## 🏷️ Meta Tags
|
||||
|
||||
### Meta Tags de base
|
||||
Définis dans `src/_includes/layouts/base.njk` :
|
||||
Tous les meta tags sont centralisés dans le composant **`src/lib/components/Seo.svelte`**, alimenté par `src/lib/i18n/{locale}/site.json`. Chaque page l'invoque avec ses props :
|
||||
|
||||
- **Title** : Dynamique selon la page
|
||||
- **Description** : Personnalisable par page via front matter
|
||||
- **Keywords** : OKI, Fédiverse, PeerTube, Pleroma, Funkwhale, Castopod, Guadeloupe, logiciel libre, GAFAM
|
||||
- **Author** : Organisation Ka Internationale
|
||||
- **Canonical URL** : Évite le contenu dupliqué
|
||||
```svelte
|
||||
<Seo title="Titre de la page" description="Description spécifique" path="/ma-page/" locale="fr" />
|
||||
```
|
||||
|
||||
### Open Graph (Facebook, LinkedIn)
|
||||
- **Title** : `Titre | ORGANISATION KA INTERNATIONALE` (home : `… | OKI`)
|
||||
- **Description** : par page, sinon `site.description`
|
||||
- **Keywords** : `site.keywords` · **Author** : `site.name`
|
||||
- **Canonical URL** : `https://o-k-i.net{path}` — évite le contenu dupliqué
|
||||
- **Hreflang** : `fr`, `en`, `x-default`, calculés via `alternatePath()` (`src/lib/i18n/index.ts`)
|
||||
|
||||
### Open Graph (10 balises)
|
||||
```html
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://o-k-i.net">
|
||||
<meta property="og:title" content="...">
|
||||
<meta property="og:description" content="...">
|
||||
<meta property="og:image" content="https://o-k-i.net/assets/images/logo-512x512.png">
|
||||
<meta property="og:url" content="https://o-k-i.net/…">
|
||||
<meta property="og:title" content="…">
|
||||
<meta property="og:description" content="…">
|
||||
<meta property="og:image" content="https://o-k-i.net/images/logo-512x512.png">
|
||||
<meta property="og:image:width" content="512">
|
||||
<meta property="og:image:height" content="512">
|
||||
<meta property="og:image:alt" content="…">
|
||||
<meta property="og:locale" content="fr_FR">
|
||||
<meta property="og:site_name" content="ORGANISATION KA INTERNATIONALE">
|
||||
```
|
||||
|
||||
### Twitter Cards
|
||||
```html
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="...">
|
||||
<meta name="twitter:description" content="...">
|
||||
<meta name="twitter:image" content="...">
|
||||
```
|
||||
`summary_large_image` + url, title, description, image, image:alt — self-hébergées comme tout le reste.
|
||||
|
||||
## 📊 Structured Data (JSON-LD)
|
||||
|
||||
### Organization Schema
|
||||
Données structurées conformes à Schema.org pour améliorer l'affichage dans les résultats de recherche.
|
||||
Deux blocs injectés par `Seo.svelte` sur toutes les pages :
|
||||
|
||||
**Type :** Organization
|
||||
1. **Organization** : nom (OKI), URL, logo, description, adresse (36 Rue Lethière, Basse-Terre 97100, GP), contact (kontak@o-k-i.net), sameAs (niyaj, kute, labola, liberapay), `foundingDate: 2024`, `nonprofitStatus`.
|
||||
2. **MusicGroup** : DJANGOKAM (genres Reggae/Zouk/Konpa/K-pop, djangokam.pawol.nu, membre OKI).
|
||||
|
||||
**Informations incluses :**
|
||||
- Nom : ORGANISATION KA INTERNATIONALE
|
||||
- Nom alternatif : OKI
|
||||
- URL : https://o-k-i.net
|
||||
- Logo
|
||||
- Description
|
||||
- Adresse physique (Basse-Terre, Guadeloupe)
|
||||
- Email de contact
|
||||
- Liens vers les plateformes sociales (GADE, BOKANTE, NIYAJ, KUTE, Discord, Labola, Liberapay)
|
||||
- Statut : Organisation à but non lucratif
|
||||
## 🔍 Attributs lang
|
||||
|
||||
## 🔍 Optimisations par page
|
||||
|
||||
### Page d'accueil
|
||||
- **Title :** "ORGANISATION KA INTERNATIONALE | OKI"
|
||||
- **Description :** Reprend site.description
|
||||
- **Keywords :** Fédiverse, logiciel libre, alternatives GAFAM, Guadeloupe
|
||||
|
||||
### Mentions légales
|
||||
- **Title :** "Mentions Légales | ORGANISATION KA INTERNATIONALE"
|
||||
- **Description :** "Mentions légales et informations juridiques de l'Organisation Ka Internationale (OKI) - Hébergement, RGPD, licence AGPL-V3"
|
||||
L'attribut `lang` du `<html>` est posé au prerender par `src/hooks.server.ts` (`fr` par défaut, `en` sous `/en/`), et `data-locale` pilote la redirection de langue (`static/lang-redirect.js`). Les microcopies en kréyòl (404, hors-ligne) portent `lang="gcf"`.
|
||||
|
||||
## 🎯 Bonnes pratiques
|
||||
|
||||
### Ajouter une nouvelle page
|
||||
1. Créer le fichier `.njk` dans `src/`
|
||||
2. Ajouter le front matter avec title et description personnalisés
|
||||
3. Mettre à jour `src/sitemap.xml`
|
||||
4. Ajouter un lien dans la navigation si nécessaire
|
||||
|
||||
### Personnaliser les meta tags
|
||||
Dans le front matter de la page :
|
||||
```yaml
|
||||
---
|
||||
layout: layouts/base.njk
|
||||
title: Titre de la page
|
||||
description: Description spécifique à cette page
|
||||
---
|
||||
```
|
||||
1. Créer `src/routes/ma-page/+page.svelte` avec `<Seo>` renseigné
|
||||
2. Ajouter la paire FR↔EN dans `PATH_PAIRS` (`src/lib/i18n/index.ts`)
|
||||
3. Mettre à jour `static/sitemap.xml`
|
||||
4. Ajouter un lien dans `src/lib/i18n/*/navigation.json` si nécessaire
|
||||
|
||||
## 🚀 Tests et validation
|
||||
|
||||
### Outils recommandés
|
||||
1. **Google Search Console** - Soumettre le sitemap
|
||||
2. **Facebook Sharing Debugger** - Tester les meta Open Graph
|
||||
- URL : https://developers.facebook.com/tools/debug/
|
||||
3. **Twitter Card Validator** - Tester les Twitter Cards
|
||||
- URL : https://cards-dev.twitter.com/validator
|
||||
4. **Schema Markup Validator** - Valider le JSON-LD
|
||||
- URL : https://validator.schema.org/
|
||||
5. **Google Rich Results Test** - Tester les données structurées
|
||||
- URL : https://search.google.com/test/rich-results
|
||||
2. **Facebook Sharing Debugger** - https://developers.facebook.com/tools/debug/
|
||||
3. **Schema Markup Validator** - https://validator.schema.org/
|
||||
4. **Google Rich Results Test** - https://search.google.com/test/rich-results
|
||||
|
||||
### Commandes utiles
|
||||
### Commandes utiles (sur `npm run preview`)
|
||||
```bash
|
||||
# Vérifier robots.txt
|
||||
curl http://localhost:8080/robots.txt
|
||||
|
||||
# Vérifier sitemap.xml
|
||||
curl http://localhost:8080/sitemap.xml
|
||||
|
||||
# Vérifier les meta tags
|
||||
curl http://localhost:8080/ | grep -E "(og:|twitter:)"
|
||||
curl http://localhost:4173/robots.txt
|
||||
curl http://localhost:4173/sitemap.xml
|
||||
curl http://localhost:4173/ | grep -oE '(property|name)="(og:|twitter:)[^"]*"'
|
||||
```
|
||||
|
||||
## 📈 Suivi et amélioration
|
||||
|
||||
### Métriques à surveiller
|
||||
- Position dans les résultats de recherche Google
|
||||
- Taux de clics (CTR) depuis les résultats de recherche
|
||||
- Nombre de pages indexées
|
||||
- Erreurs d'exploration
|
||||
|
||||
### Améliorations futures possibles
|
||||
- [ ] Ajouter un blog/actualités avec articles optimisés SEO
|
||||
- [ ] Créer des pages dédiées pour chaque service Fédiverse
|
||||
- [ ] Ajouter des FAQ avec Schema.org FAQPage
|
||||
- [ ] Optimiser les images avec attributs alt descriptifs
|
||||
- [ ] Ajouter breadcrumbs avec données structurées
|
||||
- [ ] Implémenter AMP (Accelerated Mobile Pages) si nécessaire
|
||||
- [ ] Ajouter hreflang pour versions multilingues (créole, anglais, etc.)
|
||||
- [ ] Pages dédiées pour chaque service Fédiverse
|
||||
- [ ] FAQ avec Schema.org FAQPage
|
||||
- [ ] Breadcrumbs avec données structurées
|
||||
- [ ] Hreflang pour d'autres langues (créole…)
|
||||
|
||||
## 🌍 Mots-clés ciblés
|
||||
|
||||
### Primaires
|
||||
- Organisation Ka Internationale
|
||||
- OKI
|
||||
- Fédiverse Guadeloupe
|
||||
- PeerTube Guadeloupe
|
||||
- Alternatives GAFAM
|
||||
- Organisation Ka Internationale · OKI · Fédiverse Guadeloupe · Alternatives GAFAM
|
||||
|
||||
### Secondaires
|
||||
- Pleroma
|
||||
- Funkwhale
|
||||
- Castopod
|
||||
- Logiciel libre Antilles
|
||||
- Médias libres Caraïbes
|
||||
- Hébergement éthique
|
||||
- Langues créoles
|
||||
- Réseau social libre
|
||||
- PeerTube / Mastodon / Nextcloud / Castopod Guadeloupe · Logiciel libre Antilles · Médias libres Caraïbes · Hébergement éthique · Langues créoles
|
||||
|
||||
### Longue traîne
|
||||
- "alternative libre à YouTube Guadeloupe"
|
||||
- "hébergement PeerTube Martinique"
|
||||
- "réseau social décentralisé Antilles"
|
||||
- "promotion langues créoles numérique"
|
||||
- "alternative libre à YouTube Guadeloupe" · "hébergement PeerTube Martinique" · "réseau social décentralisé Antilles" · "promotion langues créoles numérique"
|
||||
|
||||
## 📝 Checklist SEO
|
||||
|
||||
- [x] Robots.txt configuré
|
||||
- [x] Sitemap.xml créé et soumis
|
||||
- [x] Sitemap.xml (8 URLs, dates à jour)
|
||||
- [x] Meta description sur toutes les pages
|
||||
- [x] Balises title optimisées
|
||||
- [x] URL canoniques
|
||||
- [x] Open Graph tags
|
||||
- [x] Open Graph (10 balises, og:image self-hébergée)
|
||||
- [x] Twitter Cards
|
||||
- [x] JSON-LD Organization
|
||||
- [x] Logo haute résolution (512x512)
|
||||
- [ ] Images avec attributs alt
|
||||
- [ ] Performance optimale (Lighthouse score)
|
||||
- [x] Mobile-friendly (déjà responsive)
|
||||
- [x] JSON-LD Organization + MusicGroup
|
||||
- [x] Logo haute résolution (512×512)
|
||||
- [x] Images avec attributs alt
|
||||
- [x] Performance (Lighthouse mobile ≥ 90, mesuré à 91)
|
||||
- [x] Mobile-friendly (responsive, vérifié Lighthouse)
|
||||
- [x] HTTPS activé et HSTS configuré
|
||||
- [x] Hreflang FR/EN pour les versions multilingues
|
||||
- [x] Hreflang FR/EN/x-default
|
||||
- [ ] Google Search Console configuré
|
||||
- [ ] Analytics installé (optionnel, respectueux RGPD)
|
||||
- [ ] Analytics installé (optionnel, respectueux RGPD — rester sans tiers)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import BrandIcon from '$lib/components/icons/BrandIcon.svelte';
|
||||
import type { Bundle, Locale } from '$lib/i18n';
|
||||
|
||||
let { t, locale }: { t: Bundle; locale: Locale } = $props();
|
||||
|
||||
const year = new Date().getFullYear();
|
||||
const legalHref = $derived(locale === 'en' ? '/en/legal-notice/' : '/mentions-legales/');
|
||||
const legalPath = $derived(locale === 'en' ? '/en/legal-notice/' : '/mentions-legales/');
|
||||
</script>
|
||||
|
||||
<footer>
|
||||
@@ -14,7 +15,7 @@
|
||||
<p class="footer-name">{t.site.name}</p>
|
||||
<p class="footer-meta">© {year} {t.site.name} | {t.site.tagline}</p>
|
||||
<p class="footer-legal">
|
||||
<a href={legalHref}>{t.footer.legal_link}</a>
|
||||
<a href={resolve(legalPath)}>{t.footer.legal_link}</a>
|
||||
</p>
|
||||
<div class="footer-socials">
|
||||
<a href="mailto:kontak@o-k-i.net" title="Email" class="social-link" aria-label="Email">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { resolve } from '$app/paths';
|
||||
import { alternatePath, type Bundle, type Locale } from '$lib/i18n';
|
||||
|
||||
let { t, locale }: { t: Bundle; locale: Locale } = $props();
|
||||
@@ -40,7 +41,7 @@
|
||||
|
||||
<nav aria-label={t.ui?.nav_label ?? 'Navigation principale'}>
|
||||
<div class="nav-container">
|
||||
<a href={locale === 'en' ? '/en/' : '/'} class="logo" onclick={closeMenu}>
|
||||
<a href={resolve(locale === 'en' ? '/en/' : '/')} class="logo" onclick={closeMenu}>
|
||||
<img src="/images/logo-512x512.png" alt="{t.site.shortName} Logo" class="logo-image" width="40" height="40" />
|
||||
<span>{t.site.shortName}</span>
|
||||
</a>
|
||||
@@ -61,25 +62,29 @@
|
||||
<ul class="nav-dropdown">
|
||||
{#each item.children as child (child.text)}
|
||||
<li>
|
||||
<a
|
||||
href={child.url}
|
||||
target={child.external ? '_blank' : undefined}
|
||||
rel={child.external ? 'noopener noreferrer' : undefined}
|
||||
onclick={closeMenu}>{child.text}</a
|
||||
>
|
||||
{#if child.external}
|
||||
<a
|
||||
href={child.url}
|
||||
target="_blank"
|
||||
rel="external noopener noreferrer"
|
||||
onclick={closeMenu}>{child.text}</a
|
||||
>
|
||||
{:else}
|
||||
<a href={resolve(child.url)} onclick={closeMenu}>{child.text}</a>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<a href={item.url} onclick={closeMenu}>{item.text}</a>
|
||||
<a href={resolve(item.url)} onclick={closeMenu}>{item.text}</a>
|
||||
</li>
|
||||
{/if}
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<a href={altHref} class="lang-switch" hreflang={altLocale} onclick={rememberLangChoice}>
|
||||
<a href={resolve(altHref)} class="lang-switch" hreflang={altLocale} onclick={rememberLangChoice}>
|
||||
{t.ui.lang_switch_label}
|
||||
</a>
|
||||
|
||||
|
||||
@@ -66,6 +66,11 @@
|
||||
url: 'https://o-k-i.net'
|
||||
}
|
||||
});
|
||||
|
||||
// {@html} sur contenu first-party (JSON-LD généré au build, aucune donnée utilisateur).
|
||||
// `<\/script>` : échappement requis pour ne pas fermer le bloc <script> du composant.
|
||||
const orgJsonLdScript = $derived(`<script type="application/ld+json">${orgJsonLd}<\/script>`);
|
||||
const djangokamJsonLdScript = `<script type="application/ld+json">${djangokamJsonLd}<\/script>`;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -97,6 +102,6 @@
|
||||
<meta name="twitter:image" content="https://o-k-i.net/images/logo-512x512.png" />
|
||||
<meta name="twitter:image:alt" content="Logo {site.name}" />
|
||||
|
||||
{@html `<script type="application/ld+json">${orgJsonLd}</script>`}
|
||||
{@html `<script type="application/ld+json">${djangokamJsonLd}</script>`}
|
||||
{@html orgJsonLdScript}
|
||||
{@html djangokamJsonLdScript}
|
||||
</svelte:head>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<section id="clients" class="section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">
|
||||
<KineticText as="span" text={t.clients.section_title} />{' '}
|
||||
<KineticText as="span" text={t.clients.section_title} /> 
|
||||
<span class="accent-text"
|
||||
><KineticText as="span" text={t.clients.section_title_accent} /></span
|
||||
>
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<div class="clients-grid">
|
||||
{#each t.clients.items as client (client.domain)}
|
||||
<a href={client.url} target="_blank" rel="noopener noreferrer" class="client-card">
|
||||
<a href={client.url} target="_blank" rel="external noopener noreferrer" class="client-card">
|
||||
<div class="client-logo-container">
|
||||
<ResponsiveImage
|
||||
picture={LOGOS[client.logo]}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
// {@html} sur contenu first-party (JSON i18n du dépôt), aucune donnée utilisateur.
|
||||
import BrandIcon from '$lib/components/icons/BrandIcon.svelte';
|
||||
import KineticText from '$lib/components/motion/KineticText.svelte';
|
||||
import type { Bundle, Locale } from '$lib/i18n';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
// {@html} sur contenu first-party (JSON i18n du dépôt), aucune donnée utilisateur.
|
||||
import KineticText from '$lib/components/motion/KineticText.svelte';
|
||||
import ResponsiveImage from '$lib/components/ResponsiveImage.svelte';
|
||||
import type { Bundle, Locale } from '$lib/i18n';
|
||||
@@ -22,7 +23,7 @@
|
||||
<div class="container">
|
||||
<p class="djangokam-surtitre">{t.djangokam.surtitre}</p>
|
||||
<h2 class="section-title">
|
||||
<KineticText as="span" text={t.djangokam.titre} />{' '}
|
||||
<KineticText as="span" text={t.djangokam.titre} /> 
|
||||
<span class="accent-text"
|
||||
><KineticText as="span" text={t.djangokam.titreAccent} /></span
|
||||
>
|
||||
@@ -48,7 +49,7 @@
|
||||
</div>
|
||||
|
||||
<figure class="djangokam-duo">
|
||||
<a href={t.djangokam.duo.url} target="_blank" rel="noopener noreferrer">
|
||||
<a href={t.djangokam.duo.url} target="_blank" rel="external noopener noreferrer">
|
||||
<ResponsiveImage
|
||||
picture={POCHETTES[t.djangokam.duo.image]}
|
||||
alt={t.djangokam.duo.alt}
|
||||
@@ -77,7 +78,7 @@
|
||||
<div class="djangokam-clips">
|
||||
{#each t.djangokam.clips as clip (clip.titre)}
|
||||
<figure class="djangokam-clip">
|
||||
<a href={clip.url} target="_blank" rel="noopener noreferrer">
|
||||
<a href={clip.url} target="_blank" rel="external noopener noreferrer">
|
||||
<ResponsiveImage
|
||||
picture={POCHETTES[clip.image]}
|
||||
alt={clip.alt}
|
||||
@@ -97,7 +98,7 @@
|
||||
<a
|
||||
href={bouton.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
rel="external noopener noreferrer"
|
||||
class="btn"
|
||||
class:btn-solid={bouton.primary}
|
||||
>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<section id="engagement" class="section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">
|
||||
<KineticText as="span" text={t.engagement.section_title} />{' '}
|
||||
<KineticText as="span" text={t.engagement.section_title} /> 
|
||||
<span class="accent-text"
|
||||
><KineticText as="span" text={t.engagement.section_title_accent} /></span
|
||||
>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<section id="faq" class="section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">
|
||||
<KineticText as="span" text={t.faq.section_title} />{' '}
|
||||
<KineticText as="span" text={t.faq.section_title} /> 
|
||||
<span class="accent-text"
|
||||
><KineticText as="span" text={t.faq.section_title_accent} /></span
|
||||
>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<section id="services-libres" class="section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">
|
||||
<KineticText as="span" text={t.services.section_title} />{' '}
|
||||
<KineticText as="span" text={t.services.section_title} /> 
|
||||
<span class="accent-text"
|
||||
><KineticText as="span" text={t.services.section_title_accent} /></span
|
||||
>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<section id="hebergement" class="section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">
|
||||
<KineticText as="span" text={t.hosting.section_title} />{' '}
|
||||
<KineticText as="span" text={t.hosting.section_title} /> 
|
||||
<span class="accent-text"
|
||||
><KineticText as="span" text={t.hosting.section_title_accent} /></span
|
||||
>
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
class="bldg"
|
||||
href={niyaj.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
rel="external noopener noreferrer"
|
||||
aria-label="{niyaj.name} — {niyaj.platform}"
|
||||
>
|
||||
<g class="bldg-art">
|
||||
@@ -139,7 +139,7 @@
|
||||
class="bldg"
|
||||
href={gade.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
rel="external noopener noreferrer"
|
||||
aria-label="{gade.name} — {gade.platform}"
|
||||
>
|
||||
<g class="bldg-art">
|
||||
@@ -172,7 +172,7 @@
|
||||
class="bldg"
|
||||
href={bokante.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
rel="external noopener noreferrer"
|
||||
aria-label="{bokante.name} — {bokante.platform}"
|
||||
>
|
||||
<g class="bldg-art">
|
||||
@@ -209,7 +209,7 @@
|
||||
class="bldg"
|
||||
href={kute.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
rel="external noopener noreferrer"
|
||||
aria-label="{kute.name} — {kute.platform}"
|
||||
>
|
||||
<g class="bldg-art">
|
||||
@@ -259,7 +259,7 @@
|
||||
<span class="tag">{item.platform}</span>
|
||||
</div>
|
||||
<p class="vf-desc">{item.description}</p>
|
||||
<a class="vf-link" href={item.url} target="_blank" rel="noopener noreferrer"
|
||||
<a class="vf-link" href={item.url} target="_blank" rel="external noopener noreferrer"
|
||||
>{item.linkText}</a
|
||||
>
|
||||
</li>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<section id="mission" class="section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">
|
||||
<KineticText as="span" text={t.mission.section_title} />{' '}
|
||||
<KineticText as="span" text={t.mission.section_title} /> 
|
||||
<span class="accent-text"
|
||||
><KineticText as="span" text={t.mission.section_title_accent} /></span
|
||||
>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<section id="partenaires" class="section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">
|
||||
<KineticText as="span" text={t.partners.section_title} />{' '}
|
||||
<KineticText as="span" text={t.partners.section_title} /> 
|
||||
<span class="accent-text"
|
||||
><KineticText as="span" text={t.partners.section_title_accent} /></span
|
||||
>
|
||||
@@ -41,7 +41,7 @@
|
||||
<p class="partner-description">{partner.description}</p>
|
||||
<div class="partner-links">
|
||||
{#each partner.links as link (link.url)}
|
||||
<a href={link.url} target="_blank" rel="noopener noreferrer" class="partner-link">
|
||||
<a href={link.url} target="_blank" rel="external noopener noreferrer" class="partner-link">
|
||||
{link.text} →
|
||||
</a>
|
||||
{/each}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<section id="projets" class="section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">
|
||||
<KineticText as="span" text={t.projects.section_title} />{' '}
|
||||
<KineticText as="span" text={t.projects.section_title} /> 
|
||||
<span class="accent-text"
|
||||
><KineticText as="span" text={t.projects.section_title_accent} /></span
|
||||
>
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<div class="project-cards">
|
||||
{#each t.projects.items as item (item.name)}
|
||||
<a href={item.url} target="_blank" rel="noopener noreferrer" class="project-card">
|
||||
<a href={item.url} target="_blank" rel="external noopener noreferrer" class="project-card">
|
||||
<div class="project-icon">
|
||||
<svg class="icon" aria-hidden="true"
|
||||
><use href="/icons.svg#{pictoFor(item.icon)}" /></svg
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<section id="solutions" class="section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">
|
||||
<KineticText as="span" text={t.publics.section_title} />{' '}
|
||||
<KineticText as="span" text={t.publics.section_title} /> 
|
||||
<span class="accent-text"
|
||||
><KineticText as="span" text={t.publics.section_title_accent} /></span
|
||||
>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* FR par défaut ; EN sur /en/.
|
||||
*/
|
||||
|
||||
import type { Pathname } from '$app/types';
|
||||
|
||||
export type Locale = 'fr' | 'en';
|
||||
|
||||
/** Bundle de traductions : une entrée par fichier JSON de section. */
|
||||
@@ -39,7 +41,7 @@ export function localeFromPath(pathname: string): Locale {
|
||||
}
|
||||
|
||||
/** Correspondances des pages entre locales (URLs historiques préservées). */
|
||||
const PATH_PAIRS: Array<[fr: string, en: string]> = [
|
||||
const PATH_PAIRS: Array<[fr: Pathname, en: Pathname]> = [
|
||||
['/', '/en/'],
|
||||
['/dons/', '/en/donate/'],
|
||||
['/presse/', '/en/press/'],
|
||||
@@ -47,7 +49,7 @@ const PATH_PAIRS: Array<[fr: string, en: string]> = [
|
||||
];
|
||||
|
||||
/** Chemin équivalent dans l'autre locale (sans le hash). */
|
||||
export function alternatePath(pathname: string, target: Locale): string {
|
||||
export function alternatePath(pathname: string, target: Locale): Pathname {
|
||||
for (const [fr, en] of PATH_PAIRS) {
|
||||
if (pathname === fr) return target === 'en' ? en : fr;
|
||||
if (pathname === en) return target === 'fr' ? fr : en;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { resolve } from '$app/paths';
|
||||
|
||||
const status = $derived(page.status);
|
||||
const is404 = $derived(status === 404);
|
||||
@@ -19,7 +20,7 @@
|
||||
{:else}
|
||||
<p class="fr">{page.error?.message ?? 'Une erreur est survenue.'}</p>
|
||||
{/if}
|
||||
<a class="btn" href="/">Retour à l'accueil</a>
|
||||
<a class="btn" href={resolve('/')}>Retour à l'accueil</a>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
// {@html} sur contenu first-party (JSON i18n du dépôt), aucune donnée utilisateur.
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import donations from '$lib/data/donations.json';
|
||||
import { getBundle } from '$lib/i18n';
|
||||
@@ -46,7 +47,7 @@
|
||||
|
||||
<div class="donation-grid">
|
||||
{#each donations.oneTime as d (d.url)}
|
||||
<a href={d.url} class="donation-amount" target="_blank" rel="noopener noreferrer">
|
||||
<a href={d.url} class="donation-amount" target="_blank" rel="external noopener noreferrer">
|
||||
{d.amount}{t.support.one_time_suffix}
|
||||
</a>
|
||||
{/each}
|
||||
@@ -86,7 +87,7 @@
|
||||
href={d.url}
|
||||
class="donation-amount monthly"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
rel="external noopener noreferrer"
|
||||
>
|
||||
{d.amount}{t.support.monthly_suffix}
|
||||
</a>
|
||||
@@ -126,7 +127,7 @@
|
||||
<a
|
||||
href={donations.liberapay}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
rel="external noopener noreferrer"
|
||||
class="alt-donation-card alt-donation-card--liberapay"
|
||||
>
|
||||
<svg class="icon alt-donation-icon" aria-hidden="true"
|
||||
@@ -139,7 +140,7 @@
|
||||
<a
|
||||
href={donations.kofi}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
rel="external noopener noreferrer"
|
||||
class="alt-donation-card alt-donation-card--kofi"
|
||||
>
|
||||
<svg class="icon alt-donation-icon" aria-hidden="true"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
// {@html} sur contenu first-party (JSON i18n du dépôt), aucune donnée utilisateur.
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import donations from '$lib/data/donations.json';
|
||||
import { getBundle } from '$lib/i18n';
|
||||
@@ -46,7 +47,7 @@
|
||||
|
||||
<div class="donation-grid">
|
||||
{#each donations.oneTime as d (d.url)}
|
||||
<a href={d.url} class="donation-amount" target="_blank" rel="noopener noreferrer">
|
||||
<a href={d.url} class="donation-amount" target="_blank" rel="external noopener noreferrer">
|
||||
{d.amount}{t.support.one_time_suffix}
|
||||
</a>
|
||||
{/each}
|
||||
@@ -86,7 +87,7 @@
|
||||
href={d.url}
|
||||
class="donation-amount monthly"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
rel="external noopener noreferrer"
|
||||
>
|
||||
{d.amount}{t.support.monthly_suffix}
|
||||
</a>
|
||||
@@ -126,7 +127,7 @@
|
||||
<a
|
||||
href={donations.liberapay}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
rel="external noopener noreferrer"
|
||||
class="alt-donation-card alt-donation-card--liberapay"
|
||||
>
|
||||
<svg class="icon alt-donation-icon" aria-hidden="true"
|
||||
@@ -139,7 +140,7 @@
|
||||
<a
|
||||
href={donations.kofi}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
rel="external noopener noreferrer"
|
||||
class="alt-donation-card alt-donation-card--kofi"
|
||||
>
|
||||
<svg class="icon alt-donation-icon" aria-hidden="true"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import { getBundle } from '$lib/i18n';
|
||||
|
||||
@@ -212,7 +213,7 @@
|
||||
|
||||
<!-- Back to Home -->
|
||||
<div class="legal-back">
|
||||
<a href="/en/" class="btn btn-solid">← {t.ui.back_home}</a>
|
||||
<a href={resolve('/en/')} class="btn btn-solid">← {t.ui.back_home}</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { asset } from '$app/paths';
|
||||
import ResponsiveImage from '$lib/components/ResponsiveImage.svelte';
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import KineticText from '$lib/components/motion/KineticText.svelte';
|
||||
@@ -48,18 +49,18 @@
|
||||
<div class="press-visual-grid">
|
||||
{#each t.press.visuals as visual, i (visual.pdf)}
|
||||
<div class="press-visual">
|
||||
<a href={pictures[i].img.src} target="_blank" rel="noopener noreferrer">
|
||||
<a href={asset(pictures[i].img.src)} target="_blank" rel="noopener noreferrer">
|
||||
<ResponsiveImage picture={pictures[i]} alt={visual.caption} loading="lazy" />
|
||||
<span class="press-visual-caption">{visual.caption}</span>
|
||||
</a>
|
||||
<a href="/assets/press/{visual.pdf}" class="press-visual-pdf-link">
|
||||
<a href={asset(`/assets/press/${visual.pdf}`)} class="press-visual-pdf-link">
|
||||
{t.press.visual_pdf_label}
|
||||
</a>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="press-visuals-pdf">
|
||||
<a href="/assets/press/{t.press.visuals_pdf_file}" class="btn btn-solid">
|
||||
<a href={asset(`/assets/press/${t.press.visuals_pdf_file}`)} class="btn btn-solid">
|
||||
{t.press.visuals_pdf_label} →
|
||||
</a>
|
||||
</div>
|
||||
@@ -71,7 +72,7 @@
|
||||
<KineticText text={t.press.links_title} as="h2" class="press-section-title" />
|
||||
<div class="press-links-list">
|
||||
{#each t.press.links as link (link.url)}
|
||||
<a href={link.url} target="_blank" rel="noopener noreferrer">{link.text}</a>
|
||||
<a href={link.url} target="_blank" rel="external noopener noreferrer">{link.text}</a>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import { getBundle } from '$lib/i18n';
|
||||
|
||||
@@ -221,7 +222,7 @@
|
||||
|
||||
<!-- Retour à l'accueil -->
|
||||
<div class="legal-back">
|
||||
<a href="/" class="btn btn-solid">← {t.ui.back_home}</a>
|
||||
<a href={resolve('/')} class="btn btn-solid">← {t.ui.back_home}</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Hors ligne · ORGANISATION KA INTERNATIONALE</title>
|
||||
<meta name="robots" content="noindex" />
|
||||
@@ -12,7 +16,7 @@
|
||||
rétablie.
|
||||
</p>
|
||||
<p class="fr" lang="en">This page is not available offline. It will be back as soon as your connection is restored.</p>
|
||||
<a class="btn" href="/">Retour à l'accueil</a>
|
||||
<a class="btn" href={resolve('/')}>Retour à l'accueil</a>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { asset } from '$app/paths';
|
||||
import ResponsiveImage from '$lib/components/ResponsiveImage.svelte';
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import KineticText from '$lib/components/motion/KineticText.svelte';
|
||||
@@ -48,18 +49,18 @@
|
||||
<div class="press-visual-grid">
|
||||
{#each t.press.visuals as visual, i (visual.pdf)}
|
||||
<div class="press-visual">
|
||||
<a href={pictures[i].img.src} target="_blank" rel="noopener noreferrer">
|
||||
<a href={asset(pictures[i].img.src)} target="_blank" rel="noopener noreferrer">
|
||||
<ResponsiveImage picture={pictures[i]} alt={visual.caption} loading="lazy" />
|
||||
<span class="press-visual-caption">{visual.caption}</span>
|
||||
</a>
|
||||
<a href="/assets/press/{visual.pdf}" class="press-visual-pdf-link">
|
||||
<a href={asset(`/assets/press/${visual.pdf}`)} class="press-visual-pdf-link">
|
||||
{t.press.visual_pdf_label}
|
||||
</a>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="press-visuals-pdf">
|
||||
<a href="/assets/press/{t.press.visuals_pdf_file}" class="btn btn-solid">
|
||||
<a href={asset(`/assets/press/${t.press.visuals_pdf_file}`)} class="btn btn-solid">
|
||||
{t.press.visuals_pdf_label} →
|
||||
</a>
|
||||
</div>
|
||||
@@ -71,7 +72,7 @@
|
||||
<KineticText text={t.press.links_title} as="h2" class="press-section-title" />
|
||||
<div class="press-links-list">
|
||||
{#each t.press.links as link (link.url)}
|
||||
<a href={link.url} target="_blank" rel="noopener noreferrer">{link.text}</a>
|
||||
<a href={link.url} target="_blank" rel="external noopener noreferrer">{link.text}</a>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user