Compare commits
1 Commits
542323ceaa
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2947482f50 |
@@ -31,13 +31,11 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
env:
|
||||
BASE_PATH: /${{ github.event.repository.name }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: ./_site
|
||||
path: ./build
|
||||
|
||||
deploy:
|
||||
environment:
|
||||
|
||||
@@ -38,3 +38,6 @@ Thumbs.db
|
||||
*.tmp
|
||||
*.temp
|
||||
.cache/
|
||||
# Mémo local (infra, ne pas pousser)
|
||||
GITEA.md
|
||||
qa.tmp.mjs
|
||||
|
||||
@@ -2,6 +2,22 @@
|
||||
|
||||
## 📋 Vue d'ensemble
|
||||
|
||||
**Version 3 (branche `svelte`)** : migration complète 11ty → **SvelteKit 2 + Svelte 5 + TypeScript**, pré-rendu statique intégral (`adapter-static`). Les îlots deviennent des composants natifs avec fallback SSR (fin du montage manuel par `mount.js`/IntersectionObserver). L'i18n Paraglide est remplacée par un routeur maison (`src/lib/i18n/index.ts`) qui consomme **les mêmes catalogues** `src/lib/i18n/{fr,en,ht}.json` (format inlang conservé, éditables avec les outils inlang) : FR à `/`, EN `/en/`, HT `/ht/`, fallback fr automatique. Le sous-répertoire GitHub Pages est géré nativement par `paths.base: '/gwada-sirius'` (les contournements v2 — filtre `withBase`, plugin `restorePassthroughCopy`, alias `/src` — ont disparu). La v2 (11ty) est décrite ci-dessous à titre historique.
|
||||
|
||||
**Ce qui change en v3 par rapport aux notes v2 :**
|
||||
- §1 i18n : plus de compilation Paraglide — les catalogues JSON sont importés tels quels, `t(locale, key, vars?)` gère le fallback ht→fr.
|
||||
- §2 alias `/src` : sans objet (plus de Vite-sur-sortie).
|
||||
- §6 BASE_PATH : remplacé par `paths.base` en dur dans `svelte.config.js` ; liens internes via `resolve()` de `$app/paths`, assets via `%sveltekit.assets%`/`asset()`.
|
||||
- Polices : déclarées une seule fois (`static/fonts/fonts.css`) — la duplication passthrough/bundle de la v2 est corrigée.
|
||||
- `aria-current` : bug corrigé (comparaison pathname réel, voir `Nav.svelte`).
|
||||
- Le piège UTC-4 (§3) reste d'actualité : il vit dans `src/lib/dates.ts` et `SiriusPredictions.svelte` — parse par composants, jamais `new Date("YYYY-MM-DD")`.
|
||||
- §5 cache : la discipline d'incrémentation de `CACHE_NAME` s'applique à `src/service-worker.js` (actuellement `gwada-sirius-v3`).
|
||||
- SEO ajouté en v3 : `Seo.svelte` (canonical, hreflang ×3, OG, Twitter), `static/robots.txt`, `static/sitemap.xml` (15 URLs).
|
||||
|
||||
---
|
||||
|
||||
## Historique — v2 (11ty)
|
||||
|
||||
Refonte complète (v2) : passage d'une SPA Vite+Svelte 4 à un site **11ty** statique avec **Svelte 5 en îlots**, un design system CSS natif (oklch, `@layer`, container queries) et une i18n compilée (Paraglide/inlang). Conforme à la doctrine UI/UX OKI : nav à 5 entrées, fonctionne sans JavaScript pour tout le contenu éditorial, budget JS minimal.
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
@@ -13,41 +13,45 @@ Ce site permet de :
|
||||
|
||||
## 🚀 Stack technique
|
||||
|
||||
- **[11ty (Eleventy)](https://www.11ty.dev/)** — génération du site statique, contenu et navigation en HTML/CSS, fonctionnel sans JavaScript
|
||||
- **[Svelte 5](https://svelte.dev/)** en îlots — hydratation ciblée pour les 3 seuls widgets interactifs (calculateur de prédictions, carte d'observation, globe de la vague planétaire), via `@11ty/eleventy-plugin-vite`
|
||||
- **[Paraglide (inlang)](https://paraglidejs.com/)** — i18n compilée, FR (défaut) / EN / Kreyòl Ayisyen, routée par URL (`/`, `/en/`, `/ht/`)
|
||||
- **CSS natif** — tokens `oklch()`, cascade `@layer`, container queries, aucun framework CSS ni préprocesseur
|
||||
- Polices auto-hébergées (Fraunces, Atkinson Hyperlegible), zéro dépendance à Google Fonts
|
||||
Version 3, reconstruite avec **[SvelteKit 2 + Svelte 5 (runes) + TypeScript](https://svelte.dev/)**, entièrement pré-rendue en statique (`adapter-static`). L'ancienne version 11ty est conservée sur la branche `main`.
|
||||
|
||||
Conforme à la doctrine UI/UX OKI (voir document de référence interne) : navigation à 5 entrées, cibles tactiles ≥ 48 px, contraste AA/AAA, `prefers-reduced-motion` respecté, budget JS minimal.
|
||||
- **SvelteKit statique** — tout le contenu éditorial fonctionne sans JavaScript ; les 3 widgets (calculateur, carte, globe) sont des composants Svelte natifs avec fallback SSR (plus d'îlots montés à la main)
|
||||
- **i18n maison** — les catalogues au format Paraglide/inlang (`src/lib/i18n/{fr,en,ht}.json`, éditables avec les outils inlang) consommés par un routeur par dossiers : FR à `/`, EN sur `/en/`, Kreyòl sur `/ht/` (retombe sur le fr pour le contenu long)
|
||||
- **CSS natif** — tokens `oklch()`, cascade `@layer`, container queries, aucun framework
|
||||
- **Leaflet** en import dynamique côté client uniquement (tuiles OSM — exception documentée à la règle zéro-tiers)
|
||||
- Polices auto-hébergées (Fraunces, Atkinson Hyperlegible), zéro Google Fonts
|
||||
- **PWA** : manifest + service worker stale-while-revalidate (`src/service-worker.js`, incrémenter `CACHE_NAME` à chaque évolution)
|
||||
- **SEO** : canonical, hreflang ×3 + x-default, Open Graph, Twitter Cards (`src/lib/components/Seo.svelte`), `robots.txt` + `sitemap.xml` (15 URLs)
|
||||
|
||||
Conforme à la doctrine UI/UX OKI : navigation à 5 entrées, cibles tactiles ≥ 48 px, contraste AA/AAA, `prefers-reduced-motion` respecté, budget JS minimal.
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev # serveur de développement sur http://localhost:8080
|
||||
npm run build # build de production dans _site/
|
||||
npm run preview # prévisualiser le build de production
|
||||
npm run dev # http://localhost:5173/gwada-sirius/
|
||||
npm run build # build de production dans build/
|
||||
npm run preview # prévisualiser le build
|
||||
npm run check # types + accessibilité (svelte-check)
|
||||
```
|
||||
|
||||
## 🗂️ Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── _data/ # données globales (sites d'observation, navigation)
|
||||
├── _includes/ # layout de base, partiels (nav, footer)
|
||||
├── styles/ # design system CSS (tokens, reset, base, layouts, composants)
|
||||
├── islands/ # composants Svelte 5 hydratés côté client (prédictions, carte, globe)
|
||||
├── paraglide/ # messages i18n compilés (généré, ignoré par git)
|
||||
├── index.njk # accueil
|
||||
├── sirius-science.njk # astronomie + méthode de calcul
|
||||
├── culture-savoirs.njk# héritage culturel + savoir dogon
|
||||
├── observer.njk # calculateur, carte, guide d'observation
|
||||
└── associations.njk # associations qui fêtent le Wep Ronpet
|
||||
messages/ # catalogues de traduction (fr/en/ht), source pour Paraglide
|
||||
project.inlang/ # configuration du projet i18n
|
||||
├── lib/
|
||||
│ ├── i18n/ # catalogues fr/en/ht (format Paraglide) + routeur localisé
|
||||
│ ├── data/ # sites d'observation, villes de la vague, prochain lever, nav
|
||||
│ ├── styles/ # design system CSS (tokens, base, layouts, composants, thème oki)
|
||||
│ ├── components/ # Nav, Footer, Seo, pages/ (une par page), islands/ (3 widgets)
|
||||
│ └── dates.ts # formatage local des dates (piège UTC-4, voir NOTES_PROJET)
|
||||
├── routes/ # / (fr), en/, ht/ — 5 pages par locale
|
||||
└── service-worker.js # PWA (stale-while-revalidate)
|
||||
static/ # favicon.svg, manifest.webmanifest, robots.txt, sitemap.xml, fonts/
|
||||
```
|
||||
|
||||
Le déploiement GitHub Pages (sous-répertoire `/gwada-sirius/`) est géré par `paths.base` dans `svelte.config.js` + `.github/workflows/deploy.yml`.
|
||||
|
||||
## 👥 Crédits
|
||||
|
||||
Projet réalisé avec ❤️ par [cyber-mawonaj](https://github.com/cyber-mawonaj) et [OKI](https://o-k-i.net).
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import EleventyVitePlugin from "@11ty/eleventy-plugin-vite";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
import * as m from "./src/paraglide/messages.js";
|
||||
import { locales, baseLocale } from "./src/paraglide/runtime.js";
|
||||
|
||||
const OUTPUT_DIR = "_site";
|
||||
|
||||
// GitHub Pages project sites are served under /<repo-name>/, not at the
|
||||
// domain root — set BASE_PATH (no trailing slash, e.g. "/gwada-sirius")
|
||||
// in CI to prefix internal links and Vite's own asset URLs accordingly.
|
||||
// Left empty for local dev and for a future custom-domain deployment.
|
||||
const BASE_PATH = (process.env.BASE_PATH || "").replace(/\/+$/, "");
|
||||
|
||||
// Vite's build wipes the output dir (emptyOutDir) after 11ty has already
|
||||
// copied passthrough assets into it — re-copy them once Vite is done.
|
||||
// https://github.com/11ty/eleventy-plugin-vite/issues/42
|
||||
function restorePassthroughCopy(copies) {
|
||||
return {
|
||||
name: "restore-passthrough-copy",
|
||||
apply: "build",
|
||||
closeBundle() {
|
||||
for (const [from, to] of copies) {
|
||||
if (!fs.existsSync(from)) continue;
|
||||
fs.cpSync(from, path.join(OUTPUT_DIR, to), {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function (eleventyConfig) {
|
||||
eleventyConfig.addPlugin(EleventyVitePlugin, {
|
||||
viteOptions: {
|
||||
appType: "mpa",
|
||||
plugins: [
|
||||
svelte(),
|
||||
restorePassthroughCopy([
|
||||
["src/assets", "assets"],
|
||||
["public", "."],
|
||||
]),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"/src": path.resolve(".", "src"),
|
||||
},
|
||||
},
|
||||
base: BASE_PATH ? `${BASE_PATH}/` : "/",
|
||||
build: {
|
||||
emptyOutDir: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
eleventyConfig.ignores.add("src/paraglide/**");
|
||||
eleventyConfig.ignores.add("src/islands/**");
|
||||
|
||||
eleventyConfig.addPassthroughCopy("src/assets");
|
||||
eleventyConfig.addPassthroughCopy({ public: "." });
|
||||
|
||||
// Trilingual message lookup for Nunjucks templates: {{ "hero_title1" | t(locale) }}
|
||||
eleventyConfig.addFilter("t", (key, locale, params = {}) => {
|
||||
const fn = m[key];
|
||||
if (!fn) return key;
|
||||
return fn(params, { locale });
|
||||
});
|
||||
|
||||
eleventyConfig.addGlobalData("locales", locales);
|
||||
eleventyConfig.addGlobalData("baseLocale", baseLocale);
|
||||
eleventyConfig.addGlobalData("currentYear", () => new Date().getFullYear());
|
||||
|
||||
eleventyConfig.addGlobalData("basePath", BASE_PATH);
|
||||
|
||||
eleventyConfig.addFilter("localeHref", (pagePath, locale) => {
|
||||
const clean = pagePath.replace(/^\/+/, "");
|
||||
const localePath = locale === baseLocale ? `/${clean}` : `/${locale}/${clean}`;
|
||||
return BASE_PATH + localePath;
|
||||
});
|
||||
|
||||
// For static/passthrough assets referenced by absolute path outside of
|
||||
// Vite's own bundling (favicon, manifest, service worker, font preloads —
|
||||
// anything marked vite-ignore in layouts/base.njk).
|
||||
eleventyConfig.addFilter("withBase", (assetPath) => BASE_PATH + assetPath);
|
||||
|
||||
eleventyConfig.addFilter("dump", (value) => JSON.stringify(value).replace(/</g, "\\u003c"));
|
||||
|
||||
eleventyConfig.addFilter("formatDate", (isoDate, locale, options = {}) => {
|
||||
// new Date("YYYY-MM-DD") parses as UTC midnight, which can roll back a
|
||||
// day once formatted in a timezone behind UTC (e.g. Guadeloupe, UTC-4).
|
||||
// Build the date from its local components instead.
|
||||
const [year, month, day] = isoDate.split("-").map(Number);
|
||||
return new Date(year, month - 1, day).toLocaleDateString(
|
||||
locale === "ht" ? "fr" : locale,
|
||||
{ day: "numeric", month: "long", year: "numeric", ...options }
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
dir: {
|
||||
input: "src",
|
||||
output: OUTPUT_DIR,
|
||||
includes: "_includes",
|
||||
data: "_data",
|
||||
},
|
||||
templateFormats: ["njk", "md"],
|
||||
markdownTemplateEngine: "njk",
|
||||
htmlTemplateEngine: "njk",
|
||||
};
|
||||
}
|
||||
Generated
+333
-2483
File diff suppressed because it is too large
Load Diff
+15
-15
@@ -1,24 +1,24 @@
|
||||
{
|
||||
"name": "gwada-sirius",
|
||||
"version": "2.0.0",
|
||||
"version": "3.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"messages": "paraglide-js compile --project ./project.inlang --outdir ./src/paraglide",
|
||||
"dev": "npm run messages && eleventy --serve",
|
||||
"build": "npm run messages && eleventy",
|
||||
"preview": "npx serve _site",
|
||||
"deploy": "npm run build && gh-pages -d _site"
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^3.1.6",
|
||||
"@11ty/eleventy-plugin-vite": "^8.0.0",
|
||||
"@fontsource-variable/fraunces": "^5.2.9",
|
||||
"@fontsource/atkinson-hyperlegible": "^5.2.8",
|
||||
"@inlang/paraglide-js": "^2.20.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^7.1.4",
|
||||
"gh-pages": "^6.1.1",
|
||||
"svelte": "^5.56.4",
|
||||
"vite": "^8.1.3"
|
||||
"@sveltejs/adapter-static": "^3.0.10",
|
||||
"@sveltejs/kit": "^2.70.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^7.2.0",
|
||||
"@types/leaflet": "^1.9.21",
|
||||
"svelte": "^5.56.7",
|
||||
"svelte-check": "^4.7.3",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.1.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"leaflet": "^1.9.4"
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"$schema": "https://inlang.com/schema/project-settings",
|
||||
"baseLocale": "fr",
|
||||
"locales": ["fr", "en", "ht"],
|
||||
"modules": [
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js"
|
||||
],
|
||||
"plugin.inlang.messageFormat": {
|
||||
"pathPattern": "./messages/{locale}.json"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "Sirius en Guadeloupe — Wep Ronpet",
|
||||
"short_name": "Sirius Gwada",
|
||||
"description": "Lever héliaque de Sirius en Guadeloupe : astronomie, culture et observation.",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#141a33",
|
||||
"theme_color": "#141a33",
|
||||
"icons": [
|
||||
{ "src": "favicon.svg", "sizes": "any", "type": "image/svg+xml" }
|
||||
]
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Service worker minimal : résilience réseau (3G/4G irrégulier) et usage
|
||||
// hors-ligne des pages déjà visitées. Stratégie stale-while-revalidate.
|
||||
const CACHE_NAME = "gwada-sirius-v2";
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(
|
||||
caches.keys().then((keys) =>
|
||||
Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)))
|
||||
)
|
||||
);
|
||||
self.clients.claim();
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
if (event.request.method !== "GET") return;
|
||||
const url = new URL(event.request.url);
|
||||
if (url.origin !== self.location.origin) return;
|
||||
|
||||
event.respondWith(
|
||||
caches.open(CACHE_NAME).then(async (cache) => {
|
||||
const cached = await cache.match(event.request);
|
||||
const network = fetch(event.request)
|
||||
.then((response) => {
|
||||
if (response.ok) cache.put(event.request, response.clone());
|
||||
return response;
|
||||
})
|
||||
.catch(() => cached);
|
||||
|
||||
return cached || network;
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="{{ locale }}" data-theme="oki">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ pageTitle }} — {{ "meta_title" | t(locale) }}</title>
|
||||
<meta name="description" content="{{ 'meta_description' | t(locale) }}">
|
||||
<link vite-ignore rel="icon" href="{{ '/favicon.svg' | withBase }}" type="image/svg+xml">
|
||||
<link vite-ignore rel="manifest" href="{{ '/manifest.webmanifest' | withBase }}">
|
||||
|
||||
{% for loc in locales %}
|
||||
<link vite-ignore rel="alternate" hreflang="{{ loc }}" href="{{ slug | localeHref(loc) }}">
|
||||
{% endfor %}
|
||||
|
||||
<link vite-ignore rel="preload" href="{{ '/assets/fonts/fraunces-variable.woff2' | withBase }}" as="font" type="font/woff2" crossorigin>
|
||||
<link vite-ignore rel="preload" href="{{ '/assets/fonts/atkinson-400.woff2' | withBase }}" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="stylesheet" href="/src/styles/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main-content">{{ "skip_to_content" | t(locale) }}</a>
|
||||
|
||||
{% include "partials/nav.njk" %}
|
||||
|
||||
<main id="main-content">
|
||||
{{ content | safe }}
|
||||
</main>
|
||||
|
||||
{% include "partials/footer.njk" %}
|
||||
|
||||
<script>
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", () => navigator.serviceWorker.register("{{ '/sw.js' | withBase }}"));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,49 +0,0 @@
|
||||
<footer class="site-footer">
|
||||
<div class="container footer-grid">
|
||||
<div class="footer-section">
|
||||
<h2>Sirius Gwada</h2>
|
||||
<p>{{ "footer_tagline" | t(locale) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<h3>{{ "footer_links_title" | t(locale) }}</h3>
|
||||
<ul>
|
||||
{% for item in nav %}
|
||||
<li><a href="{{ item.path | localeHref(locale) }}">{{ item.key | t(locale) }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<h3>{{ "footer_resources_title" | t(locale) }}</h3>
|
||||
<ul>
|
||||
<li><a href="https://stellarium.org" target="_blank" rel="noopener">{{ "footer_resources_stellarium" | t(locale) }}</a></li>
|
||||
<li><a href="{{ '/observer/' | localeHref(locale) }}">{{ "footer_resources_guide" | t(locale) }}</a></li>
|
||||
<li><a href="{{ '/sirius-science/' | localeHref(locale) }}">{{ "footer_resources_references" | t(locale) }}</a></li>
|
||||
<li><a href="mailto:contact@o-k-i.net">{{ "footer_resources_contact" | t(locale) }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<h3>{{ "footer_next_title" | t(locale) }}</h3>
|
||||
<div class="countdown">
|
||||
<div class="countdown-item">
|
||||
<span class="countdown-value">{{ nextRising.date | formatDate(locale, { day: "numeric", month: "long" }) }}</span>
|
||||
<span class="countdown-label">{{ nextRising.time }} · {{ nextRising.year }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container footer-bottom">
|
||||
<p class="pattern-strip kente" aria-hidden="true"></p>
|
||||
<p>© {{ currentYear }} {{ "footer_rights" | t(locale) }}</p>
|
||||
<p class="footer-credits">
|
||||
{{ "footer_copyright_prefix" | t(locale) }}
|
||||
<a href="https://github.com/cyber-mawonaj" target="_blank" rel="noopener">cyber-mawonaj</a>
|
||||
{{ "footer_and" | t(locale) }}
|
||||
<a href="https://o-k-i.net" target="_blank" rel="noopener">OKI</a>
|
||||
</p>
|
||||
<p class="footer-quote">{{ "footer_quote" | t(locale) }}</p>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -1,36 +0,0 @@
|
||||
<header class="nav">
|
||||
<div class="container nav-inner">
|
||||
<a class="nav-brand" href="{{ '' | localeHref(locale) }}">
|
||||
<span aria-hidden="true">☥</span> Sirius Gwada
|
||||
</a>
|
||||
|
||||
<details class="nav-disclosure">
|
||||
<summary class="nav-toggle">
|
||||
<span class="visually-hidden">Menu</span>
|
||||
<span aria-hidden="true" class="nav-toggle-icon"></span>
|
||||
</summary>
|
||||
<ul class="nav-menu">
|
||||
{% for item in nav %}
|
||||
<li>
|
||||
<a href="{{ item.path | localeHref(locale) }}"
|
||||
{% if pageKey == item.key %}aria-current="page"{% endif %}>
|
||||
{{ item.key | t(locale) }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
<ul class="nav-lang">
|
||||
{% for loc in locales %}
|
||||
<li>
|
||||
<a href="{{ slug | localeHref(loc) }}"
|
||||
lang="{{ loc }}"
|
||||
{% if loc == locale %}aria-current="true" class="active"{% endif %}>
|
||||
{{ ("lang_name_" + loc) | t(locale) }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,21 @@
|
||||
<!doctype html>
|
||||
<html lang="%lang%" data-theme="oki">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/svg+xml" href="%sveltekit.assets%/favicon.svg" />
|
||||
<link rel="manifest" href="%sveltekit.assets%/manifest.webmanifest" />
|
||||
<link
|
||||
rel="preload"
|
||||
href="%sveltekit.assets%/fonts/fraunces-variable.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossorigin
|
||||
/>
|
||||
<link rel="stylesheet" href="%sveltekit.assets%/fonts/fonts.css" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,79 +0,0 @@
|
||||
---
|
||||
pagination:
|
||||
data: locales
|
||||
size: 1
|
||||
alias: locale
|
||||
permalink: "{% if locale == baseLocale %}/associations/index.html{% else %}/{{ locale }}/associations/index.html{% endif %}"
|
||||
layout: layouts/base.njk
|
||||
pageKey: associations
|
||||
slug: "associations/"
|
||||
eleventyComputed:
|
||||
pageTitle: "{{ 'associations_title' | t(locale) }}"
|
||||
---
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="section-header center">
|
||||
<p class="section-eyebrow">Wep Ronpet</p>
|
||||
<h1>{{ "associations_title" | t(locale) }}</h1>
|
||||
<p class="pattern-strip bogolan" aria-hidden="true" style="margin-inline:auto"></p>
|
||||
<p class="section-subtitle">{{ "associations_subtitle" | t(locale) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid-auto">
|
||||
<div class="card">
|
||||
<h2>{{ "associations_events_title" | t(locale) }}</h2>
|
||||
<div class="stack" style="--stack-space: 0">
|
||||
<a class="event-link" href="https://my.weezevent.com/nouvel-an-kamite" target="_blank" rel="noopener noreferrer">
|
||||
<span class="event-icon" aria-hidden="true">🎊</span>
|
||||
<span class="event-info">
|
||||
<span class="event-title">{{ "associations_event1_title" | t(locale) }}</span>
|
||||
<span class="event-platform">{{ "associations_event1_organizer" | t(locale) }}</span>
|
||||
<span>{{ "associations_event1_desc" | t(locale) }}</span>
|
||||
</span>
|
||||
</a>
|
||||
<a class="event-link" href="https://my.weezevent.com/nouvel-an-kamit" target="_blank" rel="noopener noreferrer">
|
||||
<span class="event-icon" aria-hidden="true">✨</span>
|
||||
<span class="event-info">
|
||||
<span class="event-title">{{ "associations_event2_title" | t(locale) }}</span>
|
||||
<span class="event-platform">{{ "associations_event2_organizer" | t(locale) }}</span>
|
||||
<span>{{ "associations_event2_desc" | t(locale) }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>{{ "associations_community_title" | t(locale) }}</h2>
|
||||
<div class="stack" style="--stack-space: 0">
|
||||
<a class="community-link" href="https://14eme-membre.net/site-web/#événements" target="_blank" rel="noopener noreferrer">
|
||||
<span class="community-icon" aria-hidden="true">🌍</span>
|
||||
<span class="community-info">
|
||||
<span class="community-title">{{ "associations_link1_title" | t(locale) }}</span>
|
||||
<span>{{ "associations_link1_desc" | t(locale) }}</span>
|
||||
</span>
|
||||
</a>
|
||||
<a class="community-link" href="https://www.facebook.com/people/Mouvement-Politique-Spirituel-MUN" target="_blank" rel="noopener noreferrer">
|
||||
<span class="community-icon" aria-hidden="true">📘</span>
|
||||
<span class="community-info">
|
||||
<span class="community-title">{{ "associations_link2_title" | t(locale) }}</span>
|
||||
<span>{{ "associations_link2_desc" | t(locale) }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="center" style="margin-top: var(--space-7)">{{ "associations_video_title" | t(locale) }}</h2>
|
||||
<div class="video-frame card" style="margin-top: var(--space-4)">
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/0_8kWlDTf94"
|
||||
title="{{ 'associations_video_title' | t(locale) }}"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen
|
||||
loading="lazy"
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
<p class="associations-note center">{{ "associations_note" | t(locale) }}</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,192 +0,0 @@
|
||||
---
|
||||
pagination:
|
||||
data: locales
|
||||
size: 1
|
||||
alias: locale
|
||||
permalink: "{% if locale == baseLocale %}/culture-savoirs/index.html{% else %}/{{ locale }}/culture-savoirs/index.html{% endif %}"
|
||||
layout: layouts/base.njk
|
||||
pageKey: culture
|
||||
slug: "culture-savoirs/"
|
||||
eleventyComputed:
|
||||
pageTitle: "{{ 'culture_title' | t(locale) }}"
|
||||
---
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="section-header center">
|
||||
<p class="section-eyebrow">Sirius Gwada</p>
|
||||
<h1>{{ "culture_title" | t(locale) }}</h1>
|
||||
<p class="pattern-strip adinkra" aria-hidden="true" style="margin-inline:auto"></p>
|
||||
<p class="section-subtitle">{{ "culture_subtitle" | t(locale) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid-auto">
|
||||
<div class="feature-card">
|
||||
<div style="font-size: var(--text-4)" aria-hidden="true">🏺</div>
|
||||
<h3>{{ "culture_egypt_title" | t(locale) }}</h3>
|
||||
<p>{{ "culture_egypt_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div style="font-size: var(--text-4)" aria-hidden="true">🌍</div>
|
||||
<h3>{{ "culture_dogon_title" | t(locale) }}</h3>
|
||||
<p>{{ "culture_dogon_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div style="font-size: var(--text-4)" aria-hidden="true">⛵</div>
|
||||
<h3>{{ "culture_caribbean_title" | t(locale) }}</h3>
|
||||
<p>{{ "culture_caribbean_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="center" style="margin-top: var(--space-7)">{{ "culture_timeline_title" | t(locale) }}</h2>
|
||||
<div class="timeline-v" style="margin-top: var(--space-5)">
|
||||
<div class="timeline-v-item">
|
||||
<p class="timeline-v-date">{{ "culture_timeline_1_date" | t(locale) }}</p>
|
||||
<h4>{{ "culture_timeline_1_title" | t(locale) }}</h4>
|
||||
<p>{{ "culture_timeline_1_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="timeline-v-item">
|
||||
<p class="timeline-v-date">{{ "culture_timeline_2_date" | t(locale) }}</p>
|
||||
<h4>{{ "culture_timeline_2_title" | t(locale) }}</h4>
|
||||
<p>{{ "culture_timeline_2_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="timeline-v-item">
|
||||
<p class="timeline-v-date">{{ "culture_timeline_3_date" | t(locale) }}</p>
|
||||
<h4>{{ "culture_timeline_3_title" | t(locale) }}</h4>
|
||||
<p>{{ "culture_timeline_3_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="timeline-v-item">
|
||||
<p class="timeline-v-date">{{ "culture_timeline_4_date" | t(locale) }}</p>
|
||||
<h4>{{ "culture_timeline_4_title" | t(locale) }}</h4>
|
||||
<p>{{ "culture_timeline_4_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" style="background: var(--surface-sunk)">
|
||||
<div class="container">
|
||||
<div class="section-header center">
|
||||
<h2>{{ "dogon_title" | t(locale) }}</h2>
|
||||
<p class="pattern-strip kente" aria-hidden="true" style="margin-inline:auto"></p>
|
||||
<p class="section-subtitle">{{ "dogon_intro" | t(locale) }}</p>
|
||||
</div>
|
||||
|
||||
<fieldset class="tabs">
|
||||
<legend class="visually-hidden">{{ "dogon_title" | t(locale) }}</legend>
|
||||
|
||||
<input type="radio" name="dogon-tab" id="dogon-tab-cosmogony" class="tab-input" checked>
|
||||
<input type="radio" name="dogon-tab" id="dogon-tab-system" class="tab-input">
|
||||
<input type="radio" name="dogon-tab" id="dogon-tab-astronomy" class="tab-input">
|
||||
<input type="radio" name="dogon-tab" id="dogon-tab-heritage" class="tab-input">
|
||||
|
||||
<div class="tab-list" role="tablist">
|
||||
<label class="tab-label" for="dogon-tab-cosmogony">{{ "dogon_tab_cosmogony" | t(locale) }}</label>
|
||||
<label class="tab-label" for="dogon-tab-system">{{ "dogon_tab_system" | t(locale) }}</label>
|
||||
<label class="tab-label" for="dogon-tab-astronomy">{{ "dogon_tab_astronomy" | t(locale) }}</label>
|
||||
<label class="tab-label" for="dogon-tab-heritage">{{ "dogon_tab_heritage" | t(locale) }}</label>
|
||||
</div>
|
||||
|
||||
<div class="tab-panels">
|
||||
<div class="tab-panel prose center">
|
||||
<p>{{ "dogon_cosmogony_p1" | t(locale) }}</p>
|
||||
<p>{{ "dogon_cosmogony_p2" | t(locale) }}</p>
|
||||
<div class="card">
|
||||
<h3>{{ "dogon_cosmogony_nommo_title" | t(locale) }}</h3>
|
||||
<p>{{ "dogon_cosmogony_nommo_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel">
|
||||
<div class="grid-auto">
|
||||
<div class="info-card">
|
||||
<h3>{{ "dogon_system_sigutolo_title" | t(locale) }}</h3>
|
||||
<p>{{ "dogon_system_sigutolo_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3>{{ "dogon_system_potolo_title" | t(locale) }}</h3>
|
||||
<p>{{ "dogon_system_potolo_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3>{{ "dogon_system_emmeya_title" | t(locale) }}</h3>
|
||||
<p>{{ "dogon_system_emmeya_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card prose center" style="margin-top: var(--space-4)">
|
||||
<h3>{{ "dogon_mystery_title" | t(locale) }}</h3>
|
||||
<div class="grid-auto">
|
||||
<div>
|
||||
<h4>{{ "dogon_mystery_theory1_title" | t(locale) }}</h4>
|
||||
<p>{{ "dogon_mystery_theory1_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>{{ "dogon_mystery_theory2_title" | t(locale) }}</h4>
|
||||
<p>{{ "dogon_mystery_theory2_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>{{ "dogon_mystery_theory3_title" | t(locale) }}</h4>
|
||||
<p>{{ "dogon_mystery_theory3_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card prose center" style="margin-top: var(--space-4)">
|
||||
<h3>{{ "dogon_calc_example_title" | t(locale) }}</h3>
|
||||
<p>{{ "dogon_calc_example_text" | t(locale) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel">
|
||||
<div class="grid-auto">
|
||||
<div class="info-card">
|
||||
<h3>{{ "dogon_astronomy_geometry_title" | t(locale) }}</h3>
|
||||
<p>{{ "dogon_astronomy_geometry_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3>{{ "dogon_astronomy_calendar_title" | t(locale) }}</h3>
|
||||
<p>{{ "dogon_astronomy_calendar_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3>{{ "dogon_astronomy_planets_title" | t(locale) }}</h3>
|
||||
<p>{{ "dogon_astronomy_planets_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3>{{ "dogon_astronomy_universe_title" | t(locale) }}</h3>
|
||||
<p>{{ "dogon_astronomy_universe_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel">
|
||||
<div class="timeline-v">
|
||||
<div class="timeline-v-item">
|
||||
<p class="timeline-v-date">{{ "dogon_heritage_1_date" | t(locale) }}</p>
|
||||
<p>{{ "dogon_heritage_1_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="timeline-v-item">
|
||||
<p class="timeline-v-date">{{ "dogon_heritage_2_date" | t(locale) }}</p>
|
||||
<p>{{ "dogon_heritage_2_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="timeline-v-item">
|
||||
<p class="timeline-v-date">{{ "dogon_heritage_3_date" | t(locale) }}</p>
|
||||
<p>{{ "dogon_heritage_3_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="timeline-v-item">
|
||||
<p class="timeline-v-date">{{ "dogon_heritage_4_date" | t(locale) }}</p>
|
||||
<p>{{ "dogon_heritage_4_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card prose center" style="margin-top: var(--space-5)">
|
||||
<h3>{{ "dogon_heritage_diaspora_title" | t(locale) }}</h3>
|
||||
<p>{{ "dogon_heritage_diaspora_desc" | t(locale) }}</p>
|
||||
<div class="grid-auto" style="margin-top: var(--space-3); text-align: left">
|
||||
<p>🇭🇹 {{ "dogon_heritage_diaspora_haiti" | t(locale) }}</p>
|
||||
<p>🇬🇵 {{ "dogon_heritage_diaspora_guadeloupe" | t(locale) }}</p>
|
||||
<p>🇲🇶 {{ "dogon_heritage_diaspora_martinique" | t(locale) }}</p>
|
||||
<p>🇯🇲 {{ "dogon_heritage_diaspora_jamaica" | t(locale) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,14 @@
|
||||
import { base } from '$app/paths';
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
|
||||
const LANGS = ['fr', 'en', 'ht'] as const;
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
// event.url.pathname inclut le base path (/gwada-sirius/…) — le retirer d'abord
|
||||
const stripped = event.url.pathname.slice(base.length);
|
||||
const segment = stripped.split('/').filter(Boolean)[0];
|
||||
const lang = LANGS.includes(segment as (typeof LANGS)[number]) ? segment : 'fr';
|
||||
return resolve(event, {
|
||||
transformPageChunk: ({ html }) => html.replaceAll('%lang%', lang)
|
||||
});
|
||||
};
|
||||
@@ -1,93 +0,0 @@
|
||||
---
|
||||
pagination:
|
||||
data: locales
|
||||
size: 1
|
||||
alias: locale
|
||||
permalink: "{% if locale == baseLocale %}/index.html{% else %}/{{ locale }}/index.html{% endif %}"
|
||||
layout: layouts/base.njk
|
||||
pageKey: home
|
||||
slug: ""
|
||||
eleventyComputed:
|
||||
pageTitle: "{{ 'hero_title1' | t(locale) }} {{ 'hero_title2' | t(locale) }}"
|
||||
---
|
||||
<section class="hero">
|
||||
<div class="hero-orbits" aria-hidden="true">
|
||||
<div class="sun"></div>
|
||||
<div class="orbit" style="width:5rem;height:5rem;animation-duration:6s"><span></span></div>
|
||||
<div class="orbit" style="width:8rem;height:8rem;animation-duration:10s"><span></span></div>
|
||||
<div class="orbit" style="width:11rem;height:11rem;animation-duration:14s"><span></span></div>
|
||||
<div class="orbit" style="width:14rem;height:14rem;animation-duration:18s"><span></span></div>
|
||||
<div class="orbit sirius" style="width:18rem;height:18rem;animation-duration:26s"><span></span></div>
|
||||
</div>
|
||||
|
||||
<div class="container center hero-content">
|
||||
<p class="section-eyebrow">{{ "hero_eyebrow" | t(locale) }}</p>
|
||||
<h1 class="hero-title">
|
||||
<span>{{ "hero_title1" | t(locale) }}</span>
|
||||
<span class="accent">{{ "hero_title2" | t(locale) }}</span>
|
||||
</h1>
|
||||
<p class="hero-subtitle">{{ "hero_subtitle" | t(locale) }}</p>
|
||||
<p class="hero-description">{{ "hero_description" | t(locale) }}</p>
|
||||
<p class="hero-date">
|
||||
{{ "hero_date_label" | t(locale) }}
|
||||
<strong>{{ nextRising.date | formatDate(locale) }} · {{ nextRising.time }}</strong>
|
||||
</p>
|
||||
<div class="cluster hero-cta">
|
||||
<a class="btn btn-primary comet-ring" href="{{ '/observer/' | localeHref(locale) }}">{{ "hero_cta_predictions" | t(locale) }}</a>
|
||||
<a class="btn btn-outline" href="{{ '/sirius-science/' | localeHref(locale) }}">{{ "hero_cta_learn" | t(locale) }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="intro">
|
||||
<div class="container">
|
||||
<div class="section-header center">
|
||||
<h2>{{ "home_intro_title" | t(locale) }}</h2>
|
||||
<p class="pattern-strip kente" aria-hidden="true" style="margin-inline:auto"></p>
|
||||
<p class="section-subtitle">{{ "home_intro_subtitle" | t(locale) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="prose center stack" style="--stack-space: var(--space-3)">
|
||||
<p>{{ "home_intro_p1" | t(locale) | safe }}</p>
|
||||
<p>{{ "home_intro_p2" | t(locale) }}</p>
|
||||
<p>{{ "home_intro_p3" | t(locale) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid-auto" style="margin-top: var(--space-6)">
|
||||
<div class="feature-card">
|
||||
<h3>{{ "feature_brightest_title" | t(locale) }}</h3>
|
||||
<p>{{ "feature_brightest_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>{{ "feature_rising_title" | t(locale) }}</h3>
|
||||
<p>{{ "feature_rising_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>{{ "feature_heritage_title" | t(locale) }}</h3>
|
||||
<p>{{ "feature_heritage_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" style="background: var(--surface-sunk)">
|
||||
<div class="container">
|
||||
<div class="section-header center">
|
||||
<h2>{{ "wave_showcase_title" | t(locale) }}</h2>
|
||||
<p class="section-subtitle">{{ "wave_showcase_subtitle" | t(locale) }}</p>
|
||||
</div>
|
||||
|
||||
<ul class="wave-badges">
|
||||
{% for city in waveCities %}
|
||||
<li class="wave-badge wave-badge-{{ loop.index0 % 3 }}">
|
||||
<span class="wave-badge-name">{{ city.name }}</span>
|
||||
<span class="wave-badge-date">{{ city.date }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<p class="center" style="margin-top: var(--space-5)">
|
||||
<a class="btn btn-outline" href="{{ '/observer/' | localeHref(locale) }}">{{ "wave_showcase_cta" | t(locale) }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,81 +0,0 @@
|
||||
<script>
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
import L from "leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import { waveCities } from "./world.js";
|
||||
|
||||
let mapContainer = $state();
|
||||
let map;
|
||||
|
||||
function markerIcon(active) {
|
||||
return L.divIcon({
|
||||
className: "wave-marker" + (active ? " wave-marker-active" : ""),
|
||||
iconSize: active ? [20, 20] : [14, 14],
|
||||
iconAnchor: active ? [10, 10] : [7, 7],
|
||||
});
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
map = L.map(mapContainer, { scrollWheelZoom: false });
|
||||
|
||||
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
maxZoom: 18,
|
||||
}).addTo(map);
|
||||
|
||||
const markers = waveCities.map((city) => {
|
||||
const isCaribbean = city.name === "Guadeloupe" || city.name === "Martinique";
|
||||
const marker = L.marker([city.lat, city.lon], {
|
||||
icon: markerIcon(isCaribbean),
|
||||
}).addTo(map);
|
||||
marker.bindPopup(`<strong>${city.name}</strong><br>${city.date}`);
|
||||
marker.on("popupopen", () => marker.setIcon(markerIcon(true)));
|
||||
marker.on("popupclose", () => marker.setIcon(markerIcon(isCaribbean)));
|
||||
return marker;
|
||||
});
|
||||
|
||||
const group = L.featureGroup(markers);
|
||||
map.fitBounds(group.getBounds().pad(0.15));
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
map?.remove();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={mapContainer}
|
||||
class="wave-map"
|
||||
role="group"
|
||||
aria-label="Carte de la progression du lever héliaque à travers l'Afrique et les Caraïbes"
|
||||
></div>
|
||||
|
||||
<style>
|
||||
.wave-map {
|
||||
width: 100%;
|
||||
height: 22rem;
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:global(.wave-marker) {
|
||||
background: var(--caribbean-teal);
|
||||
border: 2px solid oklch(20% 0.03 265);
|
||||
border-radius: 50%;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
:global(.wave-marker-active) {
|
||||
background: var(--gold-surface);
|
||||
}
|
||||
|
||||
:global(.leaflet-popup-content-wrapper) {
|
||||
background: var(--surface-raised);
|
||||
color: var(--ink);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
:global(.leaflet-popup-tip) {
|
||||
background: var(--surface-raised);
|
||||
}
|
||||
</style>
|
||||
@@ -1,24 +0,0 @@
|
||||
const container = document.querySelector('[data-island="globe"]');
|
||||
|
||||
if (container && "IntersectionObserver" in window) {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
for (const entry of entries) {
|
||||
if (!entry.isIntersecting) continue;
|
||||
observer.disconnect();
|
||||
|
||||
Promise.all([
|
||||
import("svelte"),
|
||||
import("./SiriusGlobe.svelte"),
|
||||
]).then(([{ mount }, { default: SiriusGlobe }]) => {
|
||||
const target = container.querySelector(".island-app");
|
||||
mount(SiriusGlobe, { target });
|
||||
container.classList.add("is-enhanced");
|
||||
});
|
||||
}
|
||||
},
|
||||
{ rootMargin: "200px" }
|
||||
);
|
||||
|
||||
observer.observe(container);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default as waveCities } from "../../_data/waveCities.js";
|
||||
@@ -1,81 +0,0 @@
|
||||
<script>
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
import L from "leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
|
||||
let { sites, locale } = $props();
|
||||
|
||||
let mapContainer = $state();
|
||||
let map;
|
||||
|
||||
function markerIcon(active) {
|
||||
return L.divIcon({
|
||||
className: "site-marker" + (active ? " site-marker-active" : ""),
|
||||
iconSize: active ? [22, 22] : [16, 16],
|
||||
iconAnchor: active ? [11, 11] : [8, 8],
|
||||
});
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
map = L.map(mapContainer, {
|
||||
scrollWheelZoom: false,
|
||||
});
|
||||
|
||||
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
maxZoom: 18,
|
||||
}).addTo(map);
|
||||
|
||||
const markers = sites.map((site) => {
|
||||
const marker = L.marker([site.lat, site.lon], { icon: markerIcon(false) }).addTo(map);
|
||||
const desc = site[`description_${locale}`] ?? site.description_fr;
|
||||
marker.bindPopup(
|
||||
`<strong>${site.name}</strong><br>${desc}<br><small style="color: var(--ink-dim)">${site.lat}°N, ${site.lon}°O — ${site.alt} m</small>`
|
||||
);
|
||||
marker.on("popupopen", () => marker.setIcon(markerIcon(true)));
|
||||
marker.on("popupclose", () => marker.setIcon(markerIcon(false)));
|
||||
return marker;
|
||||
});
|
||||
|
||||
const group = L.featureGroup(markers);
|
||||
map.fitBounds(group.getBounds().pad(0.2));
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
map?.remove();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="observation-map">
|
||||
<div bind:this={mapContainer} class="leaflet-container-host" role="group" aria-label="Carte des sites d'observation de Sirius en Guadeloupe"></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.leaflet-container-host {
|
||||
width: 100%;
|
||||
height: 22rem;
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:global(.site-marker) {
|
||||
background: var(--gold-surface);
|
||||
border: 2px solid oklch(20% 0.03 265);
|
||||
border-radius: 50%;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
:global(.site-marker-active) {
|
||||
background: var(--caribbean-teal);
|
||||
}
|
||||
|
||||
:global(.leaflet-popup-content-wrapper) {
|
||||
background: var(--surface-raised);
|
||||
color: var(--ink);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
:global(.leaflet-popup-tip) {
|
||||
background: var(--surface-raised);
|
||||
}
|
||||
</style>
|
||||
@@ -1,31 +0,0 @@
|
||||
const container = document.querySelector('[data-island="map"]');
|
||||
|
||||
if (container && "IntersectionObserver" in window) {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
for (const entry of entries) {
|
||||
if (!entry.isIntersecting) continue;
|
||||
observer.disconnect();
|
||||
|
||||
Promise.all([
|
||||
import("svelte"),
|
||||
import("./ObservationMap.svelte"),
|
||||
]).then(([{ mount }, { default: ObservationMap }]) => {
|
||||
const target = container.querySelector(".island-app");
|
||||
const sitesData = JSON.parse(document.getElementById("sites-data").textContent);
|
||||
const locale = document.documentElement.lang;
|
||||
|
||||
mount(ObservationMap, {
|
||||
target,
|
||||
props: { sites: sitesData, locale },
|
||||
});
|
||||
|
||||
container.classList.add("is-enhanced");
|
||||
});
|
||||
}
|
||||
},
|
||||
{ rootMargin: "200px" }
|
||||
);
|
||||
|
||||
observer.observe(container);
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
<script>
|
||||
import { untrack } from "svelte";
|
||||
|
||||
let { sites, year, locale, labels } = $props();
|
||||
|
||||
let selectedId = $state(untrack(() => sites[0].id));
|
||||
|
||||
let selected = $derived(sites.find((s) => s.id === selectedId));
|
||||
let prediction = $derived(selected.predictions[year]);
|
||||
|
||||
function formatDate(iso) {
|
||||
const [y, m, d] = iso.split("-").map(Number);
|
||||
return new Date(y, m - 1, d).toLocaleDateString(locale === "ht" ? "fr" : locale, {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
function windowLabel(iso) {
|
||||
const [y, m, d] = iso.split("-").map(Number);
|
||||
const start = new Date(y, m - 1, d - 2);
|
||||
const end = new Date(y, m - 1, d + 2);
|
||||
const fmt = (date) => date.toLocaleDateString(locale === "ht" ? "fr" : locale, { day: "numeric", month: "short" });
|
||||
return `${fmt(start)} – ${fmt(end)}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="calculator card">
|
||||
<label class="calculator-label" for="site-select">{labels.select}</label>
|
||||
<select id="site-select" bind:value={selectedId} class="calculator-select">
|
||||
{#each sites as site}
|
||||
<option value={site.id}>{site.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
||||
{#if prediction}
|
||||
<div class="calculator-result">
|
||||
<p class="calculator-result-label">{labels.heliacal}</p>
|
||||
<p class="calculator-result-value">{formatDate(prediction.date)} · {prediction.time}</p>
|
||||
<p class="calculator-result-label">{labels.window}</p>
|
||||
<p class="calculator-result-window">{windowLabel(prediction.date)}</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.calculator {
|
||||
max-width: 28rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.calculator-label {
|
||||
display: block;
|
||||
font-weight: 700;
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.calculator-select {
|
||||
width: 100%;
|
||||
min-height: var(--tap-target);
|
||||
padding-inline: var(--space-3);
|
||||
border-radius: var(--radius-sm);
|
||||
border: 2px solid var(--border);
|
||||
background: var(--surface);
|
||||
color: var(--ink);
|
||||
font-size: var(--text-1);
|
||||
}
|
||||
|
||||
.calculator-result {
|
||||
margin-top: var(--space-4);
|
||||
padding-top: var(--space-4);
|
||||
border-top: 1px solid var(--border);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.calculator-result-label {
|
||||
font-size: var(--text-0);
|
||||
color: var(--ink-soft);
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.calculator-result-value {
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--text-3);
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
.calculator-result-window {
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -1,24 +0,0 @@
|
||||
import { mount } from "svelte";
|
||||
import PredictionsCalculator from "./PredictionsCalculator.svelte";
|
||||
|
||||
const container = document.querySelector('[data-island="predictions"]');
|
||||
if (container) {
|
||||
const target = container.querySelector(".island-app");
|
||||
const sitesData = JSON.parse(document.getElementById("sites-data").textContent);
|
||||
|
||||
mount(PredictionsCalculator, {
|
||||
target,
|
||||
props: {
|
||||
sites: sitesData,
|
||||
year: Number(target.dataset.year),
|
||||
locale: target.dataset.locale,
|
||||
labels: {
|
||||
select: target.dataset.selectLabel,
|
||||
heliacal: target.dataset.heliacalLabel,
|
||||
window: target.dataset.windowLabel,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
container.classList.add("is-enhanced");
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import nav from '$lib/data/nav.js';
|
||||
import nextRising from '$lib/data/nextRising.js';
|
||||
import { formatDate } from '$lib/dates';
|
||||
import { localizedPath, t, type Locale } from '$lib/i18n';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
const { locale }: Props = $props();
|
||||
|
||||
// Calculé au build (prerender) — voir NOTES_PROJET §3 pour la logique dates.
|
||||
const rising = nextRising();
|
||||
const currentYear = new Date().getFullYear();
|
||||
</script>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="container footer-grid">
|
||||
<div class="footer-section">
|
||||
<h2>Sirius Gwada</h2>
|
||||
<p>{t(locale, 'footer_tagline')}</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<h3>{t(locale, 'footer_links_title')}</h3>
|
||||
<ul>
|
||||
{#each nav as item (item.key)}
|
||||
<li>
|
||||
<a href={resolve(localizedPath(locale, item.path))}>{t(locale, item.key)}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<h3>{t(locale, 'footer_resources_title')}</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://stellarium.org" target="_blank" rel="noopener"
|
||||
>{t(locale, 'footer_resources_stellarium')}</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href={resolve(localizedPath(locale, '/observer/'))}
|
||||
>{t(locale, 'footer_resources_guide')}</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href={resolve(localizedPath(locale, '/sirius-science/'))}
|
||||
>{t(locale, 'footer_resources_references')}</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="mailto:contact@o-k-i.net">{t(locale, 'footer_resources_contact')}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<h3>{t(locale, 'footer_next_title')}</h3>
|
||||
<div class="countdown">
|
||||
<div class="countdown-item">
|
||||
<span class="countdown-value"
|
||||
>{formatDate(rising.date, locale, { day: 'numeric', month: 'long' })}</span
|
||||
>
|
||||
<span class="countdown-label">{rising.time} · {rising.year}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container footer-bottom">
|
||||
<p class="pattern-strip kente" aria-hidden="true"></p>
|
||||
<p>© {currentYear} {t(locale, 'footer_rights')}</p>
|
||||
<p class="footer-credits">
|
||||
{t(locale, 'footer_copyright_prefix')}
|
||||
<a href="https://github.com/cyber-mawonaj" target="_blank" rel="noopener">cyber-mawonaj</a>
|
||||
{t(locale, 'footer_and')}
|
||||
<a href="https://o-k-i.net" target="_blank" rel="noopener">OKI</a>
|
||||
</p>
|
||||
<p class="footer-quote">{t(locale, 'footer_quote')}</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
/* Liens dans le corps de texte : distinguables autrement que par la couleur (WCAG) */
|
||||
.footer-credits a {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 0.15em;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,78 @@
|
||||
<script lang="ts">
|
||||
import { base, resolve } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import nav from '$lib/data/nav.js';
|
||||
import {
|
||||
LOCALES,
|
||||
localizedPath,
|
||||
switchLocalePath,
|
||||
t,
|
||||
type Locale
|
||||
} from '$lib/i18n';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
const { locale }: Props = $props();
|
||||
|
||||
// Pathname courant sans le base path (/gwada-sirius), pour comparer aux
|
||||
// chemins de nav et basculer de locale.
|
||||
const pathname = $derived.by(() => {
|
||||
const p = page.url.pathname.startsWith(base)
|
||||
? page.url.pathname.slice(base.length)
|
||||
: page.url.pathname;
|
||||
return p || '/';
|
||||
});
|
||||
|
||||
const normalize = (s: string) => s.replace(/\/+$/, '') || '/';
|
||||
|
||||
// Correction du bug aria-current du legacy : l'ancien partial comparait
|
||||
// `pageKey` (« home ») avec `item.key` (« nav_home ») — toujours faux,
|
||||
// l'entrée active n'était jamais marquée. On compare ici le pathname
|
||||
// réel au chemin localisé de l'entrée.
|
||||
const isCurrent = (path: string) =>
|
||||
normalize(pathname) === normalize(localizedPath(locale, path));
|
||||
</script>
|
||||
|
||||
<header class="nav">
|
||||
<div class="container nav-inner">
|
||||
<a class="nav-brand" href={resolve(localizedPath(locale, '/'))}>
|
||||
<span aria-hidden="true">☥</span> Sirius Gwada
|
||||
</a>
|
||||
|
||||
<details class="nav-disclosure">
|
||||
<summary class="nav-toggle">
|
||||
<span class="visually-hidden">Menu</span>
|
||||
<span aria-hidden="true" class="nav-toggle-icon"></span>
|
||||
</summary>
|
||||
<ul class="nav-menu">
|
||||
{#each nav as item (item.key)}
|
||||
<li>
|
||||
<a
|
||||
href={resolve(localizedPath(locale, item.path))}
|
||||
aria-current={isCurrent(item.path) ? 'page' : undefined}
|
||||
>
|
||||
{t(locale, item.key)}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
<ul class="nav-lang">
|
||||
{#each LOCALES as loc (loc)}
|
||||
<li>
|
||||
<a
|
||||
href={resolve(switchLocalePath(pathname, loc))}
|
||||
lang={loc}
|
||||
aria-current={loc === locale ? 'true' : undefined}
|
||||
class:active={loc === locale}
|
||||
>
|
||||
{t(locale, `lang_name_${loc}`)}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
@@ -0,0 +1,51 @@
|
||||
<script lang="ts">
|
||||
import { LOCALES, localizedPath, type Locale } from '$lib/i18n';
|
||||
|
||||
/**
|
||||
* Métadonnées SEO d'une page : title, description, canonical absolu,
|
||||
* hreflang ×3 (+ x-default), Open Graph et Twitter summary.
|
||||
* Pas d'og:image : le site n'a pas d'image de partage.
|
||||
*/
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
title: string;
|
||||
description: string;
|
||||
/** Chemin de la page sans locale, ex. `/observer/`. */
|
||||
path: string;
|
||||
}
|
||||
|
||||
const { locale, title, description, path }: Props = $props();
|
||||
|
||||
// URL de production (GitHub Pages, inclut déjà le base path /gwada-sirius).
|
||||
const SITE = 'https://cyber-mawonaj.github.io/gwada-sirius';
|
||||
|
||||
const OG_LOCALES: Record<Locale, string> = {
|
||||
fr: 'fr_FR',
|
||||
en: 'en_US',
|
||||
ht: 'ht_HT'
|
||||
};
|
||||
|
||||
const canonical = $derived(SITE + localizedPath(locale, path));
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={description} />
|
||||
<link rel="canonical" href={canonical} />
|
||||
|
||||
{#each LOCALES as loc (loc)}
|
||||
<link rel="alternate" hreflang={loc} href={SITE + localizedPath(loc, path)} />
|
||||
{/each}
|
||||
<link rel="alternate" hreflang="x-default" href={SITE + localizedPath('fr', path)} />
|
||||
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={canonical} />
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:locale" content={OG_LOCALES[locale]} />
|
||||
<meta property="og:site_name" content="Sirius Gwada" />
|
||||
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
</svelte:head>
|
||||
@@ -0,0 +1,138 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import type { Map as LeafletMap } from 'leaflet';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
import sites from '$lib/data/sites.js';
|
||||
|
||||
let { locale }: { locale: Locale } = $props();
|
||||
|
||||
let mapContainer: HTMLDivElement | undefined = $state();
|
||||
let enhanced = $state(false);
|
||||
|
||||
function description(site: (typeof sites)[number]): string {
|
||||
const desc = site as unknown as Record<string, string | undefined>;
|
||||
return desc[`description_${locale}`] ?? site.description_fr;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
let cancelled = false;
|
||||
let map: LeafletMap | undefined;
|
||||
|
||||
(async () => {
|
||||
// Leaflet touche `window` à l'évaluation : import dynamique, jamais de SSR.
|
||||
const L = await import('leaflet');
|
||||
if (cancelled || !mapContainer) return;
|
||||
|
||||
const markerIcon = (active: boolean) =>
|
||||
L.divIcon({
|
||||
className: 'site-marker' + (active ? ' site-marker-active' : ''),
|
||||
iconSize: active ? [22, 22] : [16, 16],
|
||||
iconAnchor: active ? [11, 11] : [8, 8]
|
||||
});
|
||||
|
||||
map = L.map(mapContainer, {
|
||||
scrollWheelZoom: false
|
||||
});
|
||||
|
||||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution:
|
||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
maxZoom: 18
|
||||
}).addTo(map);
|
||||
|
||||
const markers = sites.map((site) => {
|
||||
const marker = L.marker([site.lat, site.lon], { icon: markerIcon(false) }).addTo(
|
||||
map as LeafletMap
|
||||
);
|
||||
marker.bindPopup(
|
||||
`<strong>${site.name}</strong><br>${description(site)}<br><small style="color: var(--ink-dim)">${site.lat}°N, ${site.lon}°O — ${site.alt} m</small>`
|
||||
);
|
||||
marker.on('popupopen', () => marker.setIcon(markerIcon(true)));
|
||||
marker.on('popupclose', () => marker.setIcon(markerIcon(false)));
|
||||
return marker;
|
||||
});
|
||||
|
||||
const group = L.featureGroup(markers);
|
||||
map.fitBounds(group.getBounds().pad(0.2));
|
||||
enhanced = true;
|
||||
})();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
map?.remove();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="observation-map">
|
||||
<div
|
||||
bind:this={mapContainer}
|
||||
class="leaflet-container-host"
|
||||
role="group"
|
||||
aria-label={t(locale, 'observer_map_title')}
|
||||
></div>
|
||||
|
||||
<!-- Repli no-JS : liste statique des sites, retirée une fois la carte montée. -->
|
||||
{#if !enhanced}
|
||||
<ul class="site-list grid-auto">
|
||||
{#each sites as site (site.id)}
|
||||
<li class="card">
|
||||
<h3>{site.name}</h3>
|
||||
<p>{description(site)}</p>
|
||||
<p class="coords">{site.lat}°N, {site.lon}°O — {site.alt} m</p>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Hauteur fixe identique au legacy : réserve l'espace dès le SSR (zéro CLS). */
|
||||
.leaflet-container-host {
|
||||
width: 100%;
|
||||
height: 22rem;
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
background: var(--surface-sunk);
|
||||
}
|
||||
|
||||
.site-list {
|
||||
margin-top: var(--space-4);
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.coords {
|
||||
font-size: var(--text-0);
|
||||
color: var(--ink-dim);
|
||||
}
|
||||
|
||||
:global(.site-marker) {
|
||||
background: var(--gold-surface);
|
||||
border: 2px solid oklch(20% 0.03 265);
|
||||
border-radius: 50%;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
:global(.site-marker-active) {
|
||||
background: var(--caribbean-teal);
|
||||
}
|
||||
|
||||
:global(.leaflet-popup-content-wrapper) {
|
||||
background: var(--surface-raised);
|
||||
color: var(--ink);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
:global(.leaflet-popup-tip) {
|
||||
background: var(--surface-raised);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.observation-map :global(.leaflet-fade-anim),
|
||||
.observation-map :global(.leaflet-popup),
|
||||
.observation-map :global(.leaflet-zoom-anim) {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,133 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import type { Map as LeafletMap } from 'leaflet';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
import waveCities from '$lib/data/waveCities.js';
|
||||
|
||||
let { locale }: { locale: Locale } = $props();
|
||||
|
||||
let mapContainer: HTMLDivElement | undefined = $state();
|
||||
let enhanced = $state(false);
|
||||
|
||||
function isCaribbean(city: (typeof waveCities)[number]): boolean {
|
||||
return city.name === 'Guadeloupe' || city.name === 'Martinique';
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
let cancelled = false;
|
||||
let map: LeafletMap | undefined;
|
||||
|
||||
(async () => {
|
||||
// Leaflet touche `window` à l'évaluation : import dynamique, jamais de SSR.
|
||||
const L = await import('leaflet');
|
||||
if (cancelled || !mapContainer) return;
|
||||
|
||||
const markerIcon = (active: boolean) =>
|
||||
L.divIcon({
|
||||
className: 'wave-marker' + (active ? ' wave-marker-active' : ''),
|
||||
iconSize: active ? [20, 20] : [14, 14],
|
||||
iconAnchor: active ? [10, 10] : [7, 7]
|
||||
});
|
||||
|
||||
map = L.map(mapContainer, { scrollWheelZoom: false });
|
||||
|
||||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution:
|
||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
maxZoom: 18
|
||||
}).addTo(map);
|
||||
|
||||
const markers = waveCities.map((city) => {
|
||||
const marker = L.marker([city.lat, city.lon], {
|
||||
icon: markerIcon(isCaribbean(city))
|
||||
}).addTo(map as LeafletMap);
|
||||
marker.bindPopup(`<strong>${city.name}</strong><br>${city.date}`);
|
||||
marker.on('popupopen', () => marker.setIcon(markerIcon(true)));
|
||||
marker.on('popupclose', () => marker.setIcon(markerIcon(isCaribbean(city))));
|
||||
return marker;
|
||||
});
|
||||
|
||||
const group = L.featureGroup(markers);
|
||||
map.fitBounds(group.getBounds().pad(0.15));
|
||||
enhanced = true;
|
||||
})();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
map?.remove();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="sirius-globe">
|
||||
<div
|
||||
bind:this={mapContainer}
|
||||
class="wave-map"
|
||||
role="group"
|
||||
aria-label={t(locale, 'globe_title')}
|
||||
></div>
|
||||
|
||||
<!-- Repli no-JS : liste statique de la vague (ville — date), retirée une
|
||||
fois la carte montée. -->
|
||||
{#if !enhanced}
|
||||
<ul class="wave-cities grid-auto">
|
||||
{#each waveCities as city (city.name)}
|
||||
<li class="card">
|
||||
<h3>{city.name}</h3>
|
||||
<p class="wave-date">{city.date}</p>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Hauteur fixe identique au legacy : réserve l'espace dès le SSR (zéro CLS). */
|
||||
.wave-map {
|
||||
width: 100%;
|
||||
height: 22rem;
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
background: var(--surface-sunk);
|
||||
}
|
||||
|
||||
.wave-cities {
|
||||
margin-top: var(--space-4);
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.wave-date {
|
||||
font-size: var(--text-0);
|
||||
color: var(--ink-dim);
|
||||
}
|
||||
|
||||
:global(.wave-marker) {
|
||||
background: var(--caribbean-teal);
|
||||
border: 2px solid oklch(20% 0.03 265);
|
||||
border-radius: 50%;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
:global(.wave-marker-active) {
|
||||
background: var(--gold-surface);
|
||||
}
|
||||
|
||||
:global(.leaflet-popup-content-wrapper) {
|
||||
background: var(--surface-raised);
|
||||
color: var(--ink);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
:global(.leaflet-popup-tip) {
|
||||
background: var(--surface-raised);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.sirius-globe :global(.leaflet-fade-anim),
|
||||
.sirius-globe :global(.leaflet-popup),
|
||||
.sirius-globe :global(.leaflet-zoom-anim) {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,195 @@
|
||||
<script lang="ts">
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
import sites from '$lib/data/sites.js';
|
||||
import nextRising from '$lib/data/nextRising.js';
|
||||
|
||||
let { locale }: { locale: Locale } = $props();
|
||||
|
||||
type Prediction = { date: string; time: string };
|
||||
|
||||
// Année du prochain lever héliaque (calculée au build, comme le 11ty legacy).
|
||||
const { year } = nextRising();
|
||||
|
||||
let selectedId = $state(sites[0].id);
|
||||
|
||||
let selected = $derived(sites.find((s) => s.id === selectedId) ?? sites[0]);
|
||||
let prediction = $derived(
|
||||
(selected.predictions as unknown as Record<number, Prediction>)[year]
|
||||
);
|
||||
|
||||
function predictionOf(site: (typeof sites)[number]): Prediction | undefined {
|
||||
return (site.predictions as unknown as Record<number, Prediction>)[year];
|
||||
}
|
||||
|
||||
function description(site: (typeof sites)[number]): string {
|
||||
const desc = site as unknown as Record<string, string | undefined>;
|
||||
return desc[`description_${locale}`] ?? site.description_fr;
|
||||
}
|
||||
|
||||
// Piège UTC-4 (NOTES_PROJET §3) : une date ISO sans heure est interprétée en
|
||||
// UTC et peut reculer d'un jour une fois formatée en heure de Guadeloupe.
|
||||
// Toujours parser les composants et construire la date en heure locale.
|
||||
function formatDate(iso: string): string {
|
||||
const [y, m, d] = iso.split('-').map(Number);
|
||||
return new Date(y, m - 1, d).toLocaleDateString(locale === 'ht' ? 'fr' : locale, {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
});
|
||||
}
|
||||
|
||||
function windowLabel(iso: string): string {
|
||||
const [y, m, d] = iso.split('-').map(Number);
|
||||
const start = new Date(y, m - 1, d - 2);
|
||||
const end = new Date(y, m - 1, d + 2);
|
||||
const fmt = (date: Date) =>
|
||||
date.toLocaleDateString(locale === 'ht' ? 'fr' : locale, {
|
||||
day: 'numeric',
|
||||
month: 'short'
|
||||
});
|
||||
return `${fmt(start)} – ${fmt(end)}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="calculator card">
|
||||
<label class="calculator-label" for="site-select"
|
||||
>{t(locale, 'observer_calculator_select_label')}</label
|
||||
>
|
||||
<select id="site-select" bind:value={selectedId} class="calculator-select">
|
||||
{#each sites as site (site.id)}
|
||||
<option value={site.id}>{site.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
||||
{#if prediction}
|
||||
<div class="calculator-result" aria-live="polite">
|
||||
<p class="calculator-result-label">{t(locale, 'observer_calculator_result_heliacal')}</p>
|
||||
<p class="calculator-result-value">{formatDate(prediction.date)} · {prediction.time}</p>
|
||||
<p class="calculator-result-label">{t(locale, 'observer_calculator_result_window')}</p>
|
||||
<p class="calculator-result-window">{windowLabel(prediction.date)}</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Table complète : toujours rendue (SSR), c'est le repli no-JS. À
|
||||
l'hydratation, la ligne du site sélectionné est mise en valeur. -->
|
||||
<div class="predictions-table card">
|
||||
<table>
|
||||
<caption class="visually-hidden">{t(locale, 'observer_calculator_title')}</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{t(locale, 'observer_calculator_table_site')}</th>
|
||||
<th scope="col">{t(locale, 'observer_calculator_table_date')}</th>
|
||||
<th scope="col">{t(locale, 'observer_calculator_table_time')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each sites as site (site.id)}
|
||||
{@const p = predictionOf(site)}
|
||||
<tr class:selected={site.id === selectedId}>
|
||||
<th scope="row">
|
||||
{site.name}
|
||||
<div class="site-desc">{description(site)}</div>
|
||||
</th>
|
||||
{#if p}
|
||||
<td>{formatDate(p.date)}</td>
|
||||
<td>{p.time}</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.calculator {
|
||||
max-width: 28rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.calculator-label {
|
||||
display: block;
|
||||
font-weight: 700;
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.calculator-select {
|
||||
width: 100%;
|
||||
min-height: var(--tap-target);
|
||||
padding-inline: var(--space-3);
|
||||
border-radius: var(--radius-sm);
|
||||
border: 2px solid var(--border);
|
||||
background: var(--surface);
|
||||
color: var(--ink);
|
||||
font-size: var(--text-1);
|
||||
}
|
||||
|
||||
.calculator-result {
|
||||
margin-top: var(--space-4);
|
||||
padding-top: var(--space-4);
|
||||
border-top: 1px solid var(--border);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.calculator-result-label {
|
||||
font-size: var(--text-0);
|
||||
color: var(--ink-soft);
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.calculator-result-value {
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--text-3);
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
.calculator-result-window {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.predictions-table {
|
||||
overflow-x: auto;
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
thead tr {
|
||||
text-align: left;
|
||||
border-bottom: 2px solid var(--border);
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: var(--space-2);
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
border-bottom: 1px solid var(--border);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
tbody tr.selected {
|
||||
background: var(--gold-surface);
|
||||
}
|
||||
|
||||
tbody th {
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.site-desc {
|
||||
font-size: var(--text-0);
|
||||
color: var(--ink-soft);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
tbody tr {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<script lang="ts">
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
let { locale }: { locale: Locale } = $props();
|
||||
</script>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="section-header center">
|
||||
<p class="section-eyebrow">Wep Ronpet</p>
|
||||
<h1>{t(locale, 'associations_title')}</h1>
|
||||
<p class="pattern-strip bogolan" aria-hidden="true" style="margin-inline:auto"></p>
|
||||
<p class="section-subtitle">{t(locale, 'associations_subtitle')}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid-auto">
|
||||
<div class="card">
|
||||
<h2>{t(locale, 'associations_events_title')}</h2>
|
||||
<div class="stack" style="--stack-space: 0">
|
||||
<a
|
||||
class="event-link"
|
||||
href="https://my.weezevent.com/nouvel-an-kamite"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span class="event-icon" aria-hidden="true">🎊</span>
|
||||
<span class="event-info">
|
||||
<span class="event-title">{t(locale, 'associations_event1_title')}</span>
|
||||
<span class="event-platform">{t(locale, 'associations_event1_organizer')}</span>
|
||||
<span>{t(locale, 'associations_event1_desc')}</span>
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
class="event-link"
|
||||
href="https://my.weezevent.com/nouvel-an-kamit"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span class="event-icon" aria-hidden="true">✨</span>
|
||||
<span class="event-info">
|
||||
<span class="event-title">{t(locale, 'associations_event2_title')}</span>
|
||||
<span class="event-platform">{t(locale, 'associations_event2_organizer')}</span>
|
||||
<span>{t(locale, 'associations_event2_desc')}</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>{t(locale, 'associations_community_title')}</h2>
|
||||
<div class="stack" style="--stack-space: 0">
|
||||
<a
|
||||
class="community-link"
|
||||
href="https://14eme-membre.net/site-web/#événements"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span class="community-icon" aria-hidden="true">🌍</span>
|
||||
<span class="community-info">
|
||||
<span class="community-title">{t(locale, 'associations_link1_title')}</span>
|
||||
<span>{t(locale, 'associations_link1_desc')}</span>
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
class="community-link"
|
||||
href="https://www.facebook.com/people/Mouvement-Politique-Spirituel-MUN"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span class="community-icon" aria-hidden="true">📘</span>
|
||||
<span class="community-info">
|
||||
<span class="community-title">{t(locale, 'associations_link2_title')}</span>
|
||||
<span>{t(locale, 'associations_link2_desc')}</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="center" style="margin-top: var(--space-7)">{t(locale, 'associations_video_title')}</h2>
|
||||
<div class="video-frame card" style="margin-top: var(--space-4)">
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/0_8kWlDTf94"
|
||||
title={t(locale, 'associations_video_title')}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen
|
||||
loading="lazy"
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
<p class="associations-note center">{t(locale, 'associations_note')}</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,186 @@
|
||||
<script lang="ts">
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
let { locale }: { locale: Locale } = $props();
|
||||
</script>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="section-header center">
|
||||
<p class="section-eyebrow">Sirius Gwada</p>
|
||||
<h1>{t(locale, 'culture_title')}</h1>
|
||||
<p class="pattern-strip adinkra" aria-hidden="true" style="margin-inline:auto"></p>
|
||||
<p class="section-subtitle">{t(locale, 'culture_subtitle')}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid-auto">
|
||||
<div class="feature-card">
|
||||
<div style="font-size: var(--text-4)" aria-hidden="true">🏺</div>
|
||||
<h3>{t(locale, 'culture_egypt_title')}</h3>
|
||||
<p>{t(locale, 'culture_egypt_desc')}</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div style="font-size: var(--text-4)" aria-hidden="true">🌍</div>
|
||||
<h3>{t(locale, 'culture_dogon_title')}</h3>
|
||||
<p>{t(locale, 'culture_dogon_desc')}</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div style="font-size: var(--text-4)" aria-hidden="true">⛵</div>
|
||||
<h3>{t(locale, 'culture_caribbean_title')}</h3>
|
||||
<p>{t(locale, 'culture_caribbean_desc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="center" style="margin-top: var(--space-7)">{t(locale, 'culture_timeline_title')}</h2>
|
||||
<div class="timeline-v" style="margin-top: var(--space-5)">
|
||||
<div class="timeline-v-item">
|
||||
<p class="timeline-v-date">{t(locale, 'culture_timeline_1_date')}</p>
|
||||
<h4>{t(locale, 'culture_timeline_1_title')}</h4>
|
||||
<p>{t(locale, 'culture_timeline_1_desc')}</p>
|
||||
</div>
|
||||
<div class="timeline-v-item">
|
||||
<p class="timeline-v-date">{t(locale, 'culture_timeline_2_date')}</p>
|
||||
<h4>{t(locale, 'culture_timeline_2_title')}</h4>
|
||||
<p>{t(locale, 'culture_timeline_2_desc')}</p>
|
||||
</div>
|
||||
<div class="timeline-v-item">
|
||||
<p class="timeline-v-date">{t(locale, 'culture_timeline_3_date')}</p>
|
||||
<h4>{t(locale, 'culture_timeline_3_title')}</h4>
|
||||
<p>{t(locale, 'culture_timeline_3_desc')}</p>
|
||||
</div>
|
||||
<div class="timeline-v-item">
|
||||
<p class="timeline-v-date">{t(locale, 'culture_timeline_4_date')}</p>
|
||||
<h4>{t(locale, 'culture_timeline_4_title')}</h4>
|
||||
<p>{t(locale, 'culture_timeline_4_desc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" style="background: var(--surface-sunk)">
|
||||
<div class="container">
|
||||
<div class="section-header center">
|
||||
<h2>{t(locale, 'dogon_title')}</h2>
|
||||
<p class="pattern-strip kente" aria-hidden="true" style="margin-inline:auto"></p>
|
||||
<p class="section-subtitle">{t(locale, 'dogon_intro')}</p>
|
||||
</div>
|
||||
|
||||
<fieldset class="tabs">
|
||||
<legend class="visually-hidden">{t(locale, 'dogon_title')}</legend>
|
||||
|
||||
<input type="radio" name="dogon-tab" id="dogon-tab-cosmogony" class="tab-input" checked />
|
||||
<input type="radio" name="dogon-tab" id="dogon-tab-system" class="tab-input" />
|
||||
<input type="radio" name="dogon-tab" id="dogon-tab-astronomy" class="tab-input" />
|
||||
<input type="radio" name="dogon-tab" id="dogon-tab-heritage" class="tab-input" />
|
||||
|
||||
<div class="tab-list" role="tablist">
|
||||
<label class="tab-label" for="dogon-tab-cosmogony">{t(locale, 'dogon_tab_cosmogony')}</label>
|
||||
<label class="tab-label" for="dogon-tab-system">{t(locale, 'dogon_tab_system')}</label>
|
||||
<label class="tab-label" for="dogon-tab-astronomy">{t(locale, 'dogon_tab_astronomy')}</label>
|
||||
<label class="tab-label" for="dogon-tab-heritage">{t(locale, 'dogon_tab_heritage')}</label>
|
||||
</div>
|
||||
|
||||
<div class="tab-panels">
|
||||
<div class="tab-panel prose center">
|
||||
<p>{t(locale, 'dogon_cosmogony_p1')}</p>
|
||||
<p>{t(locale, 'dogon_cosmogony_p2')}</p>
|
||||
<div class="card">
|
||||
<h3>{t(locale, 'dogon_cosmogony_nommo_title')}</h3>
|
||||
<p>{t(locale, 'dogon_cosmogony_nommo_desc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel">
|
||||
<div class="grid-auto">
|
||||
<div class="info-card">
|
||||
<h3>{t(locale, 'dogon_system_sigutolo_title')}</h3>
|
||||
<p>{t(locale, 'dogon_system_sigutolo_desc')}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3>{t(locale, 'dogon_system_potolo_title')}</h3>
|
||||
<p>{t(locale, 'dogon_system_potolo_desc')}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3>{t(locale, 'dogon_system_emmeya_title')}</h3>
|
||||
<p>{t(locale, 'dogon_system_emmeya_desc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card prose center" style="margin-top: var(--space-4)">
|
||||
<h3>{t(locale, 'dogon_mystery_title')}</h3>
|
||||
<div class="grid-auto">
|
||||
<div>
|
||||
<h4>{t(locale, 'dogon_mystery_theory1_title')}</h4>
|
||||
<p>{t(locale, 'dogon_mystery_theory1_desc')}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>{t(locale, 'dogon_mystery_theory2_title')}</h4>
|
||||
<p>{t(locale, 'dogon_mystery_theory2_desc')}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>{t(locale, 'dogon_mystery_theory3_title')}</h4>
|
||||
<p>{t(locale, 'dogon_mystery_theory3_desc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card prose center" style="margin-top: var(--space-4)">
|
||||
<h3>{t(locale, 'dogon_calc_example_title')}</h3>
|
||||
<p>{t(locale, 'dogon_calc_example_text')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel">
|
||||
<div class="grid-auto">
|
||||
<div class="info-card">
|
||||
<h3>{t(locale, 'dogon_astronomy_geometry_title')}</h3>
|
||||
<p>{t(locale, 'dogon_astronomy_geometry_desc')}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3>{t(locale, 'dogon_astronomy_calendar_title')}</h3>
|
||||
<p>{t(locale, 'dogon_astronomy_calendar_desc')}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3>{t(locale, 'dogon_astronomy_planets_title')}</h3>
|
||||
<p>{t(locale, 'dogon_astronomy_planets_desc')}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3>{t(locale, 'dogon_astronomy_universe_title')}</h3>
|
||||
<p>{t(locale, 'dogon_astronomy_universe_desc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel">
|
||||
<div class="timeline-v">
|
||||
<div class="timeline-v-item">
|
||||
<p class="timeline-v-date">{t(locale, 'dogon_heritage_1_date')}</p>
|
||||
<p>{t(locale, 'dogon_heritage_1_desc')}</p>
|
||||
</div>
|
||||
<div class="timeline-v-item">
|
||||
<p class="timeline-v-date">{t(locale, 'dogon_heritage_2_date')}</p>
|
||||
<p>{t(locale, 'dogon_heritage_2_desc')}</p>
|
||||
</div>
|
||||
<div class="timeline-v-item">
|
||||
<p class="timeline-v-date">{t(locale, 'dogon_heritage_3_date')}</p>
|
||||
<p>{t(locale, 'dogon_heritage_3_desc')}</p>
|
||||
</div>
|
||||
<div class="timeline-v-item">
|
||||
<p class="timeline-v-date">{t(locale, 'dogon_heritage_4_date')}</p>
|
||||
<p>{t(locale, 'dogon_heritage_4_desc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card prose center" style="margin-top: var(--space-5)">
|
||||
<h3>{t(locale, 'dogon_heritage_diaspora_title')}</h3>
|
||||
<p>{t(locale, 'dogon_heritage_diaspora_desc')}</p>
|
||||
<div class="grid-auto" style="margin-top: var(--space-3); text-align: left">
|
||||
<p>🇭🇹 {t(locale, 'dogon_heritage_diaspora_haiti')}</p>
|
||||
<p>🇬🇵 {t(locale, 'dogon_heritage_diaspora_guadeloupe')}</p>
|
||||
<p>🇲🇶 {t(locale, 'dogon_heritage_diaspora_martinique')}</p>
|
||||
<p>🇯🇲 {t(locale, 'dogon_heritage_diaspora_jamaica')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,133 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import nextRising from '$lib/data/nextRising.js';
|
||||
import waveCities from '$lib/data/waveCities.js';
|
||||
import { formatDate } from '$lib/dates';
|
||||
import { localizedPath, t, type Locale } from '$lib/i18n';
|
||||
|
||||
/**
|
||||
* Page d'accueil — portage de legacy/src/index.njk. Factorisée ici ;
|
||||
* les routes `/`, `/en/` et `/ht/` ne sont que des coquilles qui
|
||||
* passent leur locale (et leurs métadonnées via <Seo>).
|
||||
*/
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
const { locale }: Props = $props();
|
||||
|
||||
// Calculé au build (prerender) — logique identique à l'ancien site.
|
||||
const rising = nextRising();
|
||||
</script>
|
||||
|
||||
<section class="hero">
|
||||
<div class="hero-orbits" aria-hidden="true">
|
||||
<div class="sun"></div>
|
||||
<div class="orbit" style="width:5rem;height:5rem;animation-duration:6s"><span></span></div>
|
||||
<div class="orbit" style="width:8rem;height:8rem;animation-duration:10s"><span></span></div>
|
||||
<div class="orbit" style="width:11rem;height:11rem;animation-duration:14s"><span></span></div>
|
||||
<div class="orbit" style="width:14rem;height:14rem;animation-duration:18s"><span></span></div>
|
||||
<div class="orbit sirius" style="width:18rem;height:18rem;animation-duration:26s">
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container center hero-content">
|
||||
<p class="section-eyebrow">{t(locale, 'hero_eyebrow')}</p>
|
||||
<h1 class="hero-title">
|
||||
<span>{t(locale, 'hero_title1')}</span>
|
||||
<span class="accent">{t(locale, 'hero_title2')}</span>
|
||||
</h1>
|
||||
<p class="hero-subtitle">{t(locale, 'hero_subtitle')}</p>
|
||||
<p class="hero-description">{t(locale, 'hero_description')}</p>
|
||||
<p class="hero-date">
|
||||
{t(locale, 'hero_date_label')}
|
||||
<strong>{formatDate(rising.date, locale)} · {rising.time}</strong>
|
||||
</p>
|
||||
<div class="cluster hero-cta">
|
||||
<a class="btn btn-primary comet-ring" href={resolve(localizedPath(locale, '/observer/'))}
|
||||
>{t(locale, 'hero_cta_predictions')}</a
|
||||
>
|
||||
<a class="btn btn-outline" href={resolve(localizedPath(locale, '/sirius-science/'))}
|
||||
>{t(locale, 'hero_cta_learn')}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="intro">
|
||||
<div class="container">
|
||||
<div class="section-header center">
|
||||
<h2>{t(locale, 'home_intro_title')}</h2>
|
||||
<p class="pattern-strip kente" aria-hidden="true" style="margin-inline:auto"></p>
|
||||
<p class="section-subtitle">{t(locale, 'home_intro_subtitle')}</p>
|
||||
</div>
|
||||
|
||||
<div class="prose center stack" style="--stack-space: var(--space-3)">
|
||||
<!-- home_intro_p1 contient du <strong> (| safe dans le legacy) -->
|
||||
<p>{@html t(locale, 'home_intro_p1')}</p>
|
||||
<p>{t(locale, 'home_intro_p2')}</p>
|
||||
<p>{t(locale, 'home_intro_p3')}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid-auto" style="margin-top: var(--space-6)">
|
||||
<div class="feature-card reveal">
|
||||
<h3>{t(locale, 'feature_brightest_title')}</h3>
|
||||
<p>{t(locale, 'feature_brightest_desc')}</p>
|
||||
</div>
|
||||
<div class="feature-card reveal">
|
||||
<h3>{t(locale, 'feature_rising_title')}</h3>
|
||||
<p>{t(locale, 'feature_rising_desc')}</p>
|
||||
</div>
|
||||
<div class="feature-card reveal">
|
||||
<h3>{t(locale, 'feature_heritage_title')}</h3>
|
||||
<p>{t(locale, 'feature_heritage_desc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" style="background: var(--surface-sunk)">
|
||||
<div class="container">
|
||||
<div class="section-header center">
|
||||
<h2>{t(locale, 'wave_showcase_title')}</h2>
|
||||
<p class="section-subtitle">{t(locale, 'wave_showcase_subtitle')}</p>
|
||||
</div>
|
||||
|
||||
<ul class="wave-badges">
|
||||
{#each waveCities as city, i (city.name)}
|
||||
<li class="wave-badge wave-badge-{i % 3}">
|
||||
<span class="wave-badge-name">{city.name}</span>
|
||||
<span class="wave-badge-date">{city.date}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<p class="center" style="margin-top: var(--space-5)">
|
||||
<a class="btn btn-outline" href={resolve(localizedPath(locale, '/observer/'))}
|
||||
>{t(locale, 'wave_showcase_cta')}</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
/* Reveal d'entrée au scroll, CSS pur : sans effet si animation-timeline
|
||||
n'est pas supporté, désactivé en reduced-motion. */
|
||||
@supports (animation-timeline: view()) {
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.reveal {
|
||||
animation: reveal-rise linear both;
|
||||
animation-timeline: view();
|
||||
animation-range: entry 0% entry 60%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes reveal-rise {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(1.5rem);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,138 @@
|
||||
<script lang="ts">
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
import SiriusPredictions from '$lib/components/islands/SiriusPredictions.svelte';
|
||||
import ObservationMap from '$lib/components/islands/ObservationMap.svelte';
|
||||
import SiriusGlobe from '$lib/components/islands/SiriusGlobe.svelte';
|
||||
|
||||
let { locale }: { locale: Locale } = $props();
|
||||
</script>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="section-header center">
|
||||
<p class="section-eyebrow">Sirius Gwada</p>
|
||||
<h1>{t(locale, 'observer_title')}</h1>
|
||||
<p class="pattern-strip kente" aria-hidden="true" style="margin-inline:auto"></p>
|
||||
<p class="section-subtitle">{t(locale, 'observer_subtitle')}</p>
|
||||
</div>
|
||||
|
||||
<div id="predictions-island">
|
||||
<h2 class="center">{t(locale, 'observer_calculator_title')}</h2>
|
||||
<p class="section-subtitle center">{t(locale, 'observer_calculator_subtitle')}</p>
|
||||
|
||||
<div style="margin-top: var(--space-4)">
|
||||
<SiriusPredictions {locale} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="map-island" style="margin-top: var(--space-7)">
|
||||
<h2 class="center">{t(locale, 'observer_map_title')}</h2>
|
||||
<p class="section-subtitle center">{t(locale, 'observer_map_subtitle')}</p>
|
||||
|
||||
<div style="margin-top: var(--space-4)">
|
||||
<ObservationMap {locale} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="center" style="margin-top: var(--space-7)">{t(locale, 'observer_timeline_title')}</h2>
|
||||
<div class="timeline-h card" style="margin-top: var(--space-4)">
|
||||
<div class="timeline-h-step">
|
||||
<div class="timeline-h-icon" aria-hidden="true">🌙</div>
|
||||
<p class="timeline-h-time">{t(locale, 'observer_phase1_time')}</p>
|
||||
<h4>{t(locale, 'observer_phase1_title')}</h4>
|
||||
<p>{t(locale, 'observer_phase1_desc')}</p>
|
||||
</div>
|
||||
<div class="timeline-h-step">
|
||||
<div class="timeline-h-icon" aria-hidden="true">🔭</div>
|
||||
<p class="timeline-h-time">{t(locale, 'observer_phase2_time')}</p>
|
||||
<h4>{t(locale, 'observer_phase2_title')}</h4>
|
||||
<p>{t(locale, 'observer_phase2_desc')}</p>
|
||||
</div>
|
||||
<div class="timeline-h-step">
|
||||
<div class="timeline-h-icon" aria-hidden="true">⭐</div>
|
||||
<p class="timeline-h-time">{t(locale, 'observer_phase3_time')}</p>
|
||||
<h4>{t(locale, 'observer_phase3_title')}</h4>
|
||||
<p>{t(locale, 'observer_phase3_desc')}</p>
|
||||
</div>
|
||||
<div class="timeline-h-step">
|
||||
<div class="timeline-h-icon" aria-hidden="true">✨</div>
|
||||
<p class="timeline-h-time">{t(locale, 'observer_phase4_time')}</p>
|
||||
<h4>{t(locale, 'observer_phase4_title')}</h4>
|
||||
<p>{t(locale, 'observer_phase4_desc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="center" style="margin-top: var(--space-7)">{t(locale, 'observer_equipment_title')}</h2>
|
||||
<div class="grid-auto" style="margin-top: var(--space-4)">
|
||||
<div class="card info-card essential">
|
||||
<div class="info-card-header">
|
||||
<h3>{t(locale, 'observer_equipment_lamp_name')}</h3>
|
||||
<span class="badge">{t(locale, 'observer_equipment_essential_badge')}</span>
|
||||
</div>
|
||||
<p>{t(locale, 'observer_equipment_lamp_desc')}</p>
|
||||
</div>
|
||||
<div class="card info-card essential">
|
||||
<div class="info-card-header">
|
||||
<h3>{t(locale, 'observer_equipment_compass_name')}</h3>
|
||||
<span class="badge">{t(locale, 'observer_equipment_essential_badge')}</span>
|
||||
</div>
|
||||
<p>{t(locale, 'observer_equipment_compass_desc')}</p>
|
||||
</div>
|
||||
<div class="card info-card essential">
|
||||
<div class="info-card-header">
|
||||
<h3>{t(locale, 'observer_equipment_clothes_name')}</h3>
|
||||
<span class="badge">{t(locale, 'observer_equipment_essential_badge')}</span>
|
||||
</div>
|
||||
<p>{t(locale, 'observer_equipment_clothes_desc')}</p>
|
||||
</div>
|
||||
<div class="card info-card">
|
||||
<h3>{t(locale, 'observer_equipment_binoculars_name')}</h3>
|
||||
<p>{t(locale, 'observer_equipment_binoculars_desc')}</p>
|
||||
</div>
|
||||
<div class="card info-card">
|
||||
<h3>{t(locale, 'observer_equipment_notebook_name')}</h3>
|
||||
<p>{t(locale, 'observer_equipment_notebook_desc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="center" style="margin-top: var(--space-7)">{t(locale, 'observer_tips_title')}</h2>
|
||||
<div class="grid-auto" style="margin-top: var(--space-4)">
|
||||
<div class="feature-card">
|
||||
<div style="font-size: var(--text-4)" aria-hidden="true">🌤️</div>
|
||||
<h3>{t(locale, 'observer_tip_weather_title')}</h3>
|
||||
<p>{t(locale, 'observer_tip_weather_desc')}</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div style="font-size: var(--text-4)" aria-hidden="true">🌑</div>
|
||||
<h3>{t(locale, 'observer_tip_moon_title')}</h3>
|
||||
<p>{t(locale, 'observer_tip_moon_desc')}</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div style="font-size: var(--text-4)" aria-hidden="true">📱</div>
|
||||
<h3>{t(locale, 'observer_tip_apps_title')}</h3>
|
||||
<p>{t(locale, 'observer_tip_apps_desc')}</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div style="font-size: var(--text-4)" aria-hidden="true">👥</div>
|
||||
<h3>{t(locale, 'observer_tip_group_title')}</h3>
|
||||
<p>{t(locale, 'observer_tip_group_desc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="globe-island" style="background: var(--surface-sunk)">
|
||||
<div class="container">
|
||||
<div class="section-header center">
|
||||
<h2>{t(locale, 'globe_title')}</h2>
|
||||
<p class="section-subtitle">{t(locale, 'globe_p1')}</p>
|
||||
</div>
|
||||
<p class="prose center">{t(locale, 'globe_p2')}</p>
|
||||
<div style="margin-top: var(--space-4)">
|
||||
<SiriusGlobe {locale} />
|
||||
</div>
|
||||
<p class="center" style="color: var(--ink-soft); font-size: var(--text-0)">
|
||||
{t(locale, 'globe_controls_hint')}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,108 @@
|
||||
<script lang="ts">
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
let { locale }: { locale: Locale } = $props();
|
||||
</script>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="section-header center">
|
||||
<p class="section-eyebrow">Sirius Gwada</p>
|
||||
<h1>{t(locale, 'science_title')}</h1>
|
||||
<p class="pattern-strip bogolan" aria-hidden="true" style="margin-inline:auto"></p>
|
||||
<p class="section-subtitle">{t(locale, 'science_subtitle')}</p>
|
||||
</div>
|
||||
|
||||
<fieldset class="tabs">
|
||||
<legend class="visually-hidden">{t(locale, 'science_title')}</legend>
|
||||
|
||||
<input type="radio" name="science-tab" id="tab-mechanics" class="tab-input" checked />
|
||||
<input type="radio" name="science-tab" id="tab-arcus" class="tab-input" />
|
||||
<input type="radio" name="science-tab" id="tab-factors" class="tab-input" />
|
||||
<input type="radio" name="science-tab" id="tab-calc" class="tab-input" />
|
||||
|
||||
<div class="tab-list" role="tablist">
|
||||
<label class="tab-label" for="tab-mechanics">{t(locale, 'science_tab_mechanics')}</label>
|
||||
<label class="tab-label" for="tab-arcus">{t(locale, 'science_tab_arcus')}</label>
|
||||
<label class="tab-label" for="tab-factors">{t(locale, 'science_tab_factors')}</label>
|
||||
<label class="tab-label" for="tab-calc">{t(locale, 'science_tab_calc')}</label>
|
||||
</div>
|
||||
|
||||
<div class="tab-panels">
|
||||
<div class="tab-panel prose center">
|
||||
<h2>{t(locale, 'science_mechanics_title')}</h2>
|
||||
<p>{t(locale, 'science_mechanics_p1')}</p>
|
||||
<div class="diagram-orbit card" aria-hidden="true">
|
||||
<div class="ring"></div>
|
||||
<div class="body sun">☀️<span>{t(locale, 'science_diagram_sun')}</span></div>
|
||||
<div class="body earth">🌍<span>{t(locale, 'science_diagram_earth')}</span></div>
|
||||
<div class="body sirius">⭐<span>{t(locale, 'science_diagram_sirius')}</span></div>
|
||||
</div>
|
||||
<p>{t(locale, 'science_mechanics_p2')}</p>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel prose center">
|
||||
<h2>{t(locale, 'science_arcus_title')}</h2>
|
||||
<p>{t(locale, 'science_arcus_p1')}</p>
|
||||
<div class="diagram-arcus card" aria-hidden="true">
|
||||
<span class="horizon-label">{t(locale, 'science_arcus_horizon_label')}</span>
|
||||
<div class="angle"></div>
|
||||
<span class="angle-label">9-10°</span>
|
||||
<span class="sirius-mark">⭐</span>
|
||||
<span class="sun-mark">☀️</span>
|
||||
</div>
|
||||
<p>{t(locale, 'science_arcus_p2')}</p>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel">
|
||||
<div class="grid-auto">
|
||||
<div class="info-card">
|
||||
<h3>{t(locale, 'science_factor_magnitude_title')}</h3>
|
||||
<p>{t(locale, 'science_factor_magnitude_desc')}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3>{t(locale, 'science_factor_latitude_title')}</h3>
|
||||
<p>{t(locale, 'science_factor_latitude_desc')}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3>{t(locale, 'science_factor_atmosphere_title')}</h3>
|
||||
<p>{t(locale, 'science_factor_atmosphere_desc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel prose center">
|
||||
<h2>{t(locale, 'science_calc_title')}</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>{t(locale, 'science_calc_formula_label')}</h3>
|
||||
<p style="font-family: ui-monospace, monospace">{t(locale, 'science_calc_formula')}</p>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-top: var(--space-3)">
|
||||
<h3>{t(locale, 'science_calc_step1_title')}</h3>
|
||||
<p style="font-family: ui-monospace, monospace">{t(locale, 'science_calc_step1_value')}</p>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-top: var(--space-3)">
|
||||
<h3>{t(locale, 'science_calc_step2_title')}</h3>
|
||||
<p style="font-family: ui-monospace, monospace">
|
||||
{t(locale, 'science_calc_step2_ra')}<br />
|
||||
{t(locale, 'science_calc_step2_dec')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-top: var(--space-3)">
|
||||
<h3>{t(locale, 'science_calc_arcus_title')}</h3>
|
||||
<p>{t(locale, 'science_calc_arcus_desc')}</p>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-top: var(--space-3)">
|
||||
<h3>{t(locale, 'science_calc_sources_title')}</h3>
|
||||
<p>{t(locale, 'science_calc_sources_text')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</section>
|
||||
@@ -4,13 +4,15 @@ import sites from "./sites.js";
|
||||
// comme site de référence — les 7 sites ne divergent que de quelques minutes).
|
||||
export default function () {
|
||||
const reference = sites[0];
|
||||
/** @type {Record<number, { date: string; time: string }>} */
|
||||
const predictions = reference.predictions;
|
||||
const today = new Date();
|
||||
const years = Object.keys(reference.predictions)
|
||||
const years = Object.keys(predictions)
|
||||
.map(Number)
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
for (const year of years) {
|
||||
const p = reference.predictions[year];
|
||||
const p = predictions[year];
|
||||
const when = new Date(`${p.date}T${p.time}:00`);
|
||||
if (when >= today) {
|
||||
return { ...p, year };
|
||||
@@ -18,5 +20,5 @@ export default function () {
|
||||
}
|
||||
|
||||
const lastYear = years[years.length - 1];
|
||||
return { ...reference.predictions[lastYear], year: lastYear };
|
||||
return { ...predictions[lastYear], year: lastYear };
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { Locale } from '$lib/i18n';
|
||||
|
||||
/**
|
||||
* Formate une date ISO `YYYY-MM-DD` pour l'affichage.
|
||||
*
|
||||
* Piège documenté (NOTES_PROJET §3) : `new Date("2026-07-22")` est parsé en
|
||||
* UTC minuit, ce qui peut reculer d'un jour une fois formaté dans un fuseau
|
||||
* derrière UTC (Guadeloupe, UTC-4). On construit donc la date à partir de
|
||||
* ses composants, en heure locale — logique reprise à l'identique du filtre
|
||||
* `formatDate` de l'ancien eleventy.config.js.
|
||||
*/
|
||||
export function formatDate(
|
||||
isoDate: string,
|
||||
locale: Locale,
|
||||
options: Intl.DateTimeFormatOptions = {}
|
||||
): string {
|
||||
const [year, month, day] = isoDate.split('-').map(Number);
|
||||
return new Date(year, month - 1, day).toLocaleDateString(locale === 'ht' ? 'fr' : locale, {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
...options
|
||||
});
|
||||
}
|
||||
@@ -3,6 +3,14 @@
|
||||
|
||||
"meta_title": "Sirius in Guadeloupe — Wep Ronpet, the heliacal rising",
|
||||
"meta_description": "Calculate and understand the heliacal rising of Sirius in Guadeloupe: astronomy, Egyptian and Dogon heritage, observation guide.",
|
||||
"meta_science_title": "Sirius & Science",
|
||||
"meta_science_description": "The mechanics of Sirius's heliacal rising explained: Arcus Visionis, visibility factors and calculation method for Guadeloupe.",
|
||||
"meta_culture_title": "Culture & Knowledge",
|
||||
"meta_culture_description": "Sirius across cultures: ancient Egypt, Dogon astronomical knowledge and the Caribbean heritage of Wep Ronpet.",
|
||||
"meta_observer_title": "Observe",
|
||||
"meta_observer_description": "Dates and times of Sirius's heliacal rising for 7 observation sites in Guadeloupe, map, equipment and practical tips.",
|
||||
"meta_associations_title": "Associations celebrating Wep Ronpet",
|
||||
"meta_associations_description": "Events, communities and associations celebrating Wep Ronpet, the Kamit New Year, in Guadeloupe and beyond.",
|
||||
|
||||
"skip_to_content": "Skip to content",
|
||||
"lang_name_fr": "Français",
|
||||
@@ -3,6 +3,14 @@
|
||||
|
||||
"meta_title": "Sirius en Guadeloupe — Wep Ronpet, le lever héliaque",
|
||||
"meta_description": "Calculez et comprenez le lever héliaque de Sirius en Guadeloupe : astronomie, héritage égyptien et dogon, guide d'observation.",
|
||||
"meta_science_title": "Sirius & Science",
|
||||
"meta_science_description": "La mécanique du lever héliaque de Sirius expliquée : Arcus Visionis, facteurs de visibilité et méthode de calcul pour la Guadeloupe.",
|
||||
"meta_culture_title": "Culture & Savoirs",
|
||||
"meta_culture_description": "Sirius dans les cultures : Égypte antique, savoirs astronomiques dogon et héritage caribéen du Wep Ronpet.",
|
||||
"meta_observer_title": "Observer",
|
||||
"meta_observer_description": "Dates et heures du lever héliaque de Sirius pour 7 sites d'observation en Guadeloupe, carte, équipement et conseils pratiques.",
|
||||
"meta_associations_title": "Associations qui fêtent le Wep Ronpet",
|
||||
"meta_associations_description": "Événements, communautés et associations qui célèbrent le Wep Ronpet, le Nouvel An kamite, en Guadeloupe et au-delà.",
|
||||
|
||||
"skip_to_content": "Aller au contenu",
|
||||
"lang_name_fr": "Français",
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* i18n — catalogues Paraglide (format message-format JSON) consommés
|
||||
* directement, routage par dossiers : `/` = fr, `/en/`, `/ht/`.
|
||||
* Le créole (ht) couvre le chrome ; le contenu long retombe sur le fr
|
||||
* (fallback documenté dans NOTES_PROJET).
|
||||
*/
|
||||
|
||||
import en from './en.json';
|
||||
import fr from './fr.json';
|
||||
import ht from './ht.json';
|
||||
import type { Pathname } from '$app/types';
|
||||
|
||||
export const LOCALES = ['fr', 'en', 'ht'] as const;
|
||||
export type Locale = (typeof LOCALES)[number];
|
||||
export const DEFAULT_LOCALE: Locale = 'fr';
|
||||
|
||||
type Messages = Record<string, string>;
|
||||
|
||||
const BUNDLES: Record<Locale, Messages> = {
|
||||
fr: fr as Messages,
|
||||
en: en as Messages,
|
||||
ht: ht as Messages
|
||||
};
|
||||
|
||||
export function isLocale(value: string): value is Locale {
|
||||
return (LOCALES as readonly string[]).includes(value);
|
||||
}
|
||||
|
||||
/** Locale déduite du chemin (`/en/...` → en, `/ht/...` → ht, sinon fr). */
|
||||
export function localeFromPath(pathname: string): Locale {
|
||||
const segment = pathname.split('/').filter(Boolean)[0] ?? '';
|
||||
return isLocale(segment) ? segment : DEFAULT_LOCALE;
|
||||
}
|
||||
|
||||
export function getMessages(locale: Locale): Messages {
|
||||
return BUNDLES[locale] ?? BUNDLES[DEFAULT_LOCALE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Traduit une clé : locale demandée, fallback fr, fallback clé.
|
||||
* Interpolation `{name}` via `vars`.
|
||||
*/
|
||||
export function t(locale: Locale, key: string, vars?: Record<string, string | number>): string {
|
||||
const raw = BUNDLES[locale]?.[key] ?? BUNDLES[DEFAULT_LOCALE][key] ?? key;
|
||||
if (!vars) return raw;
|
||||
return raw.replace(/\{(\w+)\}/g, (_, name) => String(vars[name] ?? `{${name}}`));
|
||||
}
|
||||
|
||||
/** Préfixe d'URL d'une locale (`''` pour fr, `/en`, `/ht`). */
|
||||
export function localePrefix(locale: Locale): string {
|
||||
return locale === DEFAULT_LOCALE ? '' : `/${locale}`;
|
||||
}
|
||||
|
||||
/** Chemin localisé (`/observer/` + en → `/en/observer/`). */
|
||||
export function localizedPath(locale: Locale, path: string): Pathname {
|
||||
const prefix = localePrefix(locale);
|
||||
return `${prefix}${path === '/' ? '/' : path}` as Pathname;
|
||||
}
|
||||
|
||||
/** Chemin de la même page dans une autre locale. */
|
||||
export function switchLocalePath(pathname: string, target: Locale): Pathname {
|
||||
const current = localeFromPath(pathname);
|
||||
const prefix = localePrefix(current);
|
||||
const bare = prefix ? pathname.slice(prefix.length) : pathname;
|
||||
return (localePrefix(target) + (bare || '/')) as Pathname;
|
||||
}
|
||||
|
||||
/** Libellé natif d'une locale (lang_name_*). */
|
||||
export function localeLabel(locale: Locale): string {
|
||||
return t(locale, `lang_name_${locale}`);
|
||||
}
|
||||
@@ -1,26 +1,6 @@
|
||||
@font-face {
|
||||
font-family: "Fraunces Variable";
|
||||
src: url("/assets/fonts/fraunces-variable.woff2") format("woff2-variations");
|
||||
font-weight: 300 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible";
|
||||
src: url("/assets/fonts/atkinson-400.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible";
|
||||
src: url("/assets/fonts/atkinson-700.woff2") format("woff2");
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
/* Les @font-face vivent dans /fonts/fonts.css (static/), chargé par app.html —
|
||||
ne pas les dupliquer ici (l'ancien build 11ty servait les polices en double,
|
||||
sous /assets/fonts/, avec des URLs non préfixées par le base path). */
|
||||
|
||||
html {
|
||||
font-size: 100%;
|
||||
@@ -1,180 +0,0 @@
|
||||
---
|
||||
pagination:
|
||||
data: locales
|
||||
size: 1
|
||||
alias: locale
|
||||
permalink: "{% if locale == baseLocale %}/observer/index.html{% else %}/{{ locale }}/observer/index.html{% endif %}"
|
||||
layout: layouts/base.njk
|
||||
pageKey: observer
|
||||
slug: "observer/"
|
||||
eleventyComputed:
|
||||
pageTitle: "{{ 'observer_title' | t(locale) }}"
|
||||
---
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="section-header center">
|
||||
<p class="section-eyebrow">Sirius Gwada</p>
|
||||
<h1>{{ "observer_title" | t(locale) }}</h1>
|
||||
<p class="pattern-strip kente" aria-hidden="true" style="margin-inline:auto"></p>
|
||||
<p class="section-subtitle">{{ "observer_subtitle" | t(locale) }}</p>
|
||||
</div>
|
||||
|
||||
<script type="application/json" id="sites-data">{{ sites | dump | safe }}</script>
|
||||
|
||||
<div id="predictions-island" data-island="predictions">
|
||||
<h2 class="center">{{ "observer_calculator_title" | t(locale) }}</h2>
|
||||
<p class="section-subtitle center">{{ "observer_calculator_subtitle" | t(locale) }}</p>
|
||||
|
||||
<div class="island-app" data-year="{{ nextRising.year }}" data-locale="{{ locale }}"
|
||||
data-select-label="{{ 'observer_calculator_select_label' | t(locale) }}"
|
||||
data-heliacal-label="{{ 'observer_calculator_result_heliacal' | t(locale) }}"
|
||||
data-window-label="{{ 'observer_calculator_result_window' | t(locale) }}"
|
||||
style="margin-top: var(--space-4)"></div>
|
||||
|
||||
<div class="island-fallback card" style="overflow-x: auto; margin-top: var(--space-4)">
|
||||
<table style="width: 100%; border-collapse: collapse">
|
||||
<caption class="visually-hidden">{{ "observer_calculator_title" | t(locale) }}</caption>
|
||||
<thead>
|
||||
<tr style="text-align: left; border-bottom: 2px solid var(--border)">
|
||||
<th style="padding: var(--space-2)" scope="col">{{ "observer_calculator_table_site" | t(locale) }}</th>
|
||||
<th style="padding: var(--space-2)" scope="col">{{ "observer_calculator_table_date" | t(locale) }}</th>
|
||||
<th style="padding: var(--space-2)" scope="col">{{ "observer_calculator_table_time" | t(locale) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for site in sites %}
|
||||
<tr style="border-bottom: 1px solid var(--border)">
|
||||
<th scope="row" style="padding: var(--space-2); font-weight: 600; text-align: left">
|
||||
{{ site.name }}
|
||||
<div style="font-size: var(--text-0); color: var(--ink-soft); font-weight: 400">
|
||||
{{ site["description_" + locale] or site.description_fr }}
|
||||
</div>
|
||||
</th>
|
||||
<td style="padding: var(--space-2)">{{ site.predictions[nextRising.year].date | formatDate(locale) }}</td>
|
||||
<td style="padding: var(--space-2)">{{ site.predictions[nextRising.year].time }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="map-island" data-island="map" style="margin-top: var(--space-7)">
|
||||
<h2 class="center">{{ "observer_map_title" | t(locale) }}</h2>
|
||||
<p class="section-subtitle center">{{ "observer_map_subtitle" | t(locale) }}</p>
|
||||
|
||||
<div class="island-app" style="margin-top: var(--space-4)"></div>
|
||||
|
||||
<ul class="island-fallback grid-auto" style="margin-top: var(--space-4); list-style: none">
|
||||
{% for site in sites %}
|
||||
<li class="card">
|
||||
<h3>{{ site.name }}</h3>
|
||||
<p>{{ site["description_" + locale] or site.description_fr }}</p>
|
||||
<p style="font-size: var(--text-0); color: var(--ink-dim)">{{ site.lat }}°N, {{ site.lon }}°O — {{ site.alt }} m</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 class="center" style="margin-top: var(--space-7)">{{ "observer_timeline_title" | t(locale) }}</h2>
|
||||
<div class="timeline-h card" style="margin-top: var(--space-4)">
|
||||
<div class="timeline-h-step">
|
||||
<div class="timeline-h-icon" aria-hidden="true">🌙</div>
|
||||
<p class="timeline-h-time">{{ "observer_phase1_time" | t(locale) }}</p>
|
||||
<h4>{{ "observer_phase1_title" | t(locale) }}</h4>
|
||||
<p>{{ "observer_phase1_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="timeline-h-step">
|
||||
<div class="timeline-h-icon" aria-hidden="true">🔭</div>
|
||||
<p class="timeline-h-time">{{ "observer_phase2_time" | t(locale) }}</p>
|
||||
<h4>{{ "observer_phase2_title" | t(locale) }}</h4>
|
||||
<p>{{ "observer_phase2_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="timeline-h-step">
|
||||
<div class="timeline-h-icon" aria-hidden="true">⭐</div>
|
||||
<p class="timeline-h-time">{{ "observer_phase3_time" | t(locale) }}</p>
|
||||
<h4>{{ "observer_phase3_title" | t(locale) }}</h4>
|
||||
<p>{{ "observer_phase3_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="timeline-h-step">
|
||||
<div class="timeline-h-icon" aria-hidden="true">✨</div>
|
||||
<p class="timeline-h-time">{{ "observer_phase4_time" | t(locale) }}</p>
|
||||
<h4>{{ "observer_phase4_title" | t(locale) }}</h4>
|
||||
<p>{{ "observer_phase4_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="center" style="margin-top: var(--space-7)">{{ "observer_equipment_title" | t(locale) }}</h2>
|
||||
<div class="grid-auto" style="margin-top: var(--space-4)">
|
||||
<div class="card info-card essential">
|
||||
<div class="info-card-header">
|
||||
<h3>{{ "observer_equipment_lamp_name" | t(locale) }}</h3>
|
||||
<span class="badge">{{ "observer_equipment_essential_badge" | t(locale) }}</span>
|
||||
</div>
|
||||
<p>{{ "observer_equipment_lamp_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="card info-card essential">
|
||||
<div class="info-card-header">
|
||||
<h3>{{ "observer_equipment_compass_name" | t(locale) }}</h3>
|
||||
<span class="badge">{{ "observer_equipment_essential_badge" | t(locale) }}</span>
|
||||
</div>
|
||||
<p>{{ "observer_equipment_compass_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="card info-card essential">
|
||||
<div class="info-card-header">
|
||||
<h3>{{ "observer_equipment_clothes_name" | t(locale) }}</h3>
|
||||
<span class="badge">{{ "observer_equipment_essential_badge" | t(locale) }}</span>
|
||||
</div>
|
||||
<p>{{ "observer_equipment_clothes_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="card info-card">
|
||||
<h3>{{ "observer_equipment_binoculars_name" | t(locale) }}</h3>
|
||||
<p>{{ "observer_equipment_binoculars_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="card info-card">
|
||||
<h3>{{ "observer_equipment_notebook_name" | t(locale) }}</h3>
|
||||
<p>{{ "observer_equipment_notebook_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="center" style="margin-top: var(--space-7)">{{ "observer_tips_title" | t(locale) }}</h2>
|
||||
<div class="grid-auto" style="margin-top: var(--space-4)">
|
||||
<div class="feature-card">
|
||||
<div style="font-size: var(--text-4)" aria-hidden="true">🌤️</div>
|
||||
<h3>{{ "observer_tip_weather_title" | t(locale) }}</h3>
|
||||
<p>{{ "observer_tip_weather_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div style="font-size: var(--text-4)" aria-hidden="true">🌑</div>
|
||||
<h3>{{ "observer_tip_moon_title" | t(locale) }}</h3>
|
||||
<p>{{ "observer_tip_moon_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div style="font-size: var(--text-4)" aria-hidden="true">📱</div>
|
||||
<h3>{{ "observer_tip_apps_title" | t(locale) }}</h3>
|
||||
<p>{{ "observer_tip_apps_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div style="font-size: var(--text-4)" aria-hidden="true">👥</div>
|
||||
<h3>{{ "observer_tip_group_title" | t(locale) }}</h3>
|
||||
<p>{{ "observer_tip_group_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="globe-island" data-island="globe" style="background: var(--surface-sunk)">
|
||||
<div class="container">
|
||||
<div class="section-header center">
|
||||
<h2>{{ "globe_title" | t(locale) }}</h2>
|
||||
<p class="section-subtitle">{{ "globe_p1" | t(locale) }}</p>
|
||||
</div>
|
||||
<p class="prose center">{{ "globe_p2" | t(locale) }}</p>
|
||||
<div class="island-app" style="margin-top: var(--space-4)"></div>
|
||||
<p class="center" style="color: var(--ink-soft); font-size: var(--text-0)">{{ "globe_controls_hint" | t(locale) }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script type="module" src="/src/islands/predictions/mount.js"></script>
|
||||
<script type="module" src="/src/islands/map/mount.js"></script>
|
||||
<script type="module" src="/src/islands/globe/mount.js"></script>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import '$lib/styles/app.css';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import Footer from '$lib/components/Footer.svelte';
|
||||
import Nav from '$lib/components/Nav.svelte';
|
||||
import { localeFromPath, t } from '$lib/i18n';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
const { children }: Props = $props();
|
||||
|
||||
// page.url.pathname inclut le base path (/gwada-sirius) : on le retire
|
||||
// avant de déduire la locale du premier segment.
|
||||
const locale = $derived(
|
||||
localeFromPath(
|
||||
page.url.pathname.startsWith(base)
|
||||
? page.url.pathname.slice(base.length) || '/'
|
||||
: page.url.pathname
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
<a class="skip-link" href="#main-content">{t(locale, 'skip_to_content')}</a>
|
||||
|
||||
<Nav {locale} />
|
||||
|
||||
<main id="main-content">
|
||||
{@render children()}
|
||||
</main>
|
||||
|
||||
<Footer {locale} />
|
||||
@@ -0,0 +1,2 @@
|
||||
export const prerender = true;
|
||||
export const trailingSlash = 'always';
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Home from '$lib/components/pages/Home.svelte';
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
const locale: Locale = 'fr';
|
||||
</script>
|
||||
|
||||
<Seo
|
||||
{locale}
|
||||
title="{t(locale, 'hero_title1')} {t(locale, 'hero_title2')} — {t(locale, 'meta_title')}"
|
||||
description={t(locale, 'meta_description')}
|
||||
path="/"
|
||||
/>
|
||||
|
||||
<Home {locale} />
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import Associations from '$lib/components/pages/Associations.svelte';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
const locale: Locale = 'fr';
|
||||
</script>
|
||||
|
||||
<Seo
|
||||
{locale}
|
||||
title={t(locale, 'meta_associations_title')}
|
||||
description={t(locale, 'meta_associations_description')}
|
||||
path="/associations/"
|
||||
/>
|
||||
|
||||
<Associations {locale} />
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import Culture from '$lib/components/pages/Culture.svelte';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
const locale: Locale = 'fr';
|
||||
</script>
|
||||
|
||||
<Seo
|
||||
{locale}
|
||||
title={t(locale, 'meta_culture_title')}
|
||||
description={t(locale, 'meta_culture_description')}
|
||||
path="/culture-savoirs/"
|
||||
/>
|
||||
|
||||
<Culture {locale} />
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Home from '$lib/components/pages/Home.svelte';
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
const locale: Locale = 'en';
|
||||
</script>
|
||||
|
||||
<Seo
|
||||
{locale}
|
||||
title="{t(locale, 'hero_title1')} {t(locale, 'hero_title2')} — {t(locale, 'meta_title')}"
|
||||
description={t(locale, 'meta_description')}
|
||||
path="/"
|
||||
/>
|
||||
|
||||
<Home {locale} />
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import Associations from '$lib/components/pages/Associations.svelte';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
const locale: Locale = 'en';
|
||||
</script>
|
||||
|
||||
<Seo
|
||||
{locale}
|
||||
title={t(locale, 'meta_associations_title')}
|
||||
description={t(locale, 'meta_associations_description')}
|
||||
path="/associations/"
|
||||
/>
|
||||
|
||||
<Associations {locale} />
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import Culture from '$lib/components/pages/Culture.svelte';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
const locale: Locale = 'en';
|
||||
</script>
|
||||
|
||||
<Seo
|
||||
{locale}
|
||||
title={t(locale, 'meta_culture_title')}
|
||||
description={t(locale, 'meta_culture_description')}
|
||||
path="/culture-savoirs/"
|
||||
/>
|
||||
|
||||
<Culture {locale} />
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import Observer from '$lib/components/pages/Observer.svelte';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
const locale: Locale = 'en';
|
||||
</script>
|
||||
|
||||
<Seo
|
||||
{locale}
|
||||
title={t(locale, 'meta_observer_title')}
|
||||
description={t(locale, 'meta_observer_description')}
|
||||
path="/observer/"
|
||||
/>
|
||||
|
||||
<Observer {locale} />
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import Science from '$lib/components/pages/Science.svelte';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
const locale: Locale = 'en';
|
||||
</script>
|
||||
|
||||
<Seo
|
||||
{locale}
|
||||
title={t(locale, 'meta_science_title')}
|
||||
description={t(locale, 'meta_science_description')}
|
||||
path="/sirius-science/"
|
||||
/>
|
||||
|
||||
<Science {locale} />
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Home from '$lib/components/pages/Home.svelte';
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
const locale: Locale = 'ht';
|
||||
</script>
|
||||
|
||||
<Seo
|
||||
{locale}
|
||||
title="{t(locale, 'hero_title1')} {t(locale, 'hero_title2')} — {t(locale, 'meta_title')}"
|
||||
description={t(locale, 'meta_description')}
|
||||
path="/"
|
||||
/>
|
||||
|
||||
<Home {locale} />
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import Associations from '$lib/components/pages/Associations.svelte';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
const locale: Locale = 'ht';
|
||||
</script>
|
||||
|
||||
<Seo
|
||||
{locale}
|
||||
title={t(locale, 'meta_associations_title')}
|
||||
description={t(locale, 'meta_associations_description')}
|
||||
path="/associations/"
|
||||
/>
|
||||
|
||||
<Associations {locale} />
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import Culture from '$lib/components/pages/Culture.svelte';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
const locale: Locale = 'ht';
|
||||
</script>
|
||||
|
||||
<Seo
|
||||
{locale}
|
||||
title={t(locale, 'meta_culture_title')}
|
||||
description={t(locale, 'meta_culture_description')}
|
||||
path="/culture-savoirs/"
|
||||
/>
|
||||
|
||||
<Culture {locale} />
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import Observer from '$lib/components/pages/Observer.svelte';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
const locale: Locale = 'ht';
|
||||
</script>
|
||||
|
||||
<Seo
|
||||
{locale}
|
||||
title={t(locale, 'meta_observer_title')}
|
||||
description={t(locale, 'meta_observer_description')}
|
||||
path="/observer/"
|
||||
/>
|
||||
|
||||
<Observer {locale} />
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import Science from '$lib/components/pages/Science.svelte';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
const locale: Locale = 'ht';
|
||||
</script>
|
||||
|
||||
<Seo
|
||||
{locale}
|
||||
title={t(locale, 'meta_science_title')}
|
||||
description={t(locale, 'meta_science_description')}
|
||||
path="/sirius-science/"
|
||||
/>
|
||||
|
||||
<Science {locale} />
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import Observer from '$lib/components/pages/Observer.svelte';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
const locale: Locale = 'fr';
|
||||
</script>
|
||||
|
||||
<Seo
|
||||
{locale}
|
||||
title={t(locale, 'meta_observer_title')}
|
||||
description={t(locale, 'meta_observer_description')}
|
||||
path="/observer/"
|
||||
/>
|
||||
|
||||
<Observer {locale} />
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Seo from '$lib/components/Seo.svelte';
|
||||
import Science from '$lib/components/pages/Science.svelte';
|
||||
import { t, type Locale } from '$lib/i18n';
|
||||
|
||||
const locale: Locale = 'fr';
|
||||
</script>
|
||||
|
||||
<Seo
|
||||
{locale}
|
||||
title={t(locale, 'meta_science_title')}
|
||||
description={t(locale, 'meta_science_description')}
|
||||
path="/sirius-science/"
|
||||
/>
|
||||
|
||||
<Science {locale} />
|
||||
@@ -0,0 +1,39 @@
|
||||
// Service worker minimal : résilience réseau (3G/4G irrégulier) et usage
|
||||
// hors-ligne des pages déjà visitées. Stratégie stale-while-revalidate.
|
||||
// NOTES_PROJET §5 : incrémenter CACHE_NAME à chaque évolution du SW.
|
||||
const CACHE_NAME = "gwada-sirius-v3";
|
||||
|
||||
self.addEventListener("install", () => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(
|
||||
caches
|
||||
.keys()
|
||||
.then((keys) =>
|
||||
Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)))
|
||||
)
|
||||
);
|
||||
self.clients.claim();
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
if (event.request.method !== "GET") return;
|
||||
const url = new URL(event.request.url);
|
||||
if (url.origin !== self.location.origin) return;
|
||||
|
||||
event.respondWith(
|
||||
caches.open(CACHE_NAME).then(async (cache) => {
|
||||
const cached = await cache.match(event.request);
|
||||
const network = fetch(event.request)
|
||||
.then((response) => {
|
||||
if (response.ok) cache.put(event.request, response.clone());
|
||||
return response;
|
||||
})
|
||||
.catch(() => cached);
|
||||
|
||||
return cached || network;
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -1,114 +0,0 @@
|
||||
---
|
||||
pagination:
|
||||
data: locales
|
||||
size: 1
|
||||
alias: locale
|
||||
permalink: "{% if locale == baseLocale %}/sirius-science/index.html{% else %}/{{ locale }}/sirius-science/index.html{% endif %}"
|
||||
layout: layouts/base.njk
|
||||
pageKey: science
|
||||
slug: "sirius-science/"
|
||||
eleventyComputed:
|
||||
pageTitle: "{{ 'science_title' | t(locale) }}"
|
||||
---
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="section-header center">
|
||||
<p class="section-eyebrow">Sirius Gwada</p>
|
||||
<h1>{{ "science_title" | t(locale) }}</h1>
|
||||
<p class="pattern-strip bogolan" aria-hidden="true" style="margin-inline:auto"></p>
|
||||
<p class="section-subtitle">{{ "science_subtitle" | t(locale) }}</p>
|
||||
</div>
|
||||
|
||||
<fieldset class="tabs">
|
||||
<legend class="visually-hidden">{{ "science_title" | t(locale) }}</legend>
|
||||
|
||||
<input type="radio" name="science-tab" id="tab-mechanics" class="tab-input" checked>
|
||||
<input type="radio" name="science-tab" id="tab-arcus" class="tab-input">
|
||||
<input type="radio" name="science-tab" id="tab-factors" class="tab-input">
|
||||
<input type="radio" name="science-tab" id="tab-calc" class="tab-input">
|
||||
|
||||
<div class="tab-list" role="tablist">
|
||||
<label class="tab-label" for="tab-mechanics">{{ "science_tab_mechanics" | t(locale) }}</label>
|
||||
<label class="tab-label" for="tab-arcus">{{ "science_tab_arcus" | t(locale) }}</label>
|
||||
<label class="tab-label" for="tab-factors">{{ "science_tab_factors" | t(locale) }}</label>
|
||||
<label class="tab-label" for="tab-calc">{{ "science_tab_calc" | t(locale) }}</label>
|
||||
</div>
|
||||
|
||||
<div class="tab-panels">
|
||||
<div class="tab-panel prose center">
|
||||
<h2>{{ "science_mechanics_title" | t(locale) }}</h2>
|
||||
<p>{{ "science_mechanics_p1" | t(locale) }}</p>
|
||||
<div class="diagram-orbit card" aria-hidden="true">
|
||||
<div class="ring"></div>
|
||||
<div class="body sun">☀️<span>{{ "science_diagram_sun" | t(locale) }}</span></div>
|
||||
<div class="body earth">🌍<span>{{ "science_diagram_earth" | t(locale) }}</span></div>
|
||||
<div class="body sirius">⭐<span>{{ "science_diagram_sirius" | t(locale) }}</span></div>
|
||||
</div>
|
||||
<p>{{ "science_mechanics_p2" | t(locale) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel prose center">
|
||||
<h2>{{ "science_arcus_title" | t(locale) }}</h2>
|
||||
<p>{{ "science_arcus_p1" | t(locale) }}</p>
|
||||
<div class="diagram-arcus card" aria-hidden="true">
|
||||
<span class="horizon-label">{{ "science_arcus_horizon_label" | t(locale) }}</span>
|
||||
<div class="angle"></div>
|
||||
<span class="angle-label">9-10°</span>
|
||||
<span class="sirius-mark">⭐</span>
|
||||
<span class="sun-mark">☀️</span>
|
||||
</div>
|
||||
<p>{{ "science_arcus_p2" | t(locale) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel">
|
||||
<div class="grid-auto">
|
||||
<div class="info-card">
|
||||
<h3>{{ "science_factor_magnitude_title" | t(locale) }}</h3>
|
||||
<p>{{ "science_factor_magnitude_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3>{{ "science_factor_latitude_title" | t(locale) }}</h3>
|
||||
<p>{{ "science_factor_latitude_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3>{{ "science_factor_atmosphere_title" | t(locale) }}</h3>
|
||||
<p>{{ "science_factor_atmosphere_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel prose center">
|
||||
<h2>{{ "science_calc_title" | t(locale) }}</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>{{ "science_calc_formula_label" | t(locale) }}</h3>
|
||||
<p style="font-family: ui-monospace, monospace">{{ "science_calc_formula" | t(locale) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-top: var(--space-3)">
|
||||
<h3>{{ "science_calc_step1_title" | t(locale) }}</h3>
|
||||
<p style="font-family: ui-monospace, monospace">{{ "science_calc_step1_value" | t(locale) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-top: var(--space-3)">
|
||||
<h3>{{ "science_calc_step2_title" | t(locale) }}</h3>
|
||||
<p style="font-family: ui-monospace, monospace">
|
||||
{{ "science_calc_step2_ra" | t(locale) }}<br>
|
||||
{{ "science_calc_step2_dec" | t(locale) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-top: var(--space-3)">
|
||||
<h3>{{ "science_calc_arcus_title" | t(locale) }}</h3>
|
||||
<p>{{ "science_calc_arcus_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-top: var(--space-3)">
|
||||
<h3>{{ "science_calc_sources_title" | t(locale) }}</h3>
|
||||
<p>{{ "science_calc_sources_text" | t(locale) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</section>
|
||||
@@ -9,12 +9,13 @@ if [ ! -d "node_modules" ]; then
|
||||
fi
|
||||
|
||||
echo "🚀 Lancement du serveur de développement..."
|
||||
echo "📱 L'application sera accessible sur http://localhost:8080"
|
||||
echo "📱 L'application sera accessible sur http://localhost:5173/gwada-sirius/"
|
||||
echo ""
|
||||
echo "Commandes disponibles:"
|
||||
echo " - Ctrl+C : Arrêter le serveur"
|
||||
echo " - npm run build : Créer une version de production (11ty + Vite -> _site/)"
|
||||
echo " - npm run build : Créer une version de production (SvelteKit -> build/)"
|
||||
echo " - npm run preview : Prévisualiser la version de production"
|
||||
echo " - npm run check : Vérifier types et accessibilité"
|
||||
echo ""
|
||||
|
||||
npm run dev
|
||||
|
||||
|
Before Width: | Height: | Size: 234 B After Width: | Height: | Size: 234 B |
@@ -0,0 +1,22 @@
|
||||
/* Fraunces variable (display, titres) + Atkinson Hyperlegible (corps) — auto-hébergées */
|
||||
@font-face {
|
||||
font-family: 'Fraunces';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url('/gwada-sirius/fonts/fraunces-variable.woff2') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Atkinson Hyperlegible';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/gwada-sirius/fonts/atkinson-400.woff2') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Atkinson Hyperlegible';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/gwada-sirius/fonts/atkinson-700.woff2') format('woff2');
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Sirius en Guadeloupe — Wep Ronpet",
|
||||
"short_name": "Sirius Gwada",
|
||||
"description": "Lever héliaque de Sirius en Guadeloupe : astronomie, culture et observation.",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#141a33",
|
||||
"theme_color": "#141a33",
|
||||
"icons": [{ "src": "favicon.svg", "sizes": "any", "type": "image/svg+xml" }]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://cyber-mawonaj.github.io/gwada-sirius/sitemap.xml
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://cyber-mawonaj.github.io/gwada-sirius/</loc>
|
||||
<lastmod>2026-07-21</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cyber-mawonaj.github.io/gwada-sirius/sirius-science/</loc>
|
||||
<lastmod>2026-07-21</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cyber-mawonaj.github.io/gwada-sirius/culture-savoirs/</loc>
|
||||
<lastmod>2026-07-21</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cyber-mawonaj.github.io/gwada-sirius/observer/</loc>
|
||||
<lastmod>2026-07-21</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cyber-mawonaj.github.io/gwada-sirius/associations/</loc>
|
||||
<lastmod>2026-07-21</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cyber-mawonaj.github.io/gwada-sirius/en/</loc>
|
||||
<lastmod>2026-07-21</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cyber-mawonaj.github.io/gwada-sirius/en/sirius-science/</loc>
|
||||
<lastmod>2026-07-21</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cyber-mawonaj.github.io/gwada-sirius/en/culture-savoirs/</loc>
|
||||
<lastmod>2026-07-21</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cyber-mawonaj.github.io/gwada-sirius/en/observer/</loc>
|
||||
<lastmod>2026-07-21</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cyber-mawonaj.github.io/gwada-sirius/en/associations/</loc>
|
||||
<lastmod>2026-07-21</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cyber-mawonaj.github.io/gwada-sirius/ht/</loc>
|
||||
<lastmod>2026-07-21</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cyber-mawonaj.github.io/gwada-sirius/ht/sirius-science/</loc>
|
||||
<lastmod>2026-07-21</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cyber-mawonaj.github.io/gwada-sirius/ht/culture-savoirs/</loc>
|
||||
<lastmod>2026-07-21</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cyber-mawonaj.github.io/gwada-sirius/ht/observer/</loc>
|
||||
<lastmod>2026-07-21</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://cyber-mawonaj.github.io/gwada-sirius/ht/associations/</loc>
|
||||
<lastmod>2026-07-21</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -0,0 +1,28 @@
|
||||
import adapter from '@sveltejs/adapter-static';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
preprocess: vitePreprocess(),
|
||||
kit: {
|
||||
adapter: adapter({
|
||||
pages: 'build',
|
||||
assets: 'build',
|
||||
precompress: false,
|
||||
strict: true
|
||||
}),
|
||||
// GitHub Pages : le site est servi sous /gwada-sirius/
|
||||
// relative: false — chemins absolus partout (sinon $app/paths.base vaut ".."
|
||||
// au prerender et les comparaisons de pathname se cassent).
|
||||
paths: {
|
||||
base: '/gwada-sirius',
|
||||
relative: false
|
||||
},
|
||||
prerender: {
|
||||
handleHttpError: 'warn',
|
||||
handleMissingId: 'warn'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()]
|
||||
});
|
||||
Reference in New Issue
Block a user