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 &#32;
- Renommage svelte_core_bestpractices.md
- Passe svelte-autofixer : 0 issue restante hors {@html} first-party assumés
This commit is contained in:
sucupira
2026-07-21 08:41:55 -04:00
parent d0d80a105e
commit 30858926fa
28 changed files with 217 additions and 277 deletions
+65 -100
View File
@@ -2,11 +2,15 @@
Merci de votre intérêt pour contribuer au site de l'ORGANISATION KA INTERNATIONALE ! 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 ## 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é ### 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 ```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 ### Ajouter un projet web
Éditez `src/_data/i18n/fr/projects.json` (et la version `en/`) : É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.
```json
{
"name": "NOM.PROJET",
"icon": "📱",
"domain": "nom.projet",
"description": "Description du projet",
"url": "https://nom.projet/"
}
```
### Ajouter un client ou partenaire ### 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 ### Ajouter une valeur
Éditez `src/_data/i18n/fr/values.json` (et la version `en/`) : Éditez `src/lib/i18n/fr/values.json` (et `en/`). Le champ `icon` suit le même mapping que les projets (`pictos.ts`).
```json
{
"icon": "🌟",
"title": "Titre",
"description": "Description de la valeur"
}
```
### Modifier la navigation ### Modifier la navigation
Éditez `src/_data/i18n/fr/navigation.json` (et la version `en/`) : Éditez `src/lib/i18n/fr/navigation.json` (et `en/`) :
```json ```json
{ { "text": "Texte du lien", "url": "/#ancre", "external": false }
"text": "Texte du lien",
"url": "#section",
"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 ### 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`). 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`).
```json
{
"title": "💬 Communauté",
"links": [
{ "text": "Discord", "url": "https://discord.gg/DPvUCA8dD" }
]
}
```
## Modifier les styles ## Modifier les styles
Les styles sont dans `src/assets/css/styles.css`. - **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`.
### Variables CSS principales - **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`.
```css - 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.
--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;
```
## Créer une nouvelle page ## 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 ```svelte
--- <script lang="ts">
layout: layouts/base.njk import Seo from '$lib/components/Seo.svelte';
title: Titre de la page import { getBundle } from '$lib/i18n';
description: Description pour le SEO
---
<section> const t = getBundle('fr');
<h1>{{ title }}</h1> </script>
<p>Contenu de votre page</p>
<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> </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/` : ```bash
npx @sveltejs/mcp svelte-autofixer ./src/lib/components/sections/MonComposant.svelte
- `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
## Workflow de développement ## Workflow de développement
@@ -146,12 +113,12 @@ Les partials sont dans `src/_includes/partials/` :
3. Testez localement : 3. Testez localement :
```bash ```bash
npm start npm run dev
``` ```
4. Buildez pour vérifier : 4. Vérifiez types, accessibilité et build :
```bash ```bash
npm run build npm run check && npm run build && npm run preview
``` ```
5. Commitez vos changements : 5. Commitez vos changements :
@@ -167,24 +134,22 @@ Les partials sont dans `src/_includes/partials/` :
## Standards de code ## Standards de code
### HTML/Nunjucks ### Svelte / TypeScript
- Indentation : 4 espaces - Svelte 5 runes uniquement (`$state`, `$derived`, `$props`…) — voir `svelte_core_bestpractices.md`
- Toujours fermer les balises - TypeScript strict, props typées
- Attributs entre guillemets doubles - `onclick={...}` (jamais `on:click`), blocs `{#each}` à clé unique
- `svelte-autofixer` propre sur tout composant modifié, `npm run check` à zéro warning
### CSS ### CSS
- Indentation : 4 espaces - Indentation : tabulations (convention du projet)
- Utiliser les variables CSS quand possible - Variables des tokens quand possible, mobile-first
- Mobile-first pour le responsive - Angles nets : `border-radius` ≤ `var(--radius-md)` (6 px)
### JavaScript ### JavaScript (fichiers statiques)
- Indentation : 4 espaces - Vanilla JS, commentaires en français, aucun script inline dans le HTML (CSP `script-src 'self'`)
- Vanilla JS (pas de framework)
- Commentaires en français
- Gestion des erreurs
### Sécurité ### 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. - Conserver les headers de sécurité lors d'une modification du serveur.
## Questions ? ## Questions ?
+9 -2
View File
@@ -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**. 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 ## Licence
+61 -119
View File
@@ -1,11 +1,11 @@
# Guide SEO - o-k-i.net # 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 ## 📋 Fichiers SEO
### 1. robots.txt ### 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. 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 ### 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 :** - `/` et `/en/` (priorité 1.0, hebdomadaire)
- Page d'accueil (priorité 1.0, hebdomadaire) - `/dons/` et `/en/donate/` (0.6, mensuelle)
- Mentions légales (priorité 0.3, 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
### Meta Tags de base 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 :
Définis dans `src/_includes/layouts/base.njk` :
- **Title** : Dynamique selon la page ```svelte
- **Description** : Personnalisable par page via front matter <Seo title="Titre de la page" description="Description spécifique" path="/ma-page/" locale="fr" />
- **Keywords** : OKI, Fédiverse, PeerTube, Pleroma, Funkwhale, Castopod, Guadeloupe, logiciel libre, GAFAM ```
- **Author** : Organisation Ka Internationale
- **Canonical URL** : Évite le contenu dupliqué
### 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 ```html
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:url" content="https://o-k-i.net"> <meta property="og:url" content="https://o-k-i.net/…">
<meta property="og:title" content="..."> <meta property="og:title" content="">
<meta property="og:description" content="..."> <meta property="og:description" content="">
<meta property="og:image" content="https://o-k-i.net/assets/images/logo-512x512.png"> <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:locale" content="fr_FR">
<meta property="og:site_name" content="ORGANISATION KA INTERNATIONALE">
``` ```
### Twitter Cards ### Twitter Cards
```html `summary_large_image` + url, title, description, image, image:alt — self-hébergées comme tout le reste.
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="...">
<meta name="twitter:description" content="...">
<meta name="twitter:image" content="...">
```
## 📊 Structured Data (JSON-LD) ## 📊 Structured Data (JSON-LD)
### Organization Schema Deux blocs injectés par `Seo.svelte` sur toutes les pages :
Données structurées conformes à Schema.org pour améliorer l'affichage dans les résultats de recherche.
**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 :** ## 🔍 Attributs lang
- 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
## 🔍 Optimisations par page 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"`.
### 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"
## 🎯 Bonnes pratiques ## 🎯 Bonnes pratiques
### Ajouter une nouvelle page ### Ajouter une nouvelle page
1. Créer le fichier `.njk` dans `src/` 1. Créer `src/routes/ma-page/+page.svelte` avec `<Seo>` renseigné
2. Ajouter le front matter avec title et description personnalisés 2. Ajouter la paire FR↔EN dans `PATH_PAIRS` (`src/lib/i18n/index.ts`)
3. Mettre à jour `src/sitemap.xml` 3. Mettre à jour `static/sitemap.xml`
4. Ajouter un lien dans la navigation si nécessaire 4. Ajouter un lien dans `src/lib/i18n/*/navigation.json` 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
---
```
## 🚀 Tests et validation ## 🚀 Tests et validation
### Outils recommandés ### Outils recommandés
1. **Google Search Console** - Soumettre le sitemap 1. **Google Search Console** - Soumettre le sitemap
2. **Facebook Sharing Debugger** - Tester les meta Open Graph 2. **Facebook Sharing Debugger** - https://developers.facebook.com/tools/debug/
- URL : https://developers.facebook.com/tools/debug/ 3. **Schema Markup Validator** - https://validator.schema.org/
3. **Twitter Card Validator** - Tester les Twitter Cards 4. **Google Rich Results Test** - https://search.google.com/test/rich-results
- 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
### Commandes utiles ### Commandes utiles (sur `npm run preview`)
```bash ```bash
# Vérifier robots.txt curl http://localhost:4173/robots.txt
curl http://localhost:8080/robots.txt curl http://localhost:4173/sitemap.xml
curl http://localhost:4173/ | grep -oE '(property|name)="(og:|twitter:)[^"]*"'
# Vérifier sitemap.xml
curl http://localhost:8080/sitemap.xml
# Vérifier les meta tags
curl http://localhost:8080/ | grep -E "(og:|twitter:)"
``` ```
## 📈 Suivi et amélioration ## 📈 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 ### Améliorations futures possibles
- [ ] Ajouter un blog/actualités avec articles optimisés SEO - [ ] Pages dédiées pour chaque service Fédiverse
- [ ] Créer des pages dédiées pour chaque service Fédiverse - [ ] FAQ avec Schema.org FAQPage
- [ ] Ajouter des FAQ avec Schema.org FAQPage - [ ] Breadcrumbs avec données structurées
- [ ] Optimiser les images avec attributs alt descriptifs - [ ] Hreflang pour d'autres langues (créole…)
- [ ] 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.)
## 🌍 Mots-clés ciblés ## 🌍 Mots-clés ciblés
### Primaires ### Primaires
- Organisation Ka Internationale - Organisation Ka Internationale · OKI · Fédiverse Guadeloupe · Alternatives GAFAM
- OKI
- Fédiverse Guadeloupe
- PeerTube Guadeloupe
- Alternatives GAFAM
### Secondaires ### Secondaires
- Pleroma - PeerTube / Mastodon / Nextcloud / Castopod Guadeloupe · Logiciel libre Antilles · Médias libres Caraïbes · Hébergement éthique · Langues créoles
- Funkwhale
- Castopod
- Logiciel libre Antilles
- Médias libres Caraïbes
- Hébergement éthique
- Langues créoles
- Réseau social libre
### Longue traîne ### Longue traîne
- "alternative libre à YouTube Guadeloupe" - "alternative libre à YouTube Guadeloupe" · "hébergement PeerTube Martinique" · "réseau social décentralisé Antilles" · "promotion langues créoles numérique"
- "hébergement PeerTube Martinique"
- "réseau social décentralisé Antilles"
- "promotion langues créoles numérique"
## 📝 Checklist SEO ## 📝 Checklist SEO
- [x] Robots.txt configuré - [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] Meta description sur toutes les pages
- [x] Balises title optimisées - [x] Balises title optimisées
- [x] URL canoniques - [x] URL canoniques
- [x] Open Graph tags - [x] Open Graph (10 balises, og:image self-hébergée)
- [x] Twitter Cards - [x] Twitter Cards
- [x] JSON-LD Organization - [x] JSON-LD Organization + MusicGroup
- [x] Logo haute résolution (512x512) - [x] Logo haute résolution (512×512)
- [ ] Images avec attributs alt - [x] Images avec attributs alt
- [ ] Performance optimale (Lighthouse score) - [x] Performance (Lighthouse mobile ≥ 90, mesuré à 91)
- [x] Mobile-friendly (déjà responsive) - [x] Mobile-friendly (responsive, vérifié Lighthouse)
- [x] HTTPS activé et HSTS configuré - [x] HTTPS activé et HSTS configuré
- [x] Hreflang FR/EN pour les versions multilingues - [x] Hreflang FR/EN/x-default
- [ ] Google Search Console configuré - [ ] Google Search Console configuré
- [ ] Analytics installé (optionnel, respectueux RGPD) - [ ] Analytics installé (optionnel, respectueux RGPD — rester sans tiers)
+3 -2
View File
@@ -1,11 +1,12 @@
<script lang="ts"> <script lang="ts">
import { resolve } from '$app/paths';
import BrandIcon from '$lib/components/icons/BrandIcon.svelte'; import BrandIcon from '$lib/components/icons/BrandIcon.svelte';
import type { Bundle, Locale } from '$lib/i18n'; import type { Bundle, Locale } from '$lib/i18n';
let { t, locale }: { t: Bundle; locale: Locale } = $props(); let { t, locale }: { t: Bundle; locale: Locale } = $props();
const year = new Date().getFullYear(); 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> </script>
<footer> <footer>
@@ -14,7 +15,7 @@
<p class="footer-name">{t.site.name}</p> <p class="footer-name">{t.site.name}</p>
<p class="footer-meta">© {year} {t.site.name} | {t.site.tagline}</p> <p class="footer-meta">© {year} {t.site.name} | {t.site.tagline}</p>
<p class="footer-legal"> <p class="footer-legal">
<a href={legalHref}>{t.footer.legal_link}</a> <a href={resolve(legalPath)}>{t.footer.legal_link}</a>
</p> </p>
<div class="footer-socials"> <div class="footer-socials">
<a href="mailto:kontak@o-k-i.net" title="Email" class="social-link" aria-label="Email"> <a href="mailto:kontak@o-k-i.net" title="Email" class="social-link" aria-label="Email">
+10 -5
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/state'; import { page } from '$app/state';
import { resolve } from '$app/paths';
import { alternatePath, type Bundle, type Locale } from '$lib/i18n'; import { alternatePath, type Bundle, type Locale } from '$lib/i18n';
let { t, locale }: { t: Bundle; locale: Locale } = $props(); let { t, locale }: { t: Bundle; locale: Locale } = $props();
@@ -40,7 +41,7 @@
<nav aria-label={t.ui?.nav_label ?? 'Navigation principale'}> <nav aria-label={t.ui?.nav_label ?? 'Navigation principale'}>
<div class="nav-container"> <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" /> <img src="/images/logo-512x512.png" alt="{t.site.shortName} Logo" class="logo-image" width="40" height="40" />
<span>{t.site.shortName}</span> <span>{t.site.shortName}</span>
</a> </a>
@@ -61,25 +62,29 @@
<ul class="nav-dropdown"> <ul class="nav-dropdown">
{#each item.children as child (child.text)} {#each item.children as child (child.text)}
<li> <li>
{#if child.external}
<a <a
href={child.url} href={child.url}
target={child.external ? '_blank' : undefined} target="_blank"
rel={child.external ? 'noopener noreferrer' : undefined} rel="external noopener noreferrer"
onclick={closeMenu}>{child.text}</a onclick={closeMenu}>{child.text}</a
> >
{:else}
<a href={resolve(child.url)} onclick={closeMenu}>{child.text}</a>
{/if}
</li> </li>
{/each} {/each}
</ul> </ul>
</li> </li>
{:else} {:else}
<li> <li>
<a href={item.url} onclick={closeMenu}>{item.text}</a> <a href={resolve(item.url)} onclick={closeMenu}>{item.text}</a>
</li> </li>
{/if} {/if}
{/each} {/each}
</ul> </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} {t.ui.lang_switch_label}
</a> </a>
+7 -2
View File
@@ -66,6 +66,11 @@
url: 'https://o-k-i.net' 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> </script>
<svelte:head> <svelte:head>
@@ -97,6 +102,6 @@
<meta name="twitter:image" content="https://o-k-i.net/images/logo-512x512.png" /> <meta name="twitter:image" content="https://o-k-i.net/images/logo-512x512.png" />
<meta name="twitter:image:alt" content="Logo {site.name}" /> <meta name="twitter:image:alt" content="Logo {site.name}" />
{@html `<script type="application/ld+json">${orgJsonLd}</script>`} {@html orgJsonLdScript}
{@html `<script type="application/ld+json">${djangokamJsonLd}</script>`} {@html djangokamJsonLdScript}
</svelte:head> </svelte:head>
+2 -2
View File
@@ -19,7 +19,7 @@
<section id="clients" class="section"> <section id="clients" class="section">
<div class="container"> <div class="container">
<h2 class="section-title"> <h2 class="section-title">
<KineticText as="span" text={t.clients.section_title} />{' '} <KineticText as="span" text={t.clients.section_title} />&#32;
<span class="accent-text" <span class="accent-text"
><KineticText as="span" text={t.clients.section_title_accent} /></span ><KineticText as="span" text={t.clients.section_title_accent} /></span
> >
@@ -28,7 +28,7 @@
<div class="clients-grid"> <div class="clients-grid">
{#each t.clients.items as client (client.domain)} {#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"> <div class="client-logo-container">
<ResponsiveImage <ResponsiveImage
picture={LOGOS[client.logo]} picture={LOGOS[client.logo]}
@@ -1,4 +1,5 @@
<script lang="ts"> <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 BrandIcon from '$lib/components/icons/BrandIcon.svelte';
import KineticText from '$lib/components/motion/KineticText.svelte'; import KineticText from '$lib/components/motion/KineticText.svelte';
import type { Bundle, Locale } from '$lib/i18n'; import type { Bundle, Locale } from '$lib/i18n';
+5 -4
View File
@@ -1,4 +1,5 @@
<script lang="ts"> <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 KineticText from '$lib/components/motion/KineticText.svelte';
import ResponsiveImage from '$lib/components/ResponsiveImage.svelte'; import ResponsiveImage from '$lib/components/ResponsiveImage.svelte';
import type { Bundle, Locale } from '$lib/i18n'; import type { Bundle, Locale } from '$lib/i18n';
@@ -22,7 +23,7 @@
<div class="container"> <div class="container">
<p class="djangokam-surtitre">{t.djangokam.surtitre}</p> <p class="djangokam-surtitre">{t.djangokam.surtitre}</p>
<h2 class="section-title"> <h2 class="section-title">
<KineticText as="span" text={t.djangokam.titre} />{' '} <KineticText as="span" text={t.djangokam.titre} />&#32;
<span class="accent-text" <span class="accent-text"
><KineticText as="span" text={t.djangokam.titreAccent} /></span ><KineticText as="span" text={t.djangokam.titreAccent} /></span
> >
@@ -48,7 +49,7 @@
</div> </div>
<figure class="djangokam-duo"> <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 <ResponsiveImage
picture={POCHETTES[t.djangokam.duo.image]} picture={POCHETTES[t.djangokam.duo.image]}
alt={t.djangokam.duo.alt} alt={t.djangokam.duo.alt}
@@ -77,7 +78,7 @@
<div class="djangokam-clips"> <div class="djangokam-clips">
{#each t.djangokam.clips as clip (clip.titre)} {#each t.djangokam.clips as clip (clip.titre)}
<figure class="djangokam-clip"> <figure class="djangokam-clip">
<a href={clip.url} target="_blank" rel="noopener noreferrer"> <a href={clip.url} target="_blank" rel="external noopener noreferrer">
<ResponsiveImage <ResponsiveImage
picture={POCHETTES[clip.image]} picture={POCHETTES[clip.image]}
alt={clip.alt} alt={clip.alt}
@@ -97,7 +98,7 @@
<a <a
href={bouton.url} href={bouton.url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="external noopener noreferrer"
class="btn" class="btn"
class:btn-solid={bouton.primary} class:btn-solid={bouton.primary}
> >
@@ -9,7 +9,7 @@
<section id="engagement" class="section"> <section id="engagement" class="section">
<div class="container"> <div class="container">
<h2 class="section-title"> <h2 class="section-title">
<KineticText as="span" text={t.engagement.section_title} />{' '} <KineticText as="span" text={t.engagement.section_title} />&#32;
<span class="accent-text" <span class="accent-text"
><KineticText as="span" text={t.engagement.section_title_accent} /></span ><KineticText as="span" text={t.engagement.section_title_accent} /></span
> >
+1 -1
View File
@@ -8,7 +8,7 @@
<section id="faq" class="section"> <section id="faq" class="section">
<div class="container"> <div class="container">
<h2 class="section-title"> <h2 class="section-title">
<KineticText as="span" text={t.faq.section_title} />{' '} <KineticText as="span" text={t.faq.section_title} />&#32;
<span class="accent-text" <span class="accent-text"
><KineticText as="span" text={t.faq.section_title_accent} /></span ><KineticText as="span" text={t.faq.section_title_accent} /></span
> >
+1 -1
View File
@@ -9,7 +9,7 @@
<section id="services-libres" class="section"> <section id="services-libres" class="section">
<div class="container"> <div class="container">
<h2 class="section-title"> <h2 class="section-title">
<KineticText as="span" text={t.services.section_title} />{' '} <KineticText as="span" text={t.services.section_title} />&#32;
<span class="accent-text" <span class="accent-text"
><KineticText as="span" text={t.services.section_title_accent} /></span ><KineticText as="span" text={t.services.section_title_accent} /></span
> >
+1 -1
View File
@@ -8,7 +8,7 @@
<section id="hebergement" class="section"> <section id="hebergement" class="section">
<div class="container"> <div class="container">
<h2 class="section-title"> <h2 class="section-title">
<KineticText as="span" text={t.hosting.section_title} />{' '} <KineticText as="span" text={t.hosting.section_title} />&#32;
<span class="accent-text" <span class="accent-text"
><KineticText as="span" text={t.hosting.section_title_accent} /></span ><KineticText as="span" text={t.hosting.section_title_accent} /></span
> >
@@ -104,7 +104,7 @@
class="bldg" class="bldg"
href={niyaj.url} href={niyaj.url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="external noopener noreferrer"
aria-label="{niyaj.name}{niyaj.platform}" aria-label="{niyaj.name}{niyaj.platform}"
> >
<g class="bldg-art"> <g class="bldg-art">
@@ -139,7 +139,7 @@
class="bldg" class="bldg"
href={gade.url} href={gade.url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="external noopener noreferrer"
aria-label="{gade.name}{gade.platform}" aria-label="{gade.name}{gade.platform}"
> >
<g class="bldg-art"> <g class="bldg-art">
@@ -172,7 +172,7 @@
class="bldg" class="bldg"
href={bokante.url} href={bokante.url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="external noopener noreferrer"
aria-label="{bokante.name}{bokante.platform}" aria-label="{bokante.name}{bokante.platform}"
> >
<g class="bldg-art"> <g class="bldg-art">
@@ -209,7 +209,7 @@
class="bldg" class="bldg"
href={kute.url} href={kute.url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="external noopener noreferrer"
aria-label="{kute.name}{kute.platform}" aria-label="{kute.name}{kute.platform}"
> >
<g class="bldg-art"> <g class="bldg-art">
@@ -259,7 +259,7 @@
<span class="tag">{item.platform}</span> <span class="tag">{item.platform}</span>
</div> </div>
<p class="vf-desc">{item.description}</p> <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 >{item.linkText}</a
> >
</li> </li>
+1 -1
View File
@@ -9,7 +9,7 @@
<section id="mission" class="section"> <section id="mission" class="section">
<div class="container"> <div class="container">
<h2 class="section-title"> <h2 class="section-title">
<KineticText as="span" text={t.mission.section_title} />{' '} <KineticText as="span" text={t.mission.section_title} />&#32;
<span class="accent-text" <span class="accent-text"
><KineticText as="span" text={t.mission.section_title_accent} /></span ><KineticText as="span" text={t.mission.section_title_accent} /></span
> >
+2 -2
View File
@@ -19,7 +19,7 @@
<section id="partenaires" class="section"> <section id="partenaires" class="section">
<div class="container"> <div class="container">
<h2 class="section-title"> <h2 class="section-title">
<KineticText as="span" text={t.partners.section_title} />{' '} <KineticText as="span" text={t.partners.section_title} />&#32;
<span class="accent-text" <span class="accent-text"
><KineticText as="span" text={t.partners.section_title_accent} /></span ><KineticText as="span" text={t.partners.section_title_accent} /></span
> >
@@ -41,7 +41,7 @@
<p class="partner-description">{partner.description}</p> <p class="partner-description">{partner.description}</p>
<div class="partner-links"> <div class="partner-links">
{#each partner.links as link (link.url)} {#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} {link.text}
</a> </a>
{/each} {/each}
+2 -2
View File
@@ -9,7 +9,7 @@
<section id="projets" class="section"> <section id="projets" class="section">
<div class="container"> <div class="container">
<h2 class="section-title"> <h2 class="section-title">
<KineticText as="span" text={t.projects.section_title} />{' '} <KineticText as="span" text={t.projects.section_title} />&#32;
<span class="accent-text" <span class="accent-text"
><KineticText as="span" text={t.projects.section_title_accent} /></span ><KineticText as="span" text={t.projects.section_title_accent} /></span
> >
@@ -17,7 +17,7 @@
<div class="project-cards"> <div class="project-cards">
{#each t.projects.items as item (item.name)} {#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"> <div class="project-icon">
<svg class="icon" aria-hidden="true" <svg class="icon" aria-hidden="true"
><use href="/icons.svg#{pictoFor(item.icon)}" /></svg ><use href="/icons.svg#{pictoFor(item.icon)}" /></svg
+1 -1
View File
@@ -9,7 +9,7 @@
<section id="solutions" class="section"> <section id="solutions" class="section">
<div class="container"> <div class="container">
<h2 class="section-title"> <h2 class="section-title">
<KineticText as="span" text={t.publics.section_title} />{' '} <KineticText as="span" text={t.publics.section_title} />&#32;
<span class="accent-text" <span class="accent-text"
><KineticText as="span" text={t.publics.section_title_accent} /></span ><KineticText as="span" text={t.publics.section_title_accent} /></span
> >
+4 -2
View File
@@ -4,6 +4,8 @@
* FR par défaut ; EN sur /en/. * FR par défaut ; EN sur /en/.
*/ */
import type { Pathname } from '$app/types';
export type Locale = 'fr' | 'en'; export type Locale = 'fr' | 'en';
/** Bundle de traductions : une entrée par fichier JSON de section. */ /** 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). */ /** 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/'], ['/', '/en/'],
['/dons/', '/en/donate/'], ['/dons/', '/en/donate/'],
['/presse/', '/en/press/'], ['/presse/', '/en/press/'],
@@ -47,7 +49,7 @@ const PATH_PAIRS: Array<[fr: string, en: string]> = [
]; ];
/** Chemin équivalent dans l'autre locale (sans le hash). */ /** 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) { for (const [fr, en] of PATH_PAIRS) {
if (pathname === fr) return target === 'en' ? en : fr; if (pathname === fr) return target === 'en' ? en : fr;
if (pathname === en) return target === 'fr' ? fr : en; if (pathname === en) return target === 'fr' ? fr : en;
+2 -1
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/state'; import { page } from '$app/state';
import { resolve } from '$app/paths';
const status = $derived(page.status); const status = $derived(page.status);
const is404 = $derived(status === 404); const is404 = $derived(status === 404);
@@ -19,7 +20,7 @@
{:else} {:else}
<p class="fr">{page.error?.message ?? 'Une erreur est survenue.'}</p> <p class="fr">{page.error?.message ?? 'Une erreur est survenue.'}</p>
{/if} {/if}
<a class="btn" href="/">Retour à l'accueil</a> <a class="btn" href={resolve('/')}>Retour à l'accueil</a>
</section> </section>
<style> <style>
+5 -4
View File
@@ -1,4 +1,5 @@
<script lang="ts"> <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 Seo from '$lib/components/Seo.svelte';
import donations from '$lib/data/donations.json'; import donations from '$lib/data/donations.json';
import { getBundle } from '$lib/i18n'; import { getBundle } from '$lib/i18n';
@@ -46,7 +47,7 @@
<div class="donation-grid"> <div class="donation-grid">
{#each donations.oneTime as d (d.url)} {#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} {d.amount}{t.support.one_time_suffix}
</a> </a>
{/each} {/each}
@@ -86,7 +87,7 @@
href={d.url} href={d.url}
class="donation-amount monthly" class="donation-amount monthly"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="external noopener noreferrer"
> >
{d.amount}{t.support.monthly_suffix} {d.amount}{t.support.monthly_suffix}
</a> </a>
@@ -126,7 +127,7 @@
<a <a
href={donations.liberapay} href={donations.liberapay}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="external noopener noreferrer"
class="alt-donation-card alt-donation-card--liberapay" class="alt-donation-card alt-donation-card--liberapay"
> >
<svg class="icon alt-donation-icon" aria-hidden="true" <svg class="icon alt-donation-icon" aria-hidden="true"
@@ -139,7 +140,7 @@
<a <a
href={donations.kofi} href={donations.kofi}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="external noopener noreferrer"
class="alt-donation-card alt-donation-card--kofi" class="alt-donation-card alt-donation-card--kofi"
> >
<svg class="icon alt-donation-icon" aria-hidden="true" <svg class="icon alt-donation-icon" aria-hidden="true"
+5 -4
View File
@@ -1,4 +1,5 @@
<script lang="ts"> <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 Seo from '$lib/components/Seo.svelte';
import donations from '$lib/data/donations.json'; import donations from '$lib/data/donations.json';
import { getBundle } from '$lib/i18n'; import { getBundle } from '$lib/i18n';
@@ -46,7 +47,7 @@
<div class="donation-grid"> <div class="donation-grid">
{#each donations.oneTime as d (d.url)} {#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} {d.amount}{t.support.one_time_suffix}
</a> </a>
{/each} {/each}
@@ -86,7 +87,7 @@
href={d.url} href={d.url}
class="donation-amount monthly" class="donation-amount monthly"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="external noopener noreferrer"
> >
{d.amount}{t.support.monthly_suffix} {d.amount}{t.support.monthly_suffix}
</a> </a>
@@ -126,7 +127,7 @@
<a <a
href={donations.liberapay} href={donations.liberapay}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="external noopener noreferrer"
class="alt-donation-card alt-donation-card--liberapay" class="alt-donation-card alt-donation-card--liberapay"
> >
<svg class="icon alt-donation-icon" aria-hidden="true" <svg class="icon alt-donation-icon" aria-hidden="true"
@@ -139,7 +140,7 @@
<a <a
href={donations.kofi} href={donations.kofi}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="external noopener noreferrer"
class="alt-donation-card alt-donation-card--kofi" class="alt-donation-card alt-donation-card--kofi"
> >
<svg class="icon alt-donation-icon" aria-hidden="true" <svg class="icon alt-donation-icon" aria-hidden="true"
+2 -1
View File
@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { resolve } from '$app/paths';
import Seo from '$lib/components/Seo.svelte'; import Seo from '$lib/components/Seo.svelte';
import { getBundle } from '$lib/i18n'; import { getBundle } from '$lib/i18n';
@@ -212,7 +213,7 @@
<!-- Back to Home --> <!-- Back to Home -->
<div class="legal-back"> <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>
</div> </div>
</section> </section>
+5 -4
View File
@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { asset } from '$app/paths';
import ResponsiveImage from '$lib/components/ResponsiveImage.svelte'; import ResponsiveImage from '$lib/components/ResponsiveImage.svelte';
import Seo from '$lib/components/Seo.svelte'; import Seo from '$lib/components/Seo.svelte';
import KineticText from '$lib/components/motion/KineticText.svelte'; import KineticText from '$lib/components/motion/KineticText.svelte';
@@ -48,18 +49,18 @@
<div class="press-visual-grid"> <div class="press-visual-grid">
{#each t.press.visuals as visual, i (visual.pdf)} {#each t.press.visuals as visual, i (visual.pdf)}
<div class="press-visual"> <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" /> <ResponsiveImage picture={pictures[i]} alt={visual.caption} loading="lazy" />
<span class="press-visual-caption">{visual.caption}</span> <span class="press-visual-caption">{visual.caption}</span>
</a> </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} {t.press.visual_pdf_label}
</a> </a>
</div> </div>
{/each} {/each}
</div> </div>
<div class="press-visuals-pdf"> <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} {t.press.visuals_pdf_label}
</a> </a>
</div> </div>
@@ -71,7 +72,7 @@
<KineticText text={t.press.links_title} as="h2" class="press-section-title" /> <KineticText text={t.press.links_title} as="h2" class="press-section-title" />
<div class="press-links-list"> <div class="press-links-list">
{#each t.press.links as link (link.url)} {#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} {/each}
</div> </div>
</div> </div>
+2 -1
View File
@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { resolve } from '$app/paths';
import Seo from '$lib/components/Seo.svelte'; import Seo from '$lib/components/Seo.svelte';
import { getBundle } from '$lib/i18n'; import { getBundle } from '$lib/i18n';
@@ -221,7 +222,7 @@
<!-- Retour à l'accueil --> <!-- Retour à l'accueil -->
<div class="legal-back"> <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>
</div> </div>
</section> </section>
+5 -1
View File
@@ -1,3 +1,7 @@
<script lang="ts">
import { resolve } from '$app/paths';
</script>
<svelte:head> <svelte:head>
<title>Hors ligne · ORGANISATION KA INTERNATIONALE</title> <title>Hors ligne · ORGANISATION KA INTERNATIONALE</title>
<meta name="robots" content="noindex" /> <meta name="robots" content="noindex" />
@@ -12,7 +16,7 @@
rétablie. rétablie.
</p> </p>
<p class="fr" lang="en">This page is not available offline. It will be back as soon as your connection is restored.</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> </section>
<style> <style>
+5 -4
View File
@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { asset } from '$app/paths';
import ResponsiveImage from '$lib/components/ResponsiveImage.svelte'; import ResponsiveImage from '$lib/components/ResponsiveImage.svelte';
import Seo from '$lib/components/Seo.svelte'; import Seo from '$lib/components/Seo.svelte';
import KineticText from '$lib/components/motion/KineticText.svelte'; import KineticText from '$lib/components/motion/KineticText.svelte';
@@ -48,18 +49,18 @@
<div class="press-visual-grid"> <div class="press-visual-grid">
{#each t.press.visuals as visual, i (visual.pdf)} {#each t.press.visuals as visual, i (visual.pdf)}
<div class="press-visual"> <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" /> <ResponsiveImage picture={pictures[i]} alt={visual.caption} loading="lazy" />
<span class="press-visual-caption">{visual.caption}</span> <span class="press-visual-caption">{visual.caption}</span>
</a> </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} {t.press.visual_pdf_label}
</a> </a>
</div> </div>
{/each} {/each}
</div> </div>
<div class="press-visuals-pdf"> <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} {t.press.visuals_pdf_label}
</a> </a>
</div> </div>
@@ -71,7 +72,7 @@
<KineticText text={t.press.links_title} as="h2" class="press-section-title" /> <KineticText text={t.press.links_title} as="h2" class="press-section-title" />
<div class="press-links-list"> <div class="press-links-list">
{#each t.press.links as link (link.url)} {#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} {/each}
</div> </div>
</div> </div>