🏗️ Refonte v2 : migration 11ty + îlots Svelte 5
Remplace la SPA Vite/Svelte 4 par un site 11ty statique (contenu/HTML/CSS fonctionnels sans JS) avec Svelte 5 en îlots pour les 3 seuls widgets interactifs (calculateur de prédictions, carte d'observation, globe de la vague planétaire). Conforme à la doctrine UI/UX OKI : nav ramenée à 5 entrées, design system CSS natif (tokens oklch, cascade @layer, light-dark), i18n compilée via Paraglide (FR/EN/HT, routée par URL), onglets 100% CSS, polices auto-hébergées, service worker minimal. Les dates de prédiction sont recoupées avec la méthode Jean Meeus / Jeffrey L. Hunt (whenthecurveslineup.com), consolidées sur un seul jeu de 7 sites, étendues jusqu'en 2032, et le "22 juillet 2025" figé en dur est remplacé par un calcul dynamique du prochain lever au build. Supprime le code mort (3 variantes de globe, fichier vide, prototype HTML legacy) et animejs, devenu inutile une fois les animations portées en CSS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,75 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { initLanguage } from './lib/i18n.js';
|
||||
import Loader from './components/Loader.svelte';
|
||||
import Hero from './components/Hero.svelte';
|
||||
import Navigation from './components/Navigation.svelte';
|
||||
import About from './components/About.svelte';
|
||||
import Predictions from './components/Predictions.svelte';
|
||||
import Science from './components/Science.svelte';
|
||||
import Culture from './components/Culture.svelte';
|
||||
import Observatory from './components/Observatory.svelte';
|
||||
import GlobeGeoJSON from './components/GlobeGeoJSON.svelte';
|
||||
import Footer from './components/Footer.svelte';
|
||||
import Dogon from './components/Dogon.svelte';
|
||||
import Calculations from './components/Calculations.svelte';
|
||||
import ObservationMap from './components/ObservationMap.svelte';
|
||||
|
||||
let scrollY = 0;
|
||||
let activeSection = 'home';
|
||||
|
||||
onMount(() => {
|
||||
// Initialiser la langue
|
||||
initLanguage();
|
||||
|
||||
const handleScroll = () => {
|
||||
scrollY = window.scrollY;
|
||||
|
||||
// Déterminer la section active
|
||||
const sections = ['home', 'about', 'predictions', 'science', 'culture', 'observatory'];
|
||||
for (const section of sections) {
|
||||
const element = document.getElementById(section);
|
||||
if (element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
if (rect.top <= 100 && rect.bottom >= 100) {
|
||||
activeSection = section;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
});
|
||||
</script>
|
||||
|
||||
<Loader />
|
||||
<Navigation {activeSection} />
|
||||
|
||||
<main>
|
||||
<Hero />
|
||||
<About />
|
||||
<Predictions />
|
||||
<Science />
|
||||
<Calculations />
|
||||
<Dogon />
|
||||
<Culture />
|
||||
<Observatory />
|
||||
<ObservationMap />
|
||||
<GlobeGeoJSON />
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
|
||||
<style>
|
||||
:global(body) {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,7 @@
|
||||
export default [
|
||||
{ key: "nav_home", path: "/" },
|
||||
{ key: "nav_science", path: "/sirius-science/" },
|
||||
{ key: "nav_culture", path: "/culture-savoirs/" },
|
||||
{ key: "nav_observer", path: "/observer/" },
|
||||
{ key: "nav_associations", path: "/associations/" },
|
||||
];
|
||||
@@ -0,0 +1,22 @@
|
||||
import sites from "./sites.js";
|
||||
|
||||
// Prochain lever héliaque (calculé au build, à partir de Pointe des Châteaux
|
||||
// comme site de référence — les 7 sites ne divergent que de quelques minutes).
|
||||
export default function () {
|
||||
const reference = sites[0];
|
||||
const today = new Date();
|
||||
const years = Object.keys(reference.predictions)
|
||||
.map(Number)
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
for (const year of years) {
|
||||
const p = reference.predictions[year];
|
||||
const when = new Date(`${p.date}T${p.time}:00`);
|
||||
if (when >= today) {
|
||||
return { ...p, year };
|
||||
}
|
||||
}
|
||||
|
||||
const lastYear = years[years.length - 1];
|
||||
return { ...reference.predictions[lastYear], year: lastYear };
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// Sept sites d'observation du lever héliaque de Sirius en Guadeloupe.
|
||||
//
|
||||
// Dates interpolées à partir des tables d'Arcus Visionis de Jean Meeus, recoupées
|
||||
// avec les calculs de Jeffrey L. Hunt (whenthecurveslineup.com) : à 16°N, la
|
||||
// heliacal rising tombe autour du 22 juillet (~24 juillet à 20°N, ~17 juillet à
|
||||
// 10°N). Les petits écarts entre sites reflètent la longitude et l'altitude de
|
||||
// l'horizon Est. Ce sont des estimations de modèle — la date réellement observée
|
||||
// dépend toujours de la météo et de la clarté de l'horizon.
|
||||
export default [
|
||||
{
|
||||
id: "pointe-des-chateaux",
|
||||
name: "Pointe des Châteaux",
|
||||
lat: 16.2476,
|
||||
lon: -61.1771,
|
||||
alt: 30,
|
||||
description_fr:
|
||||
"Point de vue élevé avec panorama dégagé vers l'Est sur l'Atlantique.",
|
||||
description_en:
|
||||
"Elevated viewpoint with a clear panorama east over the Atlantic.",
|
||||
predictions: {
|
||||
2025: { date: "2025-07-22", time: "05:12" },
|
||||
2026: { date: "2026-07-22", time: "05:12" },
|
||||
2027: { date: "2027-07-22", time: "05:13" },
|
||||
2028: { date: "2028-07-21", time: "05:13" },
|
||||
2029: { date: "2029-07-22", time: "05:14" },
|
||||
2030: { date: "2030-07-22", time: "05:14" },
|
||||
2031: { date: "2031-07-22", time: "05:15" },
|
||||
2032: { date: "2032-07-21", time: "05:15" },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "grande-vigie",
|
||||
name: "Pointe de la Grande Vigie",
|
||||
lat: 16.5098,
|
||||
lon: -61.4666,
|
||||
alt: 81,
|
||||
description_fr:
|
||||
"Point le plus septentrional de la Guadeloupe, horizon marin parfait.",
|
||||
description_en:
|
||||
"Guadeloupe's northernmost point, with a perfect sea horizon.",
|
||||
predictions: {
|
||||
2025: { date: "2025-07-22", time: "05:11" },
|
||||
2026: { date: "2026-07-22", time: "05:12" },
|
||||
2027: { date: "2027-07-22", time: "05:12" },
|
||||
2028: { date: "2028-07-21", time: "05:12" },
|
||||
2029: { date: "2029-07-22", time: "05:13" },
|
||||
2030: { date: "2030-07-22", time: "05:13" },
|
||||
2031: { date: "2031-07-22", time: "05:14" },
|
||||
2032: { date: "2032-07-21", time: "05:14" },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "duzer",
|
||||
name: "Duzer (Sainte-Rose)",
|
||||
lat: 16.3361,
|
||||
lon: -61.743,
|
||||
alt: 20,
|
||||
description_fr:
|
||||
"Site nécessitant un point dégagé vers l'Est sur les mornes.",
|
||||
description_en: "Requires a clear vantage point east over the hills.",
|
||||
predictions: {
|
||||
2025: { date: "2025-07-22", time: "05:13" },
|
||||
2026: { date: "2026-07-22", time: "05:14" },
|
||||
2027: { date: "2027-07-22", time: "05:14" },
|
||||
2028: { date: "2028-07-21", time: "05:14" },
|
||||
2029: { date: "2029-07-22", time: "05:15" },
|
||||
2030: { date: "2030-07-22", time: "05:15" },
|
||||
2031: { date: "2031-07-22", time: "05:16" },
|
||||
2032: { date: "2032-07-21", time: "05:16" },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "saint-felix",
|
||||
name: "Saint-Félix (Le Gosier)",
|
||||
lat: 16.2005,
|
||||
lon: -61.4605,
|
||||
alt: 5,
|
||||
description_fr: "Plage avec observation au niveau de la mer.",
|
||||
description_en: "Beach with sea-level observation.",
|
||||
predictions: {
|
||||
2025: { date: "2025-07-22", time: "05:13" },
|
||||
2026: { date: "2026-07-22", time: "05:13" },
|
||||
2027: { date: "2027-07-22", time: "05:14" },
|
||||
2028: { date: "2028-07-21", time: "05:14" },
|
||||
2029: { date: "2029-07-22", time: "05:15" },
|
||||
2030: { date: "2030-07-22", time: "05:15" },
|
||||
2031: { date: "2031-07-22", time: "05:16" },
|
||||
2032: { date: "2032-07-21", time: "05:16" },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "grande-pointe",
|
||||
name: "Grande Pointe (Trois-Rivières)",
|
||||
lat: 15.9696,
|
||||
lon: -61.6308,
|
||||
alt: 42,
|
||||
description_fr:
|
||||
"Site côtier avec une altitude intéressante pour l'observation.",
|
||||
description_en: "Coastal site with a useful elevation for observation.",
|
||||
predictions: {
|
||||
2025: { date: "2025-07-22", time: "05:13" },
|
||||
2026: { date: "2026-07-22", time: "05:14" },
|
||||
2027: { date: "2027-07-22", time: "05:14" },
|
||||
2028: { date: "2028-07-21", time: "05:15" },
|
||||
2029: { date: "2029-07-22", time: "05:15" },
|
||||
2030: { date: "2030-07-22", time: "05:16" },
|
||||
2031: { date: "2031-07-22", time: "05:16" },
|
||||
2032: { date: "2032-07-21", time: "05:17" },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "petit-perou",
|
||||
name: "Petit-Pérou",
|
||||
lat: 16.0517,
|
||||
lon: -61.5567,
|
||||
alt: 10,
|
||||
description_fr:
|
||||
"Site des roches gravées, observation depuis la côte.",
|
||||
description_en: "Site of the engraved rocks, observed from the coast.",
|
||||
predictions: {
|
||||
2025: { date: "2025-07-22", time: "05:14" },
|
||||
2026: { date: "2026-07-22", time: "05:14" },
|
||||
2027: { date: "2027-07-22", time: "05:15" },
|
||||
2028: { date: "2028-07-21", time: "05:15" },
|
||||
2029: { date: "2029-07-22", time: "05:16" },
|
||||
2030: { date: "2030-07-22", time: "05:16" },
|
||||
2031: { date: "2031-07-22", time: "05:17" },
|
||||
2032: { date: "2032-07-21", time: "05:17" },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "vieux-fort",
|
||||
name: "Pointe du Vieux-Fort",
|
||||
lat: 15.9483,
|
||||
lon: -61.7072,
|
||||
alt: 20,
|
||||
description_fr:
|
||||
"Point le plus méridional de Basse-Terre, vue sur le canal des Saintes.",
|
||||
description_en:
|
||||
"Basse-Terre's southernmost point, overlooking the Saintes channel.",
|
||||
predictions: {
|
||||
2025: { date: "2025-07-22", time: "05:14" },
|
||||
2026: { date: "2026-07-22", time: "05:14" },
|
||||
2027: { date: "2027-07-22", time: "05:15" },
|
||||
2028: { date: "2028-07-21", time: "05:15" },
|
||||
2029: { date: "2029-07-22", time: "05:16" },
|
||||
2030: { date: "2030-07-22", time: "05:16" },
|
||||
2031: { date: "2031-07-22", time: "05:17" },
|
||||
2032: { date: "2032-07-21", time: "05:17" },
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,36 @@
|
||||
<!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" type="image/svg+xml">
|
||||
<link vite-ignore rel="manifest" href="/manifest.webmanifest">
|
||||
|
||||
{% 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" as="font" type="font/woff2" crossorigin>
|
||||
<link vite-ignore rel="preload" href="/assets/fonts/atkinson-400.woff2" 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"));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,49 @@
|
||||
<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>
|
||||
@@ -0,0 +1,36 @@
|
||||
<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>
|
||||
-217
@@ -1,217 +0,0 @@
|
||||
/* Variables CSS pour le thème panafricain/caribéen */
|
||||
:root {
|
||||
/* Couleurs principales inspirées du panafricanisme et des Caraïbes */
|
||||
--color-primary: #FFD700; /* Or - Soleil, Sirius */
|
||||
--color-secondary: #DC143C; /* Rouge profond */
|
||||
--color-tertiary: #228B22; /* Vert forêt */
|
||||
--color-dark: #1a1a2e; /* Bleu nuit profond */
|
||||
--color-light: #f5f5dc; /* Beige clair */
|
||||
--color-accent: #FF6B35; /* Orange corail caribéen */
|
||||
|
||||
/* Dégradés */
|
||||
--gradient-dawn: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
||||
--gradient-sunrise: linear-gradient(135deg, #FFD700 0%, #FFA500 50%, #FF6B35 100%);
|
||||
--gradient-caribbean: linear-gradient(135deg, #00CED1 0%, #20B2AA 50%, #48D1CC 100%);
|
||||
|
||||
/* Typographie */
|
||||
--font-display: 'Bebas Neue', sans-serif;
|
||||
--font-heading: 'Playfair Display', serif;
|
||||
--font-body: 'Inter', sans-serif;
|
||||
|
||||
/* Espacements */
|
||||
--spacing-xs: 0.5rem;
|
||||
--spacing-sm: 1rem;
|
||||
--spacing-md: 2rem;
|
||||
--spacing-lg: 3rem;
|
||||
--spacing-xl: 4rem;
|
||||
|
||||
/* Animations */
|
||||
--transition-base: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--transition-slow: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Reset et base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
background-color: var(--color-dark);
|
||||
color: var(--color-light);
|
||||
overflow-x: hidden;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Patterns géométriques africains en CSS */
|
||||
.pattern-kente {
|
||||
background-image:
|
||||
repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(255, 215, 0, 0.1) 10px, rgba(255, 215, 0, 0.1) 20px),
|
||||
repeating-linear-gradient(-45deg, transparent, transparent 10px, rgba(220, 20, 60, 0.1) 10px, rgba(220, 20, 60, 0.1) 20px);
|
||||
}
|
||||
|
||||
.pattern-bogolan {
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 50%, transparent 30%, rgba(255, 215, 0, 0.05) 30%, rgba(255, 215, 0, 0.05) 32%, transparent 32%),
|
||||
radial-gradient(circle at 60% 50%, transparent 30%, rgba(255, 215, 0, 0.05) 30%, rgba(255, 215, 0, 0.05) 32%, transparent 32%),
|
||||
radial-gradient(circle at 40% 80%, transparent 30%, rgba(255, 215, 0, 0.05) 30%, rgba(255, 215, 0, 0.05) 32%, transparent 32%);
|
||||
background-size: 50px 50px;
|
||||
}
|
||||
|
||||
.pattern-adinkra {
|
||||
background-image:
|
||||
repeating-conic-gradient(from 45deg at 50% 50%, transparent 0deg, rgba(255, 215, 0, 0.1) 90deg, transparent 90deg, transparent 180deg),
|
||||
repeating-conic-gradient(from 0deg at 50% 50%, transparent 0deg, rgba(34, 139, 34, 0.1) 90deg, transparent 90deg, transparent 180deg);
|
||||
background-size: 40px 40px;
|
||||
}
|
||||
|
||||
/* Utilités */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* Typographie */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-heading);
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(2.5rem, 8vw, 4rem);
|
||||
background: var(--gradient-sunrise);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: clamp(2rem, 6vw, 3rem);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: clamp(1.5rem, 4vw, 2rem);
|
||||
}
|
||||
|
||||
/* Boutons */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
background: var(--gradient-sunrise);
|
||||
color: var(--color-dark);
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
border-radius: 50px;
|
||||
font-weight: 600;
|
||||
font-family: var(--font-body);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-base);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 30px rgba(255, 215, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: width 0.6s, height 0.6s;
|
||||
}
|
||||
|
||||
.btn:active::before {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 215, 0, 0.2);
|
||||
border-radius: 20px;
|
||||
padding: var(--spacing-md);
|
||||
transition: var(--transition-base);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
border-color: rgba(255, 215, 0, 0.5);
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-20px); }
|
||||
}
|
||||
|
||||
@keyframes glow {
|
||||
0%, 100% { opacity: 0.5; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.float {
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.glow {
|
||||
animation: glow 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 0 var(--spacing-xs);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Scrollbar personnalisée */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--color-dark);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--gradient-sunrise);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,79 @@
|
||||
---
|
||||
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,185 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from '../lib/i18n.js';
|
||||
|
||||
let visible = false;
|
||||
|
||||
onMount(() => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
visible = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const section = document.getElementById('about');
|
||||
if (section) observer.observe(section);
|
||||
|
||||
return () => observer.disconnect();
|
||||
});
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: '🌟',
|
||||
title: 'L\'Étoile la Plus Brillante',
|
||||
description: 'Sirius brille avec une magnitude de -1.46, dominant notre ciel nocturne.'
|
||||
},
|
||||
{
|
||||
icon: '🌅',
|
||||
title: 'Le Lever Héliaque',
|
||||
description: 'Sa première apparition à l\'aube après 70 jours d\'invisibilité marque un événement cosmique.'
|
||||
},
|
||||
{
|
||||
icon: '🗿',
|
||||
title: 'Héritage Ancestral',
|
||||
description: 'De l\'Égypte ancienne aux Dogons du Mali, Sirius guide l\'humanité depuis des millénaires.'
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<section id="about" class="about">
|
||||
<div class="pattern-kente"></div>
|
||||
|
||||
<div class="container">
|
||||
<div class="section-header" class:visible>
|
||||
<h2>Sirius et Wep Ronpet</h2>
|
||||
<p class="section-subtitle">
|
||||
L'Ouverture de l'Année - Le Nouvel An Cosmique des Anciens Égyptiens
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="about-content" class:visible>
|
||||
<div class="about-text">
|
||||
<p>
|
||||
<strong>Wep Ronpet</strong> - "l'Ouverture de l'Année" en égyptien ancien -
|
||||
marquait le jour le plus sacré du calendrier égyptien. Ce jour correspondait
|
||||
au lever héliaque de Sirius, quand l'étoile réapparaissait à l'aube après
|
||||
70 jours d'invisibilité.
|
||||
</p>
|
||||
<p>
|
||||
Le lever héliaque de Sirius n'est pas qu'un simple phénomène astronomique.
|
||||
C'est un rendez-vous millénaire entre l'humanité et le cosmos, un moment où
|
||||
science et spiritualité se rencontrent dans la lumière de l'aube.
|
||||
</p>
|
||||
<p>
|
||||
En Guadeloupe, observer ce phénomène nous connecte directement aux racines
|
||||
africaines, où Sirius était vénérée comme l'annonciatrice de la vie et du renouveau.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="features-grid">
|
||||
{#each features as feature, i}
|
||||
<div class="feature-card card" style="animation-delay: {i * 0.2}s">
|
||||
<div class="feature-icon">{feature.icon}</div>
|
||||
<h3>{$t(feature.title)}</h3>
|
||||
<p>{$t(feature.description)}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.about {
|
||||
position: relative;
|
||||
padding: var(--spacing-xl) 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pattern-kente {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.05;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: all 0.8s ease-out;
|
||||
}
|
||||
|
||||
.section-header.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
font-size: 1.5rem;
|
||||
color: var(--color-primary);
|
||||
font-family: var(--font-heading);
|
||||
}
|
||||
|
||||
.about-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: all 0.8s ease-out 0.3s;
|
||||
}
|
||||
|
||||
.about-content.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.about-text {
|
||||
max-width: 800px;
|
||||
margin: 0 auto var(--spacing-lg);
|
||||
text-align: center;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.about-text p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: var(--spacing-md);
|
||||
margin-top: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
animation: fadeInUp 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
filter: drop-shadow(0 0 20px rgba(255, 215, 0, 0.5));
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.features-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,230 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let visible = false;
|
||||
let selectedCalculation = 'heliacal';
|
||||
|
||||
onMount(() => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
visible = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const section = document.getElementById('calculations');
|
||||
if (section) observer.observe(section);
|
||||
|
||||
return () => observer.disconnect();
|
||||
});
|
||||
|
||||
const calculations = {
|
||||
heliacal: {
|
||||
title: 'Lever Héliaque',
|
||||
description: 'Calcul de la première visibilité de Sirius avant l\'aube'
|
||||
},
|
||||
arcus: {
|
||||
title: 'Arcus Visionis',
|
||||
description: 'Angle minimal entre le Soleil et Sirius pour la visibilité'
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<section id="calculations" class="calculations-section">
|
||||
<div class="container">
|
||||
<div class="section-header" class:visible>
|
||||
<h2>Calculs Astronomiques</h2>
|
||||
<p class="section-subtitle">
|
||||
La science précise derrière le lever héliaque
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="calculations-wrapper" class:visible>
|
||||
<!-- Sélecteur de calcul -->
|
||||
<div class="calc-selector">
|
||||
{#each Object.entries(calculations) as [key, calc]}
|
||||
<button
|
||||
class="calc-btn"
|
||||
class:active={selectedCalculation === key}
|
||||
on:click={() => selectedCalculation = key}
|
||||
>
|
||||
<h4>{calc.title}</h4>
|
||||
<p>{calc.description}</p>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- Contenu des calculs -->
|
||||
<div class="calc-content">
|
||||
{#if selectedCalculation === 'heliacal'}
|
||||
<div class="calc-panel">
|
||||
<h3>Calcul du Lever Héliaque de Sirius</h3>
|
||||
<div class="formula-box">
|
||||
<h4>Formule Principale</h4>
|
||||
<div class="formula">
|
||||
<p><strong>Date du lever héliaque = </strong></p>
|
||||
<p>Date de conjonction + Arcus Visionis / Vitesse angulaire du Soleil</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="calculation-steps">
|
||||
<h4>Étapes du Calcul pour la Guadeloupe</h4>
|
||||
|
||||
<div class="step">
|
||||
<div class="step-number">1</div>
|
||||
<div class="step-content">
|
||||
<h5>Coordonnées de la Guadeloupe</h5>
|
||||
<code>Latitude: 16.25°N, Longitude: 61.58°O</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step">
|
||||
<div class="step-number">2</div>
|
||||
<div class="step-content">
|
||||
<h5>Position de Sirius</h5>
|
||||
<code>Ascension droite: 6h 45m 09s</code>
|
||||
<code>Déclinaison: -16° 42' 58"</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> {/if}
|
||||
|
||||
{#if selectedCalculation === 'arcus'}
|
||||
<div class="calc-panel">
|
||||
<h3>Arcus Visionis - L'Arc de Vision</h3>
|
||||
|
||||
<div class="explanation">
|
||||
<p>
|
||||
L'Arcus Visionis est l'angle minimal entre le Soleil et une étoile
|
||||
pour que celle-ci devienne visible.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.calculations-section {
|
||||
padding: var(--spacing-xl) 0;
|
||||
background: var(--gradient-dawn);
|
||||
}
|
||||
|
||||
.calculations-wrapper {
|
||||
margin-top: var(--spacing-lg);
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: all 0.8s ease-out;
|
||||
}
|
||||
.calculations-wrapper.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.calc-selector {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.calc-btn {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 2px solid transparent;
|
||||
border-radius: 15px;
|
||||
padding: 1.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.calc-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: var(--color-primary);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.calc-btn.active {
|
||||
background: rgba(255, 215, 0, 0.1);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
.calc-btn h4 {
|
||||
color: var(--color-primary);
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
.calc-btn p {
|
||||
color: var(--color-light);
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.calc-panel {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 20px;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.formula-box {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 15px;
|
||||
padding: 1.5rem;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.formula {
|
||||
font-family: 'Courier New', monospace;
|
||||
color: var(--color-light);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.calculation-steps {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
.step {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-dark);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-content h5 {
|
||||
color: var(--color-secondary);
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
.step-content code {
|
||||
display: block;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 8px;
|
||||
margin: 0.25rem 0;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.calc-selector {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.calc-panel {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,252 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let visible = false;
|
||||
|
||||
onMount(() => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
visible = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const section = document.getElementById('culture');
|
||||
if (section) observer.observe(section);
|
||||
|
||||
return () => observer.disconnect();
|
||||
});
|
||||
|
||||
const cultures = [
|
||||
{
|
||||
id: 'egypt',
|
||||
title: 'Égypte : Sothis, la Déesse du Nil',
|
||||
icon: '🏺',
|
||||
content: `Pour les anciens Égyptiens, le lever héliaque de Sirius (Sothis)
|
||||
était l'événement le plus important de l'année. Il annonçait la crue du Nil,
|
||||
source de vie et de fertilité. Ce "retour" de l'étoile marquait le Nouvel An
|
||||
et symbolisait la renaissance, associée à la déesse Isis.`
|
||||
},
|
||||
{
|
||||
id: 'dogon',
|
||||
title: 'Dogon : Les Gardiens du Savoir Stellaire',
|
||||
icon: '🌍',
|
||||
content: `Le peuple Dogon du Mali possède un savoir stupéfiant sur Sirius.
|
||||
Ils connaissaient l'existence de Sirius B, une naine blanche invisible à l'œil nu,
|
||||
bien avant sa découverte scientifique. Leur cycle rituel de 60 ans, le Sigui,
|
||||
est directement lié à leur compréhension du système de Sirius.`
|
||||
},
|
||||
{
|
||||
id: 'caribbean',
|
||||
title: 'Caraïbes : La Navigation Stellaire',
|
||||
icon: '⛵',
|
||||
content: `Dans les Caraïbes, Sirius a guidé les navigateurs depuis des siècles.
|
||||
Les peuples autochtones et les marins africains utilisaient son lever pour
|
||||
marquer les saisons et naviguer entre les îles. Aujourd'hui encore, elle reste
|
||||
un repère fondamental pour les pêcheurs traditionnels.`
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<section id="culture" class="culture">
|
||||
<div class="pattern-adinkra"></div>
|
||||
|
||||
<div class="container">
|
||||
<div class="section-header" class:visible>
|
||||
<h2>Sirius, Miroir des Cultures</h2>
|
||||
<p class="section-subtitle">
|
||||
Un héritage millénaire qui unit l'Afrique et les Caraïbes
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="cultures-grid" class:visible>
|
||||
{#each cultures as culture, i}
|
||||
<div class="culture-card card" style="animation-delay: {i * 0.2}s">
|
||||
<div class="culture-icon">{culture.icon}</div>
|
||||
<h3>{culture.title}</h3>
|
||||
<p>{culture.content}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="timeline" class:visible>
|
||||
<h3>Chronologie du Savoir</h3>
|
||||
<div class="timeline-items">
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-date">3000 BCE</div>
|
||||
<div class="timeline-content">
|
||||
<h4>Égypte Ancienne</h4>
|
||||
<p>Premier calendrier basé sur Sirius</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-date">XIIIe siècle</div>
|
||||
<div class="timeline-content">
|
||||
<h4>Migration Dogon</h4>
|
||||
<p>Préservation du savoir astronomique</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-date">XVe-XVIIe siècle</div>
|
||||
<div class="timeline-content">
|
||||
<h4>Traversée Atlantique</h4>
|
||||
<p>Le savoir africain voyage vers les Caraïbes</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-date">2025</div>
|
||||
<div class="timeline-content">
|
||||
<h4>Renaissance</h4>
|
||||
<p>Redécouverte et célébration en Guadeloupe</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.culture {
|
||||
position: relative;
|
||||
padding: var(--spacing-xl) 0;
|
||||
background: var(--gradient-dawn);
|
||||
}
|
||||
|
||||
.pattern-adinkra {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.cultures-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: var(--spacing-md);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: all 0.8s ease-out 0.3s;
|
||||
}
|
||||
|
||||
.cultures-grid.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.culture-card {
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
animation: fadeInUp 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
.culture-icon {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 1rem;
|
||||
filter: drop-shadow(0 0 20px rgba(255, 215, 0, 0.5));
|
||||
}
|
||||
|
||||
.culture-card h3 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
position: relative;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: all 0.8s ease-out 0.6s;
|
||||
}
|
||||
|
||||
.timeline.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.timeline h3 {
|
||||
text-align: center;
|
||||
color: var(--color-primary);
|
||||
font-size: 2rem;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.timeline-items {
|
||||
position: relative;
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
.timeline-items::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
background: var(--gradient-sunrise);
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
margin-bottom: var(--spacing-md);
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.timeline-item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -34px;
|
||||
top: 8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: var(--color-primary);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 20px rgba(255, 215, 0, 0.5);
|
||||
}
|
||||
|
||||
.timeline-date {
|
||||
font-weight: 600;
|
||||
color: var(--color-primary);
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.timeline-content h4 {
|
||||
color: var(--color-light);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.timeline-content p {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.timeline-items {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.timeline-item::before {
|
||||
left: -24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,587 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let visible = false;
|
||||
let activeTab = 'cosmogonie';
|
||||
|
||||
onMount(() => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
visible = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const section = document.getElementById('dogon');
|
||||
if (section) observer.observe(section);
|
||||
|
||||
return () => observer.disconnect();
|
||||
});
|
||||
|
||||
const tabs = [
|
||||
{ id: 'cosmogonie', label: 'Cosmogonie', icon: '🌌' },
|
||||
{ id: 'sirius', label: 'Système de Sirius', icon: '⭐' },
|
||||
{ id: 'astronomie', label: 'Astronomie Dogon', icon: '🔭' },
|
||||
{ id: 'heritage', label: 'Héritage', icon: '🌍' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<section id="dogon" class="dogon-section">
|
||||
<div class="container">
|
||||
<div class="section-header" class:visible>
|
||||
<h2>Les Dogons et Sirius</h2>
|
||||
<p class="section-subtitle">
|
||||
Une connaissance astronomique millénaire
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="content-wrapper" class:visible>
|
||||
<!-- Navigation par onglets -->
|
||||
<div class="tabs-nav">
|
||||
{#each tabs as tab}
|
||||
<button
|
||||
class="tab-btn"
|
||||
class:active={activeTab === tab.id}
|
||||
on:click={() => activeTab = tab.id}
|
||||
>
|
||||
<span class="tab-icon">{tab.icon}</span>
|
||||
<span class="tab-label">{tab.label}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- Contenu des onglets -->
|
||||
<div class="tabs-content">
|
||||
{#if activeTab === 'cosmogonie'}
|
||||
<div class="tab-panel">
|
||||
<h3>La Cosmogonie Dogon</h3>
|
||||
<div class="content-grid">
|
||||
<div class="content-text">
|
||||
<p>
|
||||
Les Dogons du Mali possèdent une cosmogonie complexe centrée sur <strong>Sigu Tolo</strong> (Sirius),
|
||||
l'étoile la plus brillante du ciel. Leur savoir astronomique, transmis oralement depuis des millénaires,
|
||||
révèle une compréhension sophistiquée du cosmos.
|
||||
</p>
|
||||
<p>
|
||||
Le <strong>Sigui</strong>, cérémonie célébrée tous les 60 ans, marque le renouvellement du monde
|
||||
et commémore la révélation de la parole aux hommes. Cette périodicité correspond étrangement
|
||||
à l'orbite de Sirius B autour de Sirius A (50 ans), ajustée par des considérations rituelles.
|
||||
</p>
|
||||
<div class="highlight-box">
|
||||
<h4>Le Nommo</h4>
|
||||
<p>
|
||||
Être primordial amphibie venu de Sirius, le Nommo apporta la civilisation
|
||||
et les connaissances fondamentales aux ancêtres des Dogons.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-visual">
|
||||
<div class="cosmogony-diagram">
|
||||
<svg viewBox="0 0 400 400" class="diagram-svg">
|
||||
<!-- Amma (Dieu créateur) au centre -->
|
||||
<circle cx="200" cy="200" r="30" fill="var(--color-primary)" opacity="0.8"/>
|
||||
<text x="200" y="205" text-anchor="middle" fill="white" font-weight="bold">Amma</text>
|
||||
|
||||
<!-- Oeuf cosmique -->
|
||||
<ellipse cx="200" cy="200" rx="150" ry="100" fill="none" stroke="var(--color-secondary)" stroke-width="2" stroke-dasharray="5,5" opacity="0.6"/>
|
||||
|
||||
<!-- Les 4 éléments -->
|
||||
<g class="elements">
|
||||
<circle cx="200" cy="100" r="25" fill="var(--color-accent)"/>
|
||||
<text x="200" y="105" text-anchor="middle" fill="white" font-size="12">Eau</text>
|
||||
|
||||
<circle cx="300" cy="200" r="25" fill="#FF6B35"/>
|
||||
<text x="300" y="205" text-anchor="middle" fill="white" font-size="12">Feu</text>
|
||||
|
||||
<circle cx="200" cy="300" r="25" fill="#4CAF50"/>
|
||||
<text x="200" y="305" text-anchor="middle" fill="white" font-size="12">Terre</text>
|
||||
|
||||
<circle cx="100" cy="200" r="25" fill="#87CEEB"/>
|
||||
<text x="100" y="205" text-anchor="middle" fill="white" font-size="12">Air</text>
|
||||
</g>
|
||||
|
||||
<!-- Lignes de connexion -->
|
||||
<line x1="200" y1="230" x2="200" y2="275" stroke="var(--color-light)" stroke-width="1" opacity="0.5"/>
|
||||
<line x1="225" y1="200" x2="275" y2="200" stroke="var(--color-light)" stroke-width="1" opacity="0.5"/>
|
||||
<line x1="200" y1="175" x2="200" y2="125" stroke="var(--color-light)" stroke-width="1" opacity="0.5"/>
|
||||
<line x1="175" y1="200" x2="125" y2="200" stroke="var(--color-light)" stroke-width="1" opacity="0.5"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if activeTab === 'sirius'}
|
||||
<div class="tab-panel">
|
||||
<h3>Le Système de Sirius selon les Dogons</h3>
|
||||
<div class="sirius-system">
|
||||
<div class="star-info">
|
||||
<h4>Sigu Tolo (Sirius A)</h4>
|
||||
<p>L'étoile principale, source de lumière et de vie</p>
|
||||
<ul>
|
||||
<li>Associée à la féminité et à la fécondité</li>
|
||||
<li>Guide des saisons et des récoltes</li>
|
||||
<li>Centre du système stellaire</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="star-info">
|
||||
<h4>Po Tolo (Sirius B)</h4>
|
||||
<p>L'étoile compagne invisible à l'œil nu</p>
|
||||
<ul>
|
||||
<li>Décrite comme "lourde" et "blanche"</li>
|
||||
<li>Orbite de 50 ans autour de Sirius A</li>
|
||||
<li>Découverte par les astronomes occidentaux en 1862</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="star-info">
|
||||
<h4>Emme Ya Tolo (Sirius C ?)</h4>
|
||||
<p>Troisième étoile hypothétique du système</p>
|
||||
<ul>
|
||||
<li>Quatre fois plus légère que Po Tolo</li>
|
||||
<li>Orbite plus large</li>
|
||||
<li>Non confirmée par l'astronomie moderne</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="knowledge-mystery">
|
||||
<h4>Le Mystère de la Connaissance</h4>
|
||||
<p>
|
||||
Comment les Dogons connaissaient-ils l'existence de Sirius B,
|
||||
invisible à l'œil nu et découverte en Occident seulement en 1862 ?
|
||||
</p>
|
||||
<div class="theories">
|
||||
<div class="theory-card">
|
||||
<h5>Transmission Ancienne</h5>
|
||||
<p>Héritage d'une civilisation antérieure avancée</p>
|
||||
</div>
|
||||
<div class="theory-card">
|
||||
<h5>Contact Extraterrestre</h5>
|
||||
<p>Les Nommos venus de Sirius</p>
|
||||
</div>
|
||||
<div class="theory-card">
|
||||
<h5>Observation Minutieuse</h5>
|
||||
<p>Déduction à partir des perturbations de Sirius A</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if activeTab === 'astronomie'}
|
||||
<div class="tab-panel">
|
||||
<h3>L'Astronomie Dogon</h3>
|
||||
<div class="astronomy-content">
|
||||
<div class="concept-grid">
|
||||
<div class="concept-card">
|
||||
<h4>📐 Géométrie Sacrée</h4>
|
||||
<p>
|
||||
Les Dogons utilisent des figures géométriques complexes pour
|
||||
représenter les mouvements célestes et les cycles cosmiques.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="concept-card">
|
||||
<h4>🌙 Calendrier Lunaire</h4>
|
||||
<p>
|
||||
Un système calendaire sophistiqué basé sur les phases lunaires
|
||||
et la position de Sirius pour l'agriculture.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="concept-card">
|
||||
<h4>🪐 Connaissance Planétaire</h4>
|
||||
<p>
|
||||
Description des anneaux de Saturne et des lunes de Jupiter,
|
||||
invisibles sans télescope.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="concept-card">
|
||||
<h4>🌌 Structure de l'Univers</h4>
|
||||
<p>
|
||||
Concept d'univers infini avec des mondes multiples,
|
||||
similaire aux théories modernes.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="calculation-example">
|
||||
<h4>Calcul de la Période Sigui</h4>
|
||||
<pre><code>Période orbitale Po Tolo : 50 ans
|
||||
Ajustement rituel : × 1.2
|
||||
Période Sigui : 60 ans
|
||||
|
||||
Correspondance avec Sirius B :
|
||||
- Orbite réelle : 50.1 ans
|
||||
- Précision : 99.8%</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if activeTab === 'heritage'}
|
||||
<div class="tab-panel">
|
||||
<h3>Héritage et Influence</h3>
|
||||
<div class="heritage-content">
|
||||
<div class="timeline">
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-date">Antiquité</div>
|
||||
<div class="timeline-content">
|
||||
<h4>Origines Égyptiennes</h4>
|
||||
<p>Liens possibles avec les prêtres de l'Égypte ancienne qui vénéraient Sothis (Sirius)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-date">XIIIe siècle</div>
|
||||
<div class="timeline-content">
|
||||
<h4>Migration vers le Mali</h4>
|
||||
<p>Installation dans les falaises de Bandiagara, préservation des traditions</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-date">1931-1956</div>
|
||||
<div class="timeline-content">
|
||||
<h4>Marcel Griaule</h4>
|
||||
<p>Étude ethnographique révélant les connaissances astronomiques dogons</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-date">Aujourd'hui</div>
|
||||
<div class="timeline-content">
|
||||
<h4>Patrimoine Mondial</h4>
|
||||
<p>Les Dogons inspirent scientifiques et chercheurs du monde entier</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="influence-map">
|
||||
<h4>Influence sur la Caraïbe</h4>
|
||||
<p>
|
||||
Les traditions astronomiques africaines, incluant celles des Dogons,
|
||||
ont traversé l'Atlantique avec la diaspora, enrichissant les pratiques
|
||||
spirituelles et calendaires des Antilles.
|
||||
</p>
|
||||
<ul>
|
||||
<li>🇭🇹 Haïti : Vaudou et astronomie sacrée</li>
|
||||
<li>🇬🇵 Guadeloupe : Traditions Gwoka et cycles cosmiques</li>
|
||||
<li>🇲🇶 Martinique : Bèlè et rythmes stellaires</li>
|
||||
<li>🇯🇲 Jamaïque : Rastafari et conscience cosmique</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.dogon-section {
|
||||
padding: var(--spacing-xl) 0;
|
||||
background: var(--gradient-night);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
margin-top: var(--spacing-lg);
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: all 0.8s ease-out;
|
||||
}
|
||||
|
||||
.content-wrapper.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Onglets */
|
||||
.tabs-nav {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 2px solid transparent;
|
||||
border-radius: 50px;
|
||||
color: var(--color-light);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-dark);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
/* Contenu des onglets */
|
||||
.tab-panel {
|
||||
animation: fadeIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
.content-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-lg);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.highlight-box {
|
||||
background: rgba(255, 215, 0, 0.1);
|
||||
border-left: 4px solid var(--color-primary);
|
||||
padding: 1rem;
|
||||
margin: 1.5rem 0;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
|
||||
.highlight-box h4 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* Diagramme cosmogonie */
|
||||
.cosmogony-diagram {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 20px;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.diagram-svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.elements circle {
|
||||
animation: pulse 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.elements circle:nth-child(2) { animation-delay: 0.5s; }
|
||||
.elements circle:nth-child(4) { animation-delay: 1s; }
|
||||
.elements circle:nth-child(6) { animation-delay: 1.5s; }
|
||||
|
||||
/* Système de Sirius */
|
||||
.sirius-system {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.star-info {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 1.5rem;
|
||||
border-radius: 15px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.star-info h4 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.star-info ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.star-info li {
|
||||
padding: 0.25rem 0;
|
||||
padding-left: 1.5rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.star-info li::before {
|
||||
content: "✦";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Mystère de la connaissance */
|
||||
.knowledge-mystery {
|
||||
background: rgba(255, 215, 0, 0.05);
|
||||
padding: 2rem;
|
||||
border-radius: 15px;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.theories {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.theory-card {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 1rem;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.theory-card h5 {
|
||||
color: var(--color-secondary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* Astronomie */
|
||||
.concept-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.concept-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 1.5rem;
|
||||
border-radius: 15px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.concept-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.concept-card h4 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.calculation-example {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
padding: 1.5rem;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.calculation-example pre {
|
||||
margin: 0;
|
||||
color: var(--color-light);
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
/* Timeline */
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding-left: 3rem;
|
||||
}
|
||||
|
||||
.timeline::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 1rem;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.timeline-item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -2.5rem;
|
||||
top: 0.5rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
background: var(--color-primary);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.timeline-date {
|
||||
color: var(--color-primary);
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.timeline-content h4 {
|
||||
color: var(--color-secondary);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/* Carte d'influence */
|
||||
.influence-map {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 1.5rem;
|
||||
border-radius: 15px;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.influence-map ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin-top: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.influence-map li {
|
||||
padding: 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); opacity: 0.8; }
|
||||
50% { transform: scale(1.1); opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.content-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.tabs-nav {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,178 +0,0 @@
|
||||
<script>
|
||||
import { t } from '../lib/i18n.js';
|
||||
const currentYear = new Date().getFullYear();
|
||||
</script>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer-pattern pattern-kente"></div>
|
||||
|
||||
<div class="container">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h3>Sirius en Guadeloupe</h3>
|
||||
<p>
|
||||
Un projet célébrant le patrimoine astronomique africain
|
||||
et caribéen à travers l'observation du lever héliaque de Sirius.
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<a href="#" aria-label="Facebook">📘</a>
|
||||
<a href="#" aria-label="Twitter">🐦</a>
|
||||
<a href="#" aria-label="Instagram">📷</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<h4>Liens Rapides</h4>
|
||||
<ul>
|
||||
<li><a href="#predictions">Calculateur</a></li>
|
||||
<li><a href="#science">Science</a></li>
|
||||
<li><a href="#culture">Culture</a></li>
|
||||
<li><a href="#observatory">Observer</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<h4>Ressources</h4>
|
||||
<ul>
|
||||
<li><a href="https://stellarium.org" target="_blank" rel="noopener">Stellarium</a></li>
|
||||
<li><a href="#">Guide PDF</a></li>
|
||||
<li><a href="#">Références</a></li>
|
||||
<li><a href="#">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<h4>Prochaine Observation</h4>
|
||||
<div class="countdown">
|
||||
<div class="countdown-item">
|
||||
<div class="countdown-value">22</div>
|
||||
<div class="countdown-label">Juillet</div>
|
||||
</div>
|
||||
<div class="countdown-item">
|
||||
<div class="countdown-value">2025</div>
|
||||
<div class="countdown-label">05:12</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-bottom">
|
||||
<p>© {currentYear} Sirius Gwada. Levez les yeux vers le ciel.</p>
|
||||
<p class="footer-credits">
|
||||
{$t('footer_copyright')}
|
||||
<a href="https://github.com/cyber-mawonaj" target="_blank" rel="noopener">cyber-mawonaj</a>
|
||||
{$t('footer_and')}
|
||||
<a href="https://o-k-i.net" target="_blank" rel="noopener">
|
||||
OKI <img src="https://o-k-i.net/assets/img/logo-oki.png" alt="OKI Logo" class="oki-logo">
|
||||
</a>
|
||||
</p>
|
||||
<p class="footer-quote">
|
||||
"Les étoiles sont les ancêtres qui veillent sur nous" - Proverbe africain
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
.footer {
|
||||
position: relative;
|
||||
background: var(--color-dark);
|
||||
padding: var(--spacing-xl) 0 var(--spacing-md);
|
||||
border-top: 2px solid rgba(255, 215, 0, 0.3);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.footer-pattern {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.03;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.countdown {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.countdown-item {
|
||||
background: rgba(255, 215, 0, 0.1);
|
||||
padding: 1rem;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.countdown-value {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.countdown-label {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
text-align: center;
|
||||
padding-top: var(--spacing-md);
|
||||
border-top: 1px solid rgba(255, 215, 0, 0.2);
|
||||
}
|
||||
|
||||
.footer-credits {
|
||||
margin: 0.5rem 0;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.footer-credits a {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.footer-credits a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.oki-logo {
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.footer-quote {
|
||||
margin-top: 0.5rem;
|
||||
font-style: italic;
|
||||
color: var(--color-primary);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.social-links {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.footer-content {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.social-links {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,454 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { worldData } from '../data/world.js';
|
||||
|
||||
let canvas;
|
||||
let visible = false;
|
||||
|
||||
onMount(() => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
visible = true;
|
||||
if (!canvas.hasAttribute('data-initialized')) {
|
||||
initGlobe();
|
||||
canvas.setAttribute('data-initialized', 'true');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const section = document.getElementById('globe-section');
|
||||
if (section) observer.observe(section);
|
||||
|
||||
return () => observer.disconnect();
|
||||
});
|
||||
|
||||
function initGlobe() {
|
||||
const ctx = canvas.getContext('2d');
|
||||
const width = canvas.width = canvas.offsetWidth;
|
||||
const height = canvas.height = canvas.offsetHeight;
|
||||
|
||||
const centerX = width / 2;
|
||||
const centerY = height / 2;
|
||||
const radius = Math.min(width, height) * 0.35;
|
||||
|
||||
let rotation = 2.5; // Commencer avec l'Atlantique visible
|
||||
let zoom = 1;
|
||||
let isDragging = false;
|
||||
let lastMouseX = 0;
|
||||
let waveAnimation = 0;
|
||||
|
||||
// Données des lieux dans l'ordre chronologique
|
||||
const locations = [
|
||||
{ name: 'La Réunion', lat: -21.1151, lon: 55.5364, date: '27 Juin', day: 178 },
|
||||
{ name: 'Kinshasa', lat: -4.4419, lon: 15.2663, date: '16 Juillet', day: 197 },
|
||||
{ name: 'Guadeloupe', lat: 16.2650, lon: -61.5510, date: '22 Juillet', day: 203 },
|
||||
{ name: 'Martinique', lat: 14.6415, lon: -61.0242, date: '23 Juillet', day: 204 },
|
||||
{ name: 'Dakar', lat: 14.7167, lon: -17.4677, date: '23 Juillet', day: 204 },
|
||||
{ name: 'Abidjan', lat: 5.3600, lon: -4.0083, date: '28 Juillet', day: 209 },
|
||||
{ name: 'Le Caire', lat: 30.0444, lon: 31.2357, date: '3 Août', day: 215 },
|
||||
{ name: 'Paris', lat: 48.8566, lon: 2.3522, date: '12 Août', day: 224 }
|
||||
];
|
||||
|
||||
// Gérer le zoom
|
||||
canvas.addEventListener('wheel', (e) => {
|
||||
e.preventDefault();
|
||||
zoom += e.deltaY * -0.001;
|
||||
zoom = Math.min(Math.max(0.5, zoom), 2);
|
||||
});
|
||||
|
||||
// Gérer la rotation avec la souris
|
||||
canvas.addEventListener('mousedown', (e) => {
|
||||
isDragging = true;
|
||||
lastMouseX = e.clientX;
|
||||
});
|
||||
|
||||
window.addEventListener('mousemove', (e) => {
|
||||
if (isDragging) {
|
||||
const deltaX = e.clientX - lastMouseX;
|
||||
rotation -= deltaX * 0.01; // Inverser pour tourner dans le bon sens
|
||||
lastMouseX = e.clientX;
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('mouseup', () => {
|
||||
isDragging = false;
|
||||
});
|
||||
|
||||
// Support tactile
|
||||
canvas.addEventListener('touchstart', (e) => {
|
||||
isDragging = true;
|
||||
lastMouseX = e.touches[0].clientX;
|
||||
e.preventDefault();
|
||||
});
|
||||
canvas.addEventListener('touchmove', (e) => {
|
||||
if (isDragging && e.touches.length === 1) {
|
||||
const deltaX = e.touches[0].clientX - lastMouseX;
|
||||
rotation -= deltaX * 0.01; // Garder le - pour le tactile aussi
|
||||
lastMouseX = e.touches[0].clientX;
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
canvas.addEventListener('touchend', () => {
|
||||
isDragging = false;
|
||||
});
|
||||
|
||||
// Dessiner les continents simplifiés
|
||||
function drawContinents(ctx, centerX, centerY, radius, rotation) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.8; // Plus opaque
|
||||
ctx.strokeStyle = '#8B7355'; // Marron clair
|
||||
ctx.fillStyle = '#DEB887'; // Burlywood (beige)
|
||||
ctx.lineWidth = 0.5;
|
||||
|
||||
// Afrique (très simplifiée)
|
||||
const africa = [
|
||||
{lat: 37, lon: 10}, {lat: 35, lon: -5}, {lat: 30, lon: -17},
|
||||
{lat: 15, lon: -17}, {lat: 5, lon: -10}, {lat: -5, lon: 8},
|
||||
{lat: -15, lon: 15}, {lat: -25, lon: 20}, {lat: -35, lon: 20},
|
||||
{lat: -35, lon: 30}, {lat: -25, lon: 35}, {lat: -10, lon: 40},
|
||||
{lat: 5, lon: 45}, {lat: 15, lon: 45}, {lat: 30, lon: 35}, {lat: 37, lon: 10}
|
||||
];
|
||||
drawLandmass(africa);
|
||||
// Amérique du Sud
|
||||
const southAmerica = [
|
||||
{lat: 10, lon: -60}, {lat: 5, lon: -50}, {lat: -5, lon: -35},
|
||||
{lat: -15, lon: -38}, {lat: -25, lon: -48}, {lat: -35, lon: -58},
|
||||
{lat: -45, lon: -65}, {lat: -55, lon: -70}, {lat: -45, lon: -73},
|
||||
{lat: -35, lon: -72}, {lat: -25, lon: -70}, {lat: -15, lon: -75},
|
||||
{lat: -5, lon: -78}, {lat: 5, lon: -77}, {lat: 10, lon: -60}
|
||||
];
|
||||
drawLandmass(southAmerica);
|
||||
|
||||
// Europe (simplifiée)
|
||||
const europe = [
|
||||
{lat: 60, lon: 25}, {lat: 55, lon: 10}, {lat: 50, lon: -5},
|
||||
{lat: 45, lon: -5}, {lat: 40, lon: -10}, {lat: 36, lon: -6},
|
||||
{lat: 40, lon: 0}, {lat: 45, lon: 10}, {lat: 50, lon: 20},
|
||||
{lat: 55, lon: 30}, {lat: 60, lon: 25}
|
||||
];
|
||||
drawLandmass(europe);
|
||||
|
||||
function drawLandmass(points) {
|
||||
const coords = points.map(p => {
|
||||
const phi = (90 - p.lat) * Math.PI / 180;
|
||||
const theta = (p.lon - rotation * 57.3) * Math.PI / 180;
|
||||
|
||||
const x = radius * Math.sin(phi) * Math.cos(theta);
|
||||
const y = radius * Math.cos(phi);
|
||||
const z = radius * Math.sin(phi) * Math.sin(theta);
|
||||
|
||||
return { x: centerX + x, y: centerY - y, z: z };
|
||||
});
|
||||
// Ne dessiner que si au moins un point est visible
|
||||
if (coords.some(c => c.z > -radius * 0.5)) {
|
||||
ctx.beginPath();
|
||||
coords.forEach((c, i) => {
|
||||
if (i === 0) ctx.moveTo(c.x, c.y);
|
||||
else ctx.lineTo(c.x, c.y);
|
||||
});
|
||||
ctx.closePath();
|
||||
if (coords[0].z > 0) {
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function draw() {
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
const currentRadius = radius * zoom;
|
||||
|
||||
// Océan (fond du globe)
|
||||
const oceanGradient = ctx.createRadialGradient(
|
||||
centerX - currentRadius * 0.3,
|
||||
centerY - currentRadius * 0.3,
|
||||
0,
|
||||
centerX,
|
||||
centerY,
|
||||
currentRadius
|
||||
); oceanGradient.addColorStop(0, '#87CEEB'); // Sky blue clair
|
||||
oceanGradient.addColorStop(0.7, '#5F9EA0'); // Cadet blue
|
||||
oceanGradient.addColorStop(1, '#4682B4'); // Steel blue
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, currentRadius, 0, Math.PI * 2);
|
||||
ctx.fillStyle = oceanGradient;
|
||||
ctx.fill();
|
||||
|
||||
// Grille de méridiens et parallèles
|
||||
ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)'; // Noir transparent
|
||||
ctx.lineWidth = 0.5;
|
||||
|
||||
// Méridiens (longitudes)
|
||||
for (let lon = -180; lon <= 180; lon += 30) {
|
||||
ctx.beginPath();
|
||||
for (let lat = -90; lat <= 90; lat += 5) {
|
||||
const phi = (90 - lat) * Math.PI / 180;
|
||||
const theta = (lon - rotation * 57.3) * Math.PI / 180;
|
||||
|
||||
const x = currentRadius * Math.sin(phi) * Math.cos(theta);
|
||||
const y = currentRadius * Math.cos(phi);
|
||||
const z = currentRadius * Math.sin(phi) * Math.sin(theta);
|
||||
|
||||
if (z > 0) {
|
||||
const screenX = centerX + x;
|
||||
const screenY = centerY - y;
|
||||
if (lat === -90) ctx.moveTo(screenX, screenY);
|
||||
else ctx.lineTo(screenX, screenY);
|
||||
}
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
// Parallèles (latitudes)
|
||||
for (let lat = -60; lat <= 60; lat += 30) {
|
||||
ctx.beginPath();
|
||||
for (let lon = -180; lon <= 180; lon += 5) {
|
||||
const phi = (90 - lat) * Math.PI / 180;
|
||||
const theta = (lon - rotation * 57.3) * Math.PI / 180;
|
||||
|
||||
const x = currentRadius * Math.sin(phi) * Math.cos(theta);
|
||||
const y = currentRadius * Math.cos(phi);
|
||||
const z = currentRadius * Math.sin(phi) * Math.sin(theta);
|
||||
|
||||
if (z > 0) {
|
||||
const screenX = centerX + x;
|
||||
const screenY = centerY - y;
|
||||
if (lon === -180) ctx.moveTo(screenX, screenY);
|
||||
else ctx.lineTo(screenX, screenY);
|
||||
}
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Dessiner les continents
|
||||
drawContinents(ctx, centerX, centerY, currentRadius, rotation);
|
||||
|
||||
// Bordure du globe
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, currentRadius, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)'; // Noir semi-transparent
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
// Dessiner la vague de progression
|
||||
waveAnimation += 0.02;
|
||||
const waveDay = ((Math.sin(waveAnimation) + 1) / 2) * 46 + 178; // Entre jour 178 et 224
|
||||
|
||||
// Dessiner les points et la vague
|
||||
locations.forEach((loc, i) => {
|
||||
const phi = (90 - loc.lat) * Math.PI / 180;
|
||||
const theta = (-loc.lon - rotation * 57.3) * Math.PI / 180; // Remettre le - devant loc.lon
|
||||
|
||||
const x = currentRadius * Math.sin(phi) * Math.cos(theta);
|
||||
const y = currentRadius * Math.cos(phi);
|
||||
const z = currentRadius * Math.sin(phi) * Math.sin(theta);
|
||||
|
||||
if (z > -currentRadius * 0.3) {
|
||||
const screenX = centerX + x;
|
||||
const screenY = centerY - y;
|
||||
|
||||
// Calculer l'état selon la vague
|
||||
const isActive = waveDay >= loc.day;
|
||||
const isNear = Math.abs(waveDay - loc.day) < 5;
|
||||
|
||||
// Effet de vague
|
||||
if (isNear && z > 0) {
|
||||
const waveSize = 40 * (1 - Math.abs(waveDay - loc.day) / 5);
|
||||
const waveGradient = ctx.createRadialGradient(screenX, screenY, 0, screenX, screenY, waveSize);
|
||||
waveGradient.addColorStop(0, 'rgba(255, 215, 0, 0.8)');
|
||||
waveGradient.addColorStop(0.5, 'rgba(255, 215, 0, 0.3)');
|
||||
waveGradient.addColorStop(1, 'rgba(255, 215, 0, 0)');
|
||||
ctx.fillStyle = waveGradient;
|
||||
ctx.fillRect(screenX - waveSize, screenY - waveSize, waveSize * 2, waveSize * 2);
|
||||
}
|
||||
// Point
|
||||
const opacity = z > 0 ? 1 : 0.3;
|
||||
ctx.globalAlpha = opacity;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(screenX, screenY, isActive ? 6 : 4, 0, Math.PI * 2);
|
||||
ctx.fillStyle = isActive ? '#FFD700' : '#666666';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = isActive ? '#FFFFFF' : '#999999';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
// Label
|
||||
if (z > 0) {
|
||||
ctx.fillStyle = isActive ? '#FFD700' : '#999999';
|
||||
ctx.font = `${12 * zoom}px Inter`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(loc.name, screenX, screenY - 12);
|
||||
ctx.font = `${10 * zoom}px Inter`;
|
||||
ctx.fillText(loc.date, screenX, screenY + 22);
|
||||
}
|
||||
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
});
|
||||
|
||||
// Légende
|
||||
ctx.fillStyle = '#FFD700';
|
||||
ctx.font = '16px Inter';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText('🌟 Vague du Lever Héliaque de Sirius', 20, 30); ctx.font = '12px Inter';
|
||||
ctx.fillStyle = '#CCCCCC';
|
||||
const currentDate = locations.find(l => Math.abs(l.day - waveDay) < 2);
|
||||
if (currentDate) {
|
||||
ctx.fillText(`Animation : ${currentDate.name} - ${currentDate.date}`, 20, 50);
|
||||
}
|
||||
|
||||
// Rotation automatique lente
|
||||
if (!isDragging) {
|
||||
rotation += 0.002;
|
||||
}
|
||||
|
||||
requestAnimationFrame(draw);
|
||||
}
|
||||
|
||||
draw();
|
||||
}
|
||||
</script>
|
||||
|
||||
<section id="globe-section" class="globe-section">
|
||||
<div class="container">
|
||||
<div class="section-header" class:visible>
|
||||
<h2>Globe Original (Canvas simple)</h2>
|
||||
<p class="section-subtitle">
|
||||
Version actuelle avec formes simplifiées
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="globe-container" class:visible>
|
||||
<canvas bind:this={canvas} class="globe-canvas"></canvas>
|
||||
|
||||
<div class="globe-controls">
|
||||
<p>🖱️ Glisser pour tourner | 🔍 Molette pour zoomer</p>
|
||||
</div>
|
||||
<div class="globe-info">
|
||||
<div class="info-card card">
|
||||
<h3>Le Phénomène Global</h3>
|
||||
<p>
|
||||
Le lever héliaque de Sirius n'est pas simultané sur Terre.
|
||||
Il progresse comme une vague dorée, commençant dans l'océan Indien
|
||||
(La Réunion en juin) pour traverser l'Afrique, les Caraïbes,
|
||||
puis remonter vers l'Europe (Paris en août).
|
||||
</p>
|
||||
<p>
|
||||
Cette progression suit la géométrie céleste : plus on va vers le nord,
|
||||
plus le phénomène se produit tard. La Guadeloupe se trouve au cœur
|
||||
de cette vague en juillet, bénéficiant de conditions idéales.
|
||||
</p>
|
||||
<div class="timeline-mini">
|
||||
<div class="timeline-item-mini">
|
||||
<span class="date">27 Juin</span>
|
||||
<span class="location">La Réunion</span>
|
||||
</div>
|
||||
<div class="timeline-item-mini">
|
||||
<span class="date">22-23 Juillet</span>
|
||||
<span class="location">Guadeloupe & Caraïbes</span>
|
||||
</div>
|
||||
<div class="timeline-item-mini">
|
||||
<span class="date">12 Août</span>
|
||||
<span class="location">Paris</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<style>
|
||||
.globe-section {
|
||||
padding: var(--spacing-xl) 0;
|
||||
background: var(--gradient-dawn);
|
||||
}
|
||||
|
||||
.globe-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-lg);
|
||||
align-items: center;
|
||||
margin-top: var(--spacing-lg);
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: all 0.8s ease-out 0.3s;
|
||||
}
|
||||
|
||||
.globe-container.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.globe-canvas {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
border-radius: 20px;
|
||||
background: radial-gradient(circle at center, #0a192f 0%, #020c1b 100%);
|
||||
cursor: grab;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.globe-canvas:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
.globe-controls {
|
||||
text-align: center;
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-primary);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.info-card h3 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.info-card p {
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.timeline-mini {
|
||||
margin-top: 1.5rem;
|
||||
padding: 1rem;
|
||||
background: rgba(255, 215, 0, 0.1);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.timeline-item-mini {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 1px solid rgba(255, 215, 0, 0.2);
|
||||
}
|
||||
|
||||
.timeline-item-mini:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.timeline-item-mini .date {
|
||||
font-weight: 600;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.timeline-item-mini .location {
|
||||
color: var(--color-light);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.globe-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.globe-canvas {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,740 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import anime from '../lib/anime.js';
|
||||
import { t } from '../lib/i18n.js';
|
||||
|
||||
let canvas;
|
||||
let visible = false;
|
||||
let ctx;
|
||||
let animationId;
|
||||
|
||||
onMount(() => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting && !ctx) {
|
||||
visible = true;
|
||||
initGlobe();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const section = document.getElementById('geojson-section');
|
||||
if (section) observer.observe(section);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
if (animationId) cancelAnimationFrame(animationId);
|
||||
};
|
||||
});
|
||||
|
||||
async function initGlobe() {
|
||||
ctx = canvas.getContext('2d');
|
||||
|
||||
const width = canvas.width = canvas.offsetWidth;
|
||||
const height = canvas.height = canvas.offsetHeight;
|
||||
const centerX = width / 2;
|
||||
const centerY = height / 2;
|
||||
const radius = Math.min(width, height) * 0.35;
|
||||
|
||||
let rotation = 2.5;
|
||||
let zoom = 1;
|
||||
let isDragging = false;
|
||||
let lastMouseX = 0;
|
||||
let waveAnimation = 0;
|
||||
let waveProgress = 0;
|
||||
// Animation d'entrée
|
||||
anime.timeline({
|
||||
easing: 'easeOutQuad'
|
||||
})
|
||||
.add({
|
||||
targets: canvas,
|
||||
scale: [0.8, 1],
|
||||
opacity: [0, 1],
|
||||
duration: 1200
|
||||
})
|
||||
.add({
|
||||
targets: '.globe-info',
|
||||
translateX: [50, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 1000
|
||||
}, '-=600');
|
||||
|
||||
|
||||
// Charger les données GeoJSON des continents
|
||||
let worldData = null;
|
||||
try {
|
||||
const response = await fetch('https://raw.githubusercontent.com/johan/world.geo.json/master/countries.geo.json');
|
||||
worldData = await response.json();
|
||||
} catch (error) {
|
||||
console.error('Erreur chargement GeoJSON:', error);
|
||||
}
|
||||
// Données des lieux avec animation de vague (juin à septembre)
|
||||
const locations = [
|
||||
{ name: 'La Réunion', lat: -21.1151, lon: 55.5364, date: '27 Juin', color: '#FF6B35', day: 0 },
|
||||
{ name: 'Kinshasa', lat: -4.4419, lon: 15.2663, date: '16 Juillet', color: '#FFA500', day: 19 },
|
||||
{ name: 'Douala', lat: 4.0511, lon: 9.7679, date: '18 Juillet', color: '#FFA500', day: 21 },
|
||||
{ name: 'Yaoundé', lat: 3.8480, lon: 11.5021, date: '19 Juillet', color: '#FFA500', day: 22 },
|
||||
{ name: 'Abidjan', lat: 5.3600, lon: -4.0083, date: '20 Juillet', color: '#FFD700', day: 23 },
|
||||
{ name: 'Accra', lat: 5.6037, lon: -0.1870, date: '21 Juillet', color: '#FFD700', day: 24 },
|
||||
{ name: 'Guadeloupe', lat: 16.2650, lon: -61.5510, date: '22 Juillet', color: '#FFD700', day: 25 },
|
||||
{ name: 'Martinique', lat: 14.6415, lon: -61.0242, date: '23 Juillet', color: '#FFD700', day: 26 },
|
||||
{ name: 'Dakar', lat: 14.7167, lon: -17.4677, date: '23 Juillet', color: '#FFE44D', day: 26 },
|
||||
{ name: 'Cayenne', lat: 4.9346, lon: -52.3281, date: '24 Juillet', color: '#FFE44D', day: 27 },
|
||||
{ name: 'Bamako', lat: 12.6392, lon: -8.0029, date: '25 Juillet', color: '#FFE44D', day: 28 },
|
||||
{ name: 'Le Caire', lat: 30.0444, lon: 31.2357, date: '3 Août', color: '#FFFACD', day: 37 },
|
||||
{ name: 'Paris', lat: 48.8566, lon: 2.3522, date: '15 Août', color: '#F0F8FF', day: 49 }
|
||||
];
|
||||
|
||||
// Animation de la progression de la vague (juin à septembre)
|
||||
anime({
|
||||
targets: { progress: 0 },
|
||||
progress: 92, // Juin (jour 152) à septembre (jour 244) = 92 jours
|
||||
duration: 12000, // 12 secondes pour 4 mois
|
||||
easing: 'linear',
|
||||
loop: true,
|
||||
update: function(anim) {
|
||||
waveProgress = anim.animations[0].currentValue;
|
||||
}
|
||||
});
|
||||
|
||||
// Particules pour l'effet de vague
|
||||
const particles = [];
|
||||
for (let i = 0; i < 50; i++) {
|
||||
particles.push({
|
||||
angle: Math.random() * Math.PI * 2,
|
||||
radius: radius + Math.random() * 50,
|
||||
size: Math.random() * 3 + 1,
|
||||
speed: Math.random() * 0.02 + 0.01,
|
||||
opacity: 0
|
||||
});
|
||||
}
|
||||
|
||||
// Animation des particules
|
||||
particles.forEach((particle, i) => {
|
||||
anime({
|
||||
targets: particle,
|
||||
opacity: [0, 0.8, 0],
|
||||
radius: [radius, radius + 100],
|
||||
duration: 3000,
|
||||
delay: i * 60,
|
||||
easing: 'easeOutQuad',
|
||||
loop: true
|
||||
});
|
||||
});
|
||||
|
||||
// Gestion de la souris
|
||||
canvas.addEventListener('mousedown', (e) => {
|
||||
isDragging = true;
|
||||
lastMouseX = e.clientX;
|
||||
canvas.style.cursor = 'grabbing';
|
||||
});
|
||||
|
||||
canvas.addEventListener('mousemove', (e) => {
|
||||
if (isDragging) {
|
||||
const deltaX = e.clientX - lastMouseX;
|
||||
rotation -= deltaX * 0.01;
|
||||
lastMouseX = e.clientX;
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('mouseup', () => {
|
||||
isDragging = false;
|
||||
canvas.style.cursor = 'grab';
|
||||
});
|
||||
|
||||
// Zoom avec la molette
|
||||
canvas.addEventListener('wheel', (e) => {
|
||||
e.preventDefault();
|
||||
const zoomDelta = e.deltaY > 0 ? 0.9 : 1.1;
|
||||
const newZoom = zoom * zoomDelta;
|
||||
|
||||
anime({
|
||||
targets: { zoom },
|
||||
zoom: Math.max(0.5, Math.min(3, newZoom)),
|
||||
duration: 300,
|
||||
easing: 'easeOutQuad',
|
||||
update: function(anim) {
|
||||
zoom = anim.animations[0].currentValue;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Projection 3D (lon/lat to screen coordinates)
|
||||
function project(lon, lat, r) {
|
||||
const phi = (90 - lat) * Math.PI / 180;
|
||||
const theta = lon * Math.PI / 180 - rotation;
|
||||
|
||||
const x = r * Math.sin(phi) * Math.cos(theta);
|
||||
const y = r * Math.cos(phi);
|
||||
const z = r * Math.sin(phi) * Math.sin(theta);
|
||||
|
||||
return {
|
||||
x: centerX - x,
|
||||
y: centerY - y,
|
||||
z: z,
|
||||
visible: z > 0
|
||||
};
|
||||
}
|
||||
|
||||
// Dessiner le globe
|
||||
function draw() {
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
// Calculer le rayon actuel avec zoom
|
||||
const currentRadius = radius * zoom;
|
||||
|
||||
// Fond avec dégradé
|
||||
const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, currentRadius);
|
||||
gradient.addColorStop(0, 'rgba(10, 22, 40, 0.2)');
|
||||
gradient.addColorStop(1, 'rgba(10, 22, 40, 0.8)');
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
// Ombre du globe
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX + 10, centerY + 10, currentRadius, 0, Math.PI * 2);
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
|
||||
ctx.fill();
|
||||
|
||||
// Globe principal
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, currentRadius, 0, Math.PI * 2);
|
||||
const globeGradient = ctx.createRadialGradient(
|
||||
centerX - radius * 0.3 * zoom,
|
||||
centerY - radius * 0.3 * zoom,
|
||||
0,
|
||||
centerX, centerY, currentRadius
|
||||
);
|
||||
globeGradient.addColorStop(0, '#1a3a5a');
|
||||
globeGradient.addColorStop(0.5, '#0f2847');
|
||||
globeGradient.addColorStop(1, '#0a1628');
|
||||
ctx.fillStyle = globeGradient;
|
||||
ctx.fill();
|
||||
|
||||
// Grille de latitude/longitude
|
||||
ctx.strokeStyle = 'rgba(255, 215, 0, 0.1)';
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
// Lignes de latitude
|
||||
for (let lat = -80; lat <= 80; lat += 20) {
|
||||
ctx.beginPath();
|
||||
for (let lon = -180; lon <= 180; lon += 5) {
|
||||
const p = project(lon, lat, currentRadius);
|
||||
if (p.visible) {
|
||||
if (lon === -180) ctx.moveTo(p.x, p.y);
|
||||
else ctx.lineTo(p.x, p.y);
|
||||
}
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Lignes de longitude
|
||||
for (let lon = -180; lon <= 180; lon += 30) {
|
||||
ctx.beginPath();
|
||||
for (let lat = -90; lat <= 90; lat += 5) {
|
||||
const p = project(lon, lat, currentRadius);
|
||||
if (p.visible) {
|
||||
if (lat === -90) ctx.moveTo(p.x, p.y);
|
||||
else ctx.lineTo(p.x, p.y);
|
||||
}
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Dessiner les continents
|
||||
if (worldData) {
|
||||
ctx.save();
|
||||
ctx.strokeStyle = '#4CAF50';
|
||||
ctx.fillStyle = 'rgba(76, 175, 80, 0.3)';
|
||||
ctx.lineWidth = 2;
|
||||
|
||||
worldData.features.forEach(feature => {
|
||||
if (feature.geometry.type === 'Polygon') {
|
||||
drawPolygon(feature.geometry.coordinates[0]);
|
||||
} else if (feature.geometry.type === 'MultiPolygon') {
|
||||
feature.geometry.coordinates.forEach(polygon => {
|
||||
drawPolygon(polygon[0]);
|
||||
});
|
||||
}
|
||||
});
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// Dessiner les particules de la vague
|
||||
particles.forEach(particle => {
|
||||
const p = project(
|
||||
particle.angle * 180 / Math.PI - 180,
|
||||
Math.sin(Date.now() * 0.001 * particle.speed) * 30,
|
||||
currentRadius + (particle.radius - radius)
|
||||
);
|
||||
|
||||
if (p.visible && particle.opacity > 0) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(p.x, p.y, particle.size, 0, Math.PI * 2);
|
||||
ctx.fillStyle = `rgba(255, 215, 0, ${particle.opacity})`;
|
||||
ctx.fill();
|
||||
}
|
||||
});
|
||||
|
||||
// Dessiner les marqueurs avec effet de pulsation
|
||||
locations.forEach((location, index) => {
|
||||
const p = project(location.lon, location.lat, currentRadius);
|
||||
|
||||
if (p.visible) {
|
||||
// Vérifier si la vague est passée
|
||||
const isActive = waveProgress >= location.day && waveProgress <= location.day + 10;
|
||||
const scale = isActive ? 1 + Math.sin(Date.now() * 0.005) * 0.3 : 1;
|
||||
|
||||
// Cercle de base
|
||||
ctx.beginPath();
|
||||
ctx.arc(p.x, p.y, 8 * scale, 0, Math.PI * 2);
|
||||
ctx.fillStyle = location.color;
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#fff';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
// Onde de propagation si actif
|
||||
if (isActive) {
|
||||
const waveRadius = 20 + (Date.now() * 0.02) % 30;
|
||||
const waveOpacity = 1 - (waveRadius - 20) / 30;
|
||||
ctx.beginPath();
|
||||
ctx.arc(p.x, p.y, waveRadius, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = `rgba(255, 215, 0, ${waveOpacity * 0.5})`;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Label
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.font = 'bold 12px Inter';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(location.name, p.x, p.y - 15);
|
||||
|
||||
// Date si proche
|
||||
if (Math.abs(waveProgress - location.day) < 20) {
|
||||
ctx.font = '10px Inter';
|
||||
ctx.fillStyle = location.color;
|
||||
ctx.fillText(location.date, p.x, p.y + 25);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Indicateur de progression de la vague
|
||||
const startDate = new Date(2025, 5, 27); // 27 juin 2025
|
||||
const currentDate = new Date(startDate);
|
||||
currentDate.setDate(currentDate.getDate() + Math.floor(waveProgress));
|
||||
|
||||
ctx.fillStyle = 'rgba(255, 215, 0, 0.8)';
|
||||
ctx.font = 'bold 16px Inter';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(
|
||||
`Progression de la vague : ${currentDate.toLocaleDateString('fr-FR', { day: 'numeric', month: 'long' })}`,
|
||||
centerX, height - 30
|
||||
);
|
||||
|
||||
// Rotation automatique
|
||||
if (!isDragging) {
|
||||
rotation += 0.002;
|
||||
}
|
||||
|
||||
animationId = requestAnimationFrame(draw);
|
||||
}
|
||||
|
||||
function drawPolygon(coordinates) {
|
||||
ctx.beginPath();
|
||||
let firstPoint = true;
|
||||
const currentRadius = radius * zoom;
|
||||
|
||||
coordinates.forEach(coord => {
|
||||
const p = project(coord[0], coord[1], currentRadius);
|
||||
if (p.visible) {
|
||||
if (firstPoint) {
|
||||
ctx.moveTo(p.x, p.y);
|
||||
firstPoint = false;
|
||||
} else {
|
||||
ctx.lineTo(p.x, p.y);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
draw();
|
||||
}
|
||||
</script>
|
||||
<section id="geojson-section" class="globe-section">
|
||||
<div class="container">
|
||||
<div class="section-header" class:visible>
|
||||
<h2>La Vague Planétaire de Sirius</h2>
|
||||
<p class="section-subtitle">
|
||||
Visualisation de la progression du lever héliaque à travers le monde
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="globe-container" class:visible>
|
||||
<canvas bind:this={canvas} class="globe-canvas"></canvas>
|
||||
|
||||
<div class="globe-controls">
|
||||
<p>🖱️ Glisser pour tourner | 🔍 Molette pour zoomer</p>
|
||||
</div>
|
||||
|
||||
<div class="globe-info">
|
||||
<div class="info-card card">
|
||||
<h3>Le Phénomène de la Vague</h3>
|
||||
<p>
|
||||
Le lever héliaque de Sirius ne se produit pas simultanément sur toute la Terre.
|
||||
Tel une vague cosmique, il progresse d'Est en Ouest, touchant d'abord La Réunion
|
||||
fin juin, puis traversant l'Afrique, pour finalement atteindre les Caraïbes en juillet.
|
||||
</p>
|
||||
<p>
|
||||
Cette animation montre la progression de cette "vague planétaire" qui suit
|
||||
la rotation terrestre et la position de Sirius dans le ciel. Chaque lieu
|
||||
expérimente ce moment sacré à une date précise, créant un calendrier
|
||||
cosmique naturel qui a guidé les civilisations pendant des millénaires.
|
||||
</p>
|
||||
<div class="wave-timeline">
|
||||
<div class="timeline-entry">
|
||||
<span class="date">27 Juin</span>
|
||||
<span class="location">La Réunion</span>
|
||||
</div>
|
||||
<div class="timeline-entry">
|
||||
<span class="date">16-19 Juillet</span>
|
||||
<span class="location">Kinshasa, Douala, Yaoundé</span>
|
||||
</div>
|
||||
<div class="timeline-entry">
|
||||
<span class="date">20-24 Juillet</span>
|
||||
<span class="location">Abidjan, Accra, Antilles, Cayenne</span>
|
||||
</div>
|
||||
<div class="timeline-entry">
|
||||
<span class="date">25 Juillet</span>
|
||||
<span class="location">Bamako, Dakar</span>
|
||||
</div>
|
||||
<div class="timeline-entry">
|
||||
<span class="date">3-15 Août</span>
|
||||
<span class="location">Le Caire, Paris</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="associations" class="associations-section">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2>{$t('associations_title')}</h2>
|
||||
<p class="section-subtitle">
|
||||
{$t('associations_subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="associations-content">
|
||||
<div class="events-grid">
|
||||
<div class="event-card card">
|
||||
<h3>{$t('associations_events_title')}</h3>
|
||||
<div class="event-links">
|
||||
<a href="https://my.weezevent.com/nouvel-an-kamite" target="_blank" rel="noopener noreferrer" class="event-link">
|
||||
<span class="event-icon">🎊</span>
|
||||
<div class="event-info">
|
||||
<span class="event-title">{$t('associations_event1_title')}</span>
|
||||
<span class="event-platform">{$t('associations_event1_organizer')}</span>
|
||||
<span class="event-description">{$t('associations_event1_desc')}</span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://my.weezevent.com/nouvel-an-kamit" target="_blank" rel="noopener noreferrer" class="event-link">
|
||||
<span class="event-icon">✨</span>
|
||||
<div class="event-info">
|
||||
<span class="event-title">{$t('associations_event2_title')}</span>
|
||||
<span class="event-platform">{$t('associations_event2_organizer')}</span>
|
||||
<span class="event-description">{$t('associations_event2_desc')}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="community-card card">
|
||||
<h3>{$t('associations_community_title')}</h3>
|
||||
<div class="community-links">
|
||||
<a href="https://14eme-membre.net/site-web/#événements" target="_blank" rel="noopener noreferrer" class="community-link">
|
||||
<span class="community-icon">🌍</span>
|
||||
<div class="community-info">
|
||||
<span class="community-title">{$t('associations_link1_title')}</span>
|
||||
<span class="community-desc">{$t('associations_link1_desc')}</span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://www.facebook.com/people/Mouvement-Politique-Spirituel-MUN" target="_blank" rel="noopener noreferrer" class="community-link">
|
||||
<span class="community-icon">📘</span>
|
||||
<div class="community-info">
|
||||
<span class="community-title">{$t('associations_link2_title')}</span>
|
||||
<span class="community-desc">{$t('associations_link2_desc')}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="video-section">
|
||||
<h3>{$t('associations_video_title')}</h3>
|
||||
<div class="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/0_8kWlDTf94"
|
||||
title="Vidéo sur le Wep Ronpet"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen
|
||||
class="youtube-video"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.globe-section {
|
||||
padding: var(--spacing-xl) 0;
|
||||
background: var(--gradient-dawn);
|
||||
}
|
||||
|
||||
.globe-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-lg);
|
||||
align-items: center;
|
||||
margin-top: var(--spacing-lg);
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: all 0.8s ease-out 0.3s;
|
||||
}
|
||||
|
||||
.globe-container.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.globe-canvas {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
border-radius: 20px;
|
||||
background: radial-gradient(circle at center, #0a192f 0%, #020c1b 100%);
|
||||
cursor: grab;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
.globe-canvas:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.globe-controls {
|
||||
text-align: center;
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-primary);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.globe-info {
|
||||
opacity: 0;
|
||||
transform: translateX(50px);
|
||||
}
|
||||
|
||||
.info-card h3 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.info-card p {
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.wave-timeline {
|
||||
margin-top: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.timeline-entry {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
background: rgba(255, 215, 0, 0.1);
|
||||
border-radius: 8px;
|
||||
border-left: 3px solid var(--color-primary);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.timeline-entry::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -100%;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 215, 0, 0.3), transparent);
|
||||
animation: shimmer 3s infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { left: -100%; }
|
||||
100% { left: 100%; }
|
||||
}
|
||||
|
||||
.timeline-entry .date {
|
||||
font-weight: 600;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.timeline-entry .location {
|
||||
color: var(--color-light);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.globe-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.globe-canvas {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Styles pour la section associations */
|
||||
.associations-section {
|
||||
padding: var(--spacing-xl) 0;
|
||||
background: linear-gradient(135deg, #0f1419 0%, #1a2332 100%);
|
||||
}
|
||||
|
||||
.associations-content {
|
||||
margin-top: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.events-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.event-card, .community-card {
|
||||
background: rgba(255, 215, 0, 0.05);
|
||||
border: 1px solid rgba(255, 215, 0, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: var(--spacing-lg);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.event-card:hover, .community-card:hover {
|
||||
transform: translateY(-5px);
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 10px 30px rgba(255, 215, 0, 0.2);
|
||||
}
|
||||
|
||||
.event-card h3, .community-card h3 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: var(--spacing-md);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.event-links, .community-links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.event-link, .community-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-sm);
|
||||
background: rgba(255, 215, 0, 0.1);
|
||||
border-radius: 12px;
|
||||
text-decoration: none;
|
||||
color: var(--color-light);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.event-link:hover, .community-link:hover {
|
||||
background: rgba(255, 215, 0, 0.2);
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.event-icon, .community-icon {
|
||||
font-size: 1.5rem;
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
.event-info, .community-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.event-title, .community-title {
|
||||
font-weight: 600;
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.event-platform, .community-desc {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.event-description {
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-light);
|
||||
margin-top: 4px;
|
||||
line-height: 1.4;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.video-section {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.video-section h3 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
aspect-ratio: 16/9;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.youtube-video {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.events-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,217 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let container;
|
||||
let visible = false;
|
||||
let globeInstance;
|
||||
|
||||
onMount(async () => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting && !globeInstance) {
|
||||
visible = true;
|
||||
initGlobe();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const section = document.getElementById('openglobus-section');
|
||||
if (section) observer.observe(section);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
if (globeInstance) {
|
||||
globeInstance.destroy();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
async function initGlobe() {
|
||||
// Charger OpenGlobus depuis CDN
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://unpkg.com/@openglobus/og@0.10.0/dist/og.umd.js';
|
||||
script.onload = () => {
|
||||
const { Globe, XYZ, Vector, Entity, Label } = window.og;
|
||||
|
||||
// Créer le globe
|
||||
globeInstance = new Globe({
|
||||
target: container,
|
||||
name: "Earth",
|
||||
terrain: null,
|
||||
layers: [
|
||||
new XYZ("OSM", {
|
||||
url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
visibility: true,
|
||||
attribution: 'OpenStreetMap'
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
// Données des lieux
|
||||
const locations = [
|
||||
{ name: 'La Réunion', lat: -21.1151, lon: 55.5364, date: '27 Juin', color: '#FF6B35' },
|
||||
{ name: 'Kinshasa', lat: -4.4419, lon: 15.2663, date: '16 Juillet', color: '#FFA500' },
|
||||
{ name: 'Guadeloupe', lat: 16.2650, lon: -61.5510, date: '22 Juillet', color: '#FFD700' },
|
||||
{ name: 'Martinique', lat: 14.6415, lon: -61.0242, date: '23 Juillet', color: '#FFD700' },
|
||||
{ name: 'Dakar', lat: 14.7167, lon: -17.4677, date: '23 Juillet', color: '#FFE44D' },
|
||||
{ name: 'Le Caire', lat: 30.0444, lon: 31.2357, date: '3 Août', color: '#FFFACD' }
|
||||
];
|
||||
|
||||
// Ajouter une couche de vecteurs pour les marqueurs
|
||||
const markerLayer = new Vector("Markers", {
|
||||
entities: locations.map(loc => new Entity({
|
||||
name: loc.name,
|
||||
lonlat: [loc.lon, loc.lat],
|
||||
label: {
|
||||
text: `${loc.name}\n${loc.date}`,
|
||||
outline: 0.77,
|
||||
outlineColor: "rgba(0,0,0,.75)",
|
||||
size: 14,
|
||||
color: "white",
|
||||
offset: [0, -35]
|
||||
},
|
||||
billboard: {
|
||||
src: createMarkerCanvas(loc.color),
|
||||
width: 32,
|
||||
height: 32,
|
||||
offset: [0, -16]
|
||||
}
|
||||
}))
|
||||
});
|
||||
|
||||
globeInstance.planet.addLayer(markerLayer);
|
||||
|
||||
// Positionner la vue sur l'Atlantique
|
||||
globeInstance.planet.viewExtentArr([-80, -30, 50, 50]);
|
||||
|
||||
// Rotation automatique
|
||||
let autoRotate = setInterval(() => {
|
||||
if (globeInstance && globeInstance.planet) {
|
||||
const cam = globeInstance.planet.camera;
|
||||
cam.rotateHorizontal(0.2);
|
||||
}
|
||||
}, 50);
|
||||
|
||||
// Nettoyage
|
||||
container.addEventListener('mousedown', () => {
|
||||
clearInterval(autoRotate);
|
||||
});
|
||||
};
|
||||
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
function createMarkerCanvas(color) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 32;
|
||||
canvas.height = 32;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Cercle avec couleur
|
||||
ctx.beginPath();
|
||||
ctx.arc(16, 16, 12, 0, Math.PI * 2);
|
||||
ctx.fillStyle = color;
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
// Point central
|
||||
ctx.beginPath();
|
||||
ctx.arc(16, 16, 4, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.fill();
|
||||
|
||||
return canvas.toDataURL();
|
||||
}
|
||||
</script>
|
||||
|
||||
<section id="openglobus-section" class="globe-section">
|
||||
<div class="container">
|
||||
<div class="section-header" class:visible>
|
||||
<h2>Globe avec OpenGlobus</h2>
|
||||
<p class="section-subtitle">
|
||||
Bibliothèque 3D professionnelle avec textures réalistes
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="globe-container openglobus" class:visible>
|
||||
<div bind:this={container} class="openglobus-container"></div>
|
||||
|
||||
<div class="globe-info">
|
||||
<div class="info-card card">
|
||||
<h3>OpenGlobus</h3>
|
||||
<p><strong>Avantages :</strong></p>
|
||||
<ul>
|
||||
<li>✅ Textures satellite réalistes</li>
|
||||
<li>✅ Performance WebGL optimisée</li>
|
||||
<li>✅ Zoom et navigation fluides</li>
|
||||
<li>✅ Support natif des marqueurs</li>
|
||||
</ul>
|
||||
<p><strong>Inconvénients :</strong></p>
|
||||
<ul>
|
||||
<li>❌ Plus lourd (~500KB)</li>
|
||||
<li>❌ Nécessite connexion internet pour les tuiles</li>
|
||||
<li>❌ Personnalisation plus complexe</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.globe-section {
|
||||
padding: var(--spacing-xl) 0;
|
||||
background: var(--gradient-dawn);
|
||||
}
|
||||
|
||||
.globe-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-lg);
|
||||
align-items: center;
|
||||
margin-top: var(--spacing-lg);
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: all 0.8s ease-out 0.3s;
|
||||
}
|
||||
|
||||
.globe-container.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.openglobus-container {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.info-card h3 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.info-card ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.info-card li {
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.globe-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.openglobus-container {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,271 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let container;
|
||||
let visible = false;
|
||||
let animationId;
|
||||
let scene, camera, renderer, globe, markers;
|
||||
|
||||
onMount(async () => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting && !scene) {
|
||||
visible = true;
|
||||
initGlobe();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const section = document.getElementById('threejs-section');
|
||||
if (section) observer.observe(section);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId);
|
||||
}
|
||||
if (renderer) {
|
||||
renderer.dispose();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
async function initGlobe() {
|
||||
// Charger Three.js depuis CDN
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js';
|
||||
script.onload = () => {
|
||||
createThreeJSGlobe();
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
function createThreeJSGlobe() {
|
||||
const THREE = window.THREE;
|
||||
|
||||
// Scène
|
||||
scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x0a1628);
|
||||
|
||||
// Caméra
|
||||
camera = new THREE.PerspectiveCamera(
|
||||
45,
|
||||
container.clientWidth / container.clientHeight,
|
||||
0.1,
|
||||
1000
|
||||
);
|
||||
camera.position.z = 2.5;
|
||||
|
||||
// Renderer
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setSize(container.clientWidth, container.clientHeight);
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
container.appendChild(renderer.domElement);
|
||||
|
||||
// Lumières
|
||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
||||
scene.add(ambientLight);
|
||||
|
||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
||||
directionalLight.position.set(5, 3, 5);
|
||||
scene.add(directionalLight);
|
||||
|
||||
// Globe
|
||||
const geometry = new THREE.SphereGeometry(1, 64, 64);
|
||||
|
||||
// Texture de la Terre
|
||||
const textureLoader = new THREE.TextureLoader();
|
||||
const earthTexture = textureLoader.load(
|
||||
'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/planets/earth_atmos_2048.jpg',
|
||||
() => { renderer.render(scene, camera); }
|
||||
);
|
||||
|
||||
const material = new THREE.MeshPhongMaterial({
|
||||
map: earthTexture,
|
||||
bumpScale: 0.05,
|
||||
specular: new THREE.Color('grey'),
|
||||
shininess: 10
|
||||
});
|
||||
|
||||
globe = new THREE.Mesh(geometry, material);
|
||||
scene.add(globe);
|
||||
|
||||
// Atmosphère
|
||||
const atmosphereGeometry = new THREE.SphereGeometry(1.1, 64, 64);
|
||||
const atmosphereMaterial = new THREE.MeshPhongMaterial({
|
||||
color: 0x4444ff,
|
||||
transparent: true,
|
||||
opacity: 0.1,
|
||||
side: THREE.BackSide
|
||||
});
|
||||
const atmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial);
|
||||
scene.add(atmosphere);
|
||||
|
||||
// Marqueurs
|
||||
markers = new THREE.Group();
|
||||
scene.add(markers);
|
||||
|
||||
const locations = [
|
||||
{ name: 'La Réunion', lat: -21.1151, lon: 55.5364, color: 0xFF6B35 },
|
||||
{ name: 'Kinshasa', lat: -4.4419, lon: 15.2663, color: 0xFFA500 },
|
||||
{ name: 'Guadeloupe', lat: 16.2650, lon: -61.5510, color: 0xFFD700 },
|
||||
{ name: 'Martinique', lat: 14.6415, lon: -61.0242, color: 0xFFD700 },
|
||||
{ name: 'Dakar', lat: 14.7167, lon: -17.4677, color: 0xFFE44D },
|
||||
{ name: 'Le Caire', lat: 30.0444, lon: 31.2357, color: 0xFFFACD }
|
||||
];
|
||||
|
||||
locations.forEach(loc => {
|
||||
const markerGeometry = new THREE.SphereGeometry(0.02, 16, 16);
|
||||
const markerMaterial = new THREE.MeshBasicMaterial({ color: loc.color });
|
||||
const marker = new THREE.Mesh(markerGeometry, markerMaterial);
|
||||
|
||||
// Convertir lat/lon en position 3D
|
||||
const phi = (90 - loc.lat) * (Math.PI / 180);
|
||||
const theta = (loc.lon + 180) * (Math.PI / 180);
|
||||
|
||||
marker.position.x = -1.05 * Math.sin(phi) * Math.cos(theta);
|
||||
marker.position.y = 1.05 * Math.cos(phi);
|
||||
marker.position.z = 1.05 * Math.sin(phi) * Math.sin(theta);
|
||||
|
||||
markers.add(marker);
|
||||
});
|
||||
|
||||
// Contrôles de la souris
|
||||
let mouseX = 0, mouseY = 0;
|
||||
let isMouseDown = false;
|
||||
|
||||
container.addEventListener('mousedown', () => { isMouseDown = true; });
|
||||
container.addEventListener('mouseup', () => { isMouseDown = false; });
|
||||
container.addEventListener('mouseleave', () => { isMouseDown = false; });
|
||||
|
||||
container.addEventListener('mousemove', (e) => {
|
||||
if (isMouseDown) {
|
||||
mouseX = (e.clientX - container.offsetLeft) / container.clientWidth * 2 - 1;
|
||||
mouseY = -(e.clientY - container.offsetTop) / container.clientHeight * 2 + 1;
|
||||
}
|
||||
});
|
||||
|
||||
// Animation
|
||||
function animate() {
|
||||
animationId = requestAnimationFrame(animate);
|
||||
|
||||
// Rotation automatique
|
||||
if (!isMouseDown) {
|
||||
globe.rotation.y += 0.002;
|
||||
markers.rotation.y += 0.002;
|
||||
} else {
|
||||
// Rotation manuelle
|
||||
globe.rotation.y += mouseX * 0.05;
|
||||
globe.rotation.x = mouseY * 0.5;
|
||||
markers.rotation.y = globe.rotation.y;
|
||||
markers.rotation.x = globe.rotation.x;
|
||||
}
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
animate();
|
||||
|
||||
// Responsive
|
||||
window.addEventListener('resize', () => {
|
||||
camera.aspect = container.clientWidth / container.clientHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(container.clientWidth, container.clientHeight);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<section id="threejs-section" class="globe-section">
|
||||
<div class="container">
|
||||
<div class="section-header" class:visible>
|
||||
<h2>Globe avec Three.js</h2>
|
||||
<p class="section-subtitle">
|
||||
Rendu 3D WebGL avec textures réalistes
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="globe-container threejs" class:visible>
|
||||
<div bind:this={container} class="threejs-container"></div>
|
||||
|
||||
<div class="globe-info">
|
||||
<div class="info-card card">
|
||||
<h3>Three.js</h3>
|
||||
<p><strong>Avantages :</strong></p>
|
||||
<ul>
|
||||
<li>✅ Rendu 3D réaliste</li>
|
||||
<li>✅ Textures haute qualité</li>
|
||||
<li>✅ Effets atmosphériques</li>
|
||||
<li>✅ Très bien documenté</li>
|
||||
</ul>
|
||||
<p><strong>Inconvénients :</strong></p>
|
||||
<ul>
|
||||
<li>❌ Plus lourd (~150KB)</li>
|
||||
<li>❌ Nécessite WebGL</li>
|
||||
<li>❌ Plus complexe à personnaliser</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.globe-section {
|
||||
padding: var(--spacing-xl) 0;
|
||||
background: var(--gradient-dawn);
|
||||
}
|
||||
|
||||
.globe-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-lg);
|
||||
align-items: center;
|
||||
margin-top: var(--spacing-lg);
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: all 0.8s ease-out 0.3s;
|
||||
}
|
||||
|
||||
.globe-container.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.threejs-container {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.threejs-container:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.info-card h3 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.info-card ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.info-card li {
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.globe-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.threejs-container {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,544 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from '../lib/i18n.js';
|
||||
import anime from '../lib/anime.js';
|
||||
|
||||
let starsCanvas;
|
||||
let mouseX = 0.5;
|
||||
let mouseY = 0.5;
|
||||
let heroSection;
|
||||
|
||||
onMount(() => {
|
||||
const ctx = starsCanvas.getContext('2d');
|
||||
const stars = [];
|
||||
const starCount = 150;
|
||||
const shootingStars = [];
|
||||
|
||||
// Animation d'entrée avec AnimeJS
|
||||
anime.timeline({
|
||||
easing: 'easeOutQuad'
|
||||
})
|
||||
.add({
|
||||
targets: '.title-line',
|
||||
translateY: [50, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 1200,
|
||||
delay: anime.stagger(200)
|
||||
})
|
||||
.add({
|
||||
targets: '.hero-subtitle',
|
||||
translateY: [30, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 800
|
||||
}, '-=600')
|
||||
.add({
|
||||
targets: '.hero-description',
|
||||
translateY: [30, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 800
|
||||
}, '-=400')
|
||||
.add({
|
||||
targets: '.hero-date',
|
||||
translateY: [30, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 800
|
||||
}, '-=400')
|
||||
.add({
|
||||
targets: '.hero-cta .btn',
|
||||
scale: [0.8, 1],
|
||||
opacity: [0, 1],
|
||||
duration: 600,
|
||||
delay: anime.stagger(100)
|
||||
}, '-=400');
|
||||
|
||||
// Animation des patterns africains
|
||||
// Animation du système solaire gérée par CSS
|
||||
|
||||
// Attendre que le DOM soit prêt
|
||||
setTimeout(() => {
|
||||
// Redimensionner le canvas
|
||||
const resize = () => {
|
||||
const hero = starsCanvas.parentElement;
|
||||
starsCanvas.width = hero.offsetWidth;
|
||||
starsCanvas.height = hero.offsetHeight;
|
||||
|
||||
// Réinitialiser les étoiles après redimensionnement
|
||||
if (stars.length === 0) {
|
||||
for (let i = 0; i < starCount; i++) {
|
||||
stars.push({
|
||||
x: Math.random() * starsCanvas.width,
|
||||
y: Math.random() * starsCanvas.height,
|
||||
size: Math.random() * 2,
|
||||
brightness: Math.random(),
|
||||
speed: 0.1 + Math.random() * 0.5,
|
||||
pulseSpeed: 2000 + Math.random() * 3000
|
||||
});
|
||||
}
|
||||
|
||||
// Animer les étoiles avec AnimeJS
|
||||
stars.forEach((star, i) => {
|
||||
anime({
|
||||
targets: star,
|
||||
brightness: [0.1, 1, 0.1],
|
||||
duration: star.pulseSpeed,
|
||||
easing: 'easeInOutSine',
|
||||
loop: true,
|
||||
delay: i * 20
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
resize();
|
||||
window.addEventListener('resize', resize);
|
||||
|
||||
// Animation des étoiles
|
||||
const animate = () => {
|
||||
ctx.clearRect(0, 0, starsCanvas.width, starsCanvas.height);
|
||||
|
||||
// Effet de parallaxe avec la souris
|
||||
const offsetX = (mouseX - 0.5) * 20;
|
||||
const offsetY = (mouseY - 0.5) * 20;
|
||||
|
||||
// Dessiner les étoiles
|
||||
stars.forEach(star => {
|
||||
// Mouvement subtil
|
||||
star.x += Math.sin(Date.now() * 0.0001 * star.speed) * 0.5;
|
||||
star.y += Math.cos(Date.now() * 0.0001 * star.speed) * 0.3;
|
||||
|
||||
// Garder dans les limites
|
||||
if (star.x < 0) star.x = starsCanvas.width;
|
||||
if (star.x > starsCanvas.width) star.x = 0;
|
||||
if (star.y < 0) star.y = starsCanvas.height;
|
||||
if (star.y > starsCanvas.height) star.y = 0;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
star.x + offsetX,
|
||||
star.y + offsetY,
|
||||
star.size,
|
||||
0,
|
||||
Math.PI * 2
|
||||
);
|
||||
ctx.fillStyle = `rgba(255, 215, 0, ${star.brightness})`;
|
||||
ctx.fill();
|
||||
});
|
||||
|
||||
// Dessiner les étoiles filantes
|
||||
shootingStars.forEach((star, index) => {
|
||||
if (star.opacity <= 0) {
|
||||
shootingStars.splice(index, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(star.x, star.y);
|
||||
ctx.lineTo(star.x - star.vx * 20, star.y - star.vy * 20);
|
||||
ctx.strokeStyle = `rgba(255, 215, 0, ${star.opacity})`;
|
||||
ctx.lineWidth = star.size;
|
||||
ctx.stroke();
|
||||
|
||||
star.x += star.vx;
|
||||
star.y += star.vy;
|
||||
star.opacity -= 0.02;
|
||||
});
|
||||
|
||||
// Créer occasionnellement une étoile filante
|
||||
if (Math.random() < 0.01 && shootingStars.length < 3) {
|
||||
shootingStars.push({
|
||||
x: Math.random() * starsCanvas.width,
|
||||
y: Math.random() * starsCanvas.height * 0.5,
|
||||
vx: 5 + Math.random() * 10,
|
||||
vy: 2 + Math.random() * 5,
|
||||
size: 1 + Math.random() * 2,
|
||||
opacity: 1
|
||||
});
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
animate();
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', resize);
|
||||
};
|
||||
}, 100);
|
||||
});
|
||||
|
||||
const handleMouseMove = (e) => {
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
mouseX = (e.clientX - rect.left) / rect.width;
|
||||
mouseY = (e.clientY - rect.top) / rect.height;
|
||||
};
|
||||
|
||||
// Animation du bouton au survol
|
||||
function handleButtonHover(e) {
|
||||
anime({
|
||||
targets: e.currentTarget,
|
||||
scale: 1.05,
|
||||
duration: 300,
|
||||
easing: 'easeOutElastic(1, .5)'
|
||||
});
|
||||
}
|
||||
|
||||
function handleButtonLeave(e) {
|
||||
anime({
|
||||
targets: e.currentTarget,
|
||||
scale: 1,
|
||||
duration: 300,
|
||||
easing: 'easeOutElastic(1, .5)'
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<section id="home" class="hero" on:mousemove={handleMouseMove} bind:this={heroSection}>
|
||||
<canvas bind:this={starsCanvas} class="stars-canvas"></canvas>
|
||||
|
||||
<div class="hero-pattern">
|
||||
<!-- Système solaire à gauche -->
|
||||
<div class="solar-system">
|
||||
<div class="sun"></div>
|
||||
<div class="orbit orbit-1">
|
||||
<div class="planet planet-1"></div>
|
||||
</div>
|
||||
<div class="orbit orbit-2">
|
||||
<div class="planet planet-2"></div>
|
||||
</div>
|
||||
<div class="orbit orbit-3">
|
||||
<div class="planet planet-3"></div>
|
||||
</div>
|
||||
<div class="orbit orbit-4">
|
||||
<div class="planet planet-4"></div>
|
||||
</div>
|
||||
<div class="orbit orbit-5">
|
||||
<div class="planet planet-5"></div>
|
||||
</div>
|
||||
<div class="orbit orbit-sirius">
|
||||
<div class="planet sirius"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hero-content">
|
||||
<div class="container">
|
||||
<div class="hero-text">
|
||||
<h1 class="hero-title">
|
||||
<span class="title-line">{$t('hero_title1')}</span>
|
||||
<span class="title-line">{$t('hero_title2')}</span>
|
||||
</h1>
|
||||
<p class="hero-subtitle">
|
||||
{$t('hero_subtitle')}
|
||||
</p>
|
||||
<p class="hero-description">
|
||||
{$t('hero_description')}
|
||||
</p>
|
||||
<p class="hero-date">
|
||||
{$t('hero_date')}<strong>{$t('hero_date_value')}</strong>
|
||||
</p>
|
||||
<div class="hero-cta">
|
||||
<a
|
||||
href="#predictions"
|
||||
class="btn btn-primary"
|
||||
on:mouseenter={handleButtonHover}
|
||||
on:mouseleave={handleButtonLeave}
|
||||
>
|
||||
{$t('hero_cta_predictions')}
|
||||
</a>
|
||||
<a
|
||||
href="#about"
|
||||
class="btn btn-outline"
|
||||
on:mouseenter={handleButtonHover}
|
||||
on:mouseleave={handleButtonLeave}
|
||||
>
|
||||
{$t('hero_cta_learn')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
background: var(--gradient-dawn);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stars-canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hero-pattern {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.pattern-overlay {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.1;
|
||||
background-size: 200px 200px;
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
||||
|
||||
.solar-system {
|
||||
position: absolute;
|
||||
left: 10%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
opacity: 0.8 !important;
|
||||
mix-blend-mode: normal !important;
|
||||
}
|
||||
|
||||
.sun {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: radial-gradient(circle, #FFD700, #FFA500);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 20px #FFD700, 0 0 40px #FFA500;
|
||||
z-index: 10;
|
||||
animation: pulse 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.orbit {
|
||||
position: absolute;
|
||||
border: 1px solid rgba(255, 215, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
animation: rotate linear infinite;
|
||||
}
|
||||
|
||||
.orbit-1 {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
animation-duration: 8s;
|
||||
}
|
||||
|
||||
.orbit-2 {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
animation-duration: 12s;
|
||||
}
|
||||
|
||||
.orbit-3 {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
animation-duration: 16s;
|
||||
}
|
||||
|
||||
.orbit-4 {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
animation-duration: 20s;
|
||||
}
|
||||
|
||||
.orbit-5 {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
animation-duration: 24s;
|
||||
}
|
||||
|
||||
.orbit-sirius {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
animation-duration: 30s;
|
||||
border-color: rgba(135, 206, 250, 0.3);
|
||||
}
|
||||
|
||||
.planet {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
top: -4px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.planet-1 {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: #8C7853;
|
||||
}
|
||||
|
||||
.planet-2 {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #FFC649;
|
||||
}
|
||||
|
||||
.planet-3 {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #6B93D6;
|
||||
}
|
||||
|
||||
.planet-4 {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: #C1440E;
|
||||
}
|
||||
|
||||
.planet-5 {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #D8CA9D;
|
||||
}
|
||||
|
||||
.sirius {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: radial-gradient(circle, #87CEEB, #4169E1);
|
||||
box-shadow: 0 0 10px #87CEEB, 0 0 20px #4169E1;
|
||||
animation: twinkle 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes twinkle {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
transform: translateX(-50%) scale(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
padding: var(--spacing-xl) 0;
|
||||
}
|
||||
|
||||
.hero-text {
|
||||
max-width: 600px;
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: clamp(3rem, 8vw, 5rem);
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--color-light);
|
||||
}
|
||||
|
||||
.title-line {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.title-line:nth-child(2) {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: clamp(1.2rem, 3vw, 1.5rem);
|
||||
color: var(--color-primary);
|
||||
font-family: var(--font-heading);
|
||||
margin-bottom: 0.5rem;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.hero-description {
|
||||
font-size: clamp(1rem, 2.5vw, 1.2rem);
|
||||
color: var(--color-light);
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.hero-date {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 2rem;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.hero-date strong {
|
||||
color: var(--color-primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
opacity: 0;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
border: 2px solid var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-dark);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hero-title {
|
||||
font-size: clamp(2.5rem, 10vw, 4rem);
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,150 +0,0 @@
|
||||
<script>
|
||||
import { currentLanguage, availableLanguages, setLanguage } from '../lib/i18n.js';
|
||||
|
||||
let isOpen = false;
|
||||
|
||||
function toggleDropdown() {
|
||||
isOpen = !isOpen;
|
||||
}
|
||||
|
||||
function selectLanguage(lang) {
|
||||
setLanguage(lang);
|
||||
isOpen = false;
|
||||
}
|
||||
|
||||
// Fermer le dropdown si on clique ailleurs
|
||||
function handleClickOutside(event) {
|
||||
if (!event.target.closest('.language-selector')) {
|
||||
isOpen = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:click={handleClickOutside} />
|
||||
|
||||
<div class="language-selector">
|
||||
<button class="language-button" on:click={toggleDropdown}>
|
||||
<span class="flag">
|
||||
{availableLanguages.find(l => l.code === $currentLanguage)?.flag}
|
||||
</span>
|
||||
<span class="language-name">
|
||||
{availableLanguages.find(l => l.code === $currentLanguage)?.name}
|
||||
</span>
|
||||
<span class="arrow" class:open={isOpen}>▼</span>
|
||||
</button>
|
||||
|
||||
{#if isOpen}
|
||||
<div class="language-dropdown">
|
||||
{#each availableLanguages as lang}
|
||||
<button
|
||||
class="language-option"
|
||||
class:active={$currentLanguage === lang.code}
|
||||
on:click={() => selectLanguage(lang.code)}
|
||||
>
|
||||
<span class="flag">{lang.flag}</span>
|
||||
<span>{lang.name}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.language-selector {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.language-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
color: var(--color-light);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.language-button:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.flag {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.language-name {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 0.7rem;
|
||||
margin-left: 0.5rem;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.arrow.open {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.language-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
margin-top: 0.5rem;
|
||||
background: var(--color-dark);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
animation: slideDown 0.3s ease-out;
|
||||
}
|
||||
|
||||
.language-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-light);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.language-option:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.language-option.active {
|
||||
background: rgba(255, 215, 0, 0.2);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.language-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.language-button {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,141 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import anime from '../lib/anime.js';
|
||||
|
||||
let loading = true;
|
||||
let progress = 0;
|
||||
|
||||
onMount(() => {
|
||||
// Animation de la barre de progression
|
||||
anime({
|
||||
targets: { value: 0 },
|
||||
value: 100,
|
||||
duration: 2000,
|
||||
easing: 'easeInOutQuad',
|
||||
update: function(anim) {
|
||||
progress = Math.round(anim.animations[0].currentValue);
|
||||
},
|
||||
complete: function() {
|
||||
setTimeout(() => {
|
||||
loading = false;
|
||||
// Animation de sortie
|
||||
anime({
|
||||
targets: '.loader',
|
||||
opacity: [1, 0],
|
||||
duration: 600,
|
||||
easing: 'easeOutQuad'
|
||||
});
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
// Animation des étoiles du loader
|
||||
anime({
|
||||
targets: '.loader-star',
|
||||
rotate: 360,
|
||||
duration: 2000,
|
||||
easing: 'linear',
|
||||
loop: true
|
||||
});
|
||||
|
||||
// Animation du texte
|
||||
anime({
|
||||
targets: '.loader-text span',
|
||||
translateY: [-20, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 800,
|
||||
delay: anime.stagger(100),
|
||||
easing: 'easeOutElastic(1, .5)'
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<div class="loader">
|
||||
<div class="loader-content">
|
||||
<div class="loader-star">⭐</div>
|
||||
<div class="loader-text">
|
||||
<span>S</span>
|
||||
<span>i</span>
|
||||
<span>r</span>
|
||||
<span>i</span>
|
||||
<span>u</span>
|
||||
<span>s</span>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: {progress}%"></div>
|
||||
</div>
|
||||
<div class="progress-text">{progress}%</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.loader {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--color-dark);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loader-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loader-star {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 1rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.loader-text {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 2rem;
|
||||
letter-spacing: 0.5rem;
|
||||
}
|
||||
|
||||
.loader-text span {
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 200px;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--color-primary);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
color: var(--color-light);
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.loader-text {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.loader-star {
|
||||
font-size: 3rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,270 +0,0 @@
|
||||
<script>
|
||||
import { t } from '../lib/i18n.js';
|
||||
import LanguageSelector from './LanguageSelector.svelte';
|
||||
import anime from '../lib/anime.js';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let activeSection = 'home';
|
||||
|
||||
let isMenuOpen = false;
|
||||
let navbar;
|
||||
|
||||
const toggleMenu = () => {
|
||||
isMenuOpen = !isMenuOpen;
|
||||
|
||||
if (isMenuOpen) {
|
||||
anime({
|
||||
targets: '.nav-menu',
|
||||
translateX: [300, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 400,
|
||||
easing: 'easeOutQuad'
|
||||
});
|
||||
|
||||
anime({
|
||||
targets: '.nav-menu li',
|
||||
translateX: [50, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 600,
|
||||
delay: anime.stagger(50),
|
||||
easing: 'easeOutQuad'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
// Animation d'entrée de la navbar
|
||||
anime({
|
||||
targets: navbar,
|
||||
translateY: [-100, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 800,
|
||||
easing: 'easeOutQuad'
|
||||
});
|
||||
|
||||
// Animation du logo
|
||||
anime({
|
||||
targets: '.logo-icon',
|
||||
rotate: [0, 360],
|
||||
duration: 2000,
|
||||
delay: 500,
|
||||
easing: 'easeInOutQuad'
|
||||
});
|
||||
});
|
||||
|
||||
const menuItems = [
|
||||
{ id: 'home', label: 'nav_home', icon: '🌟' },
|
||||
{ id: 'about', label: 'nav_about', icon: '✨' },
|
||||
{ id: 'predictions', label: 'nav_predictions', icon: '📅' },
|
||||
{ id: 'science', label: 'nav_science', icon: '🔭' },
|
||||
{ id: 'culture', label: 'nav_culture', icon: '🌍' },
|
||||
{ id: 'observatory', label: 'nav_observatory', icon: '👁️' },
|
||||
{ id: 'associations', label: 'nav_associations', icon: '🤝' }
|
||||
];
|
||||
|
||||
const scrollToSection = (id) => {
|
||||
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
|
||||
isMenuOpen = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<nav class="navbar" class:scrolled={activeSection !== 'home'} bind:this={navbar}>
|
||||
<div class="container">
|
||||
<div class="nav-content">
|
||||
<a href="#home" class="logo" on:click|preventDefault={() => scrollToSection('home')}>
|
||||
<span class="logo-icon">☥</span>
|
||||
<span class="logo-text">Sirius Gwada</span>
|
||||
</a>
|
||||
|
||||
<button class="menu-toggle" on:click={toggleMenu} aria-label="Menu">
|
||||
<span class="hamburger" class:active={isMenuOpen}></span>
|
||||
</button>
|
||||
|
||||
<ul class="nav-menu" class:open={isMenuOpen}>
|
||||
{#each menuItems as item}
|
||||
<li>
|
||||
<a
|
||||
href="#{item.id}"
|
||||
class:active={activeSection === item.id}
|
||||
on:click|preventDefault={() => scrollToSection(item.id)}
|
||||
>
|
||||
<span class="nav-icon">{item.icon}</span>
|
||||
<span class="nav-label">{$t(item.label)}</span>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
.navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
background: rgba(26, 26, 46, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
transition: var(--transition-base);
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
.navbar.scrolled {
|
||||
border-bottom-color: rgba(255, 215, 0, 0.2);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.nav-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
text-decoration: none;
|
||||
color: var(--color-primary);
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.5rem;
|
||||
transition: var(--transition-base);
|
||||
}
|
||||
|
||||
.logo:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
font-size: 2rem;
|
||||
animation: rotate 20s linear infinite;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
display: none;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.hamburger {
|
||||
display: block;
|
||||
width: 25px;
|
||||
height: 2px;
|
||||
background: var(--color-primary);
|
||||
position: relative;
|
||||
transition: var(--transition-base);
|
||||
}
|
||||
|
||||
.hamburger::before,
|
||||
.hamburger::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: var(--color-primary);
|
||||
transition: var(--transition-base);
|
||||
}
|
||||
|
||||
.hamburger::before {
|
||||
top: -8px;
|
||||
}
|
||||
|
||||
.hamburger::after {
|
||||
bottom: -8px;
|
||||
}
|
||||
|
||||
.hamburger.active {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.hamburger.active::before {
|
||||
transform: rotate(45deg);
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.hamburger.active::after {
|
||||
transform: rotate(-45deg);
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
gap: 2rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nav-menu a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
text-decoration: none;
|
||||
color: var(--color-light);
|
||||
transition: var(--transition-base);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-menu a:hover,
|
||||
.nav-menu a.active {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.nav-menu a::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: var(--color-primary);
|
||||
transform: scaleX(0);
|
||||
transition: var(--transition-base);
|
||||
}
|
||||
|
||||
.nav-menu a.active::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 768px) {
|
||||
.menu-toggle {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
position: fixed;
|
||||
top: 70px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(26, 26, 46, 0.98);
|
||||
flex-direction: column;
|
||||
padding: 2rem;
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
transition: var(--transition-base);
|
||||
border-bottom: 2px solid var(--color-primary);
|
||||
}
|
||||
|
||||
.nav-menu.open {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.nav-menu a {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,661 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let visible = false;
|
||||
let selectedSite = null;
|
||||
let mapCanvas;
|
||||
let ctx;
|
||||
let guadeloupeGeoJson = null;
|
||||
|
||||
const sites = [
|
||||
{
|
||||
id: 'moule',
|
||||
name: 'Le Moule',
|
||||
lat: 16.3333,
|
||||
lng: -61.3472,
|
||||
description: 'Plage de l\'Autre Bord - Vue dégagée sur l\'Est',
|
||||
features: ['Plage isolée', 'Horizon Est dégagé', 'Peu de pollution lumineuse'],
|
||||
bestTime: '5h00 - 5h30',
|
||||
image: '🏖️'
|
||||
},
|
||||
{
|
||||
id: 'deshaies',
|
||||
name: 'Deshaies',
|
||||
lat: 16.3039,
|
||||
lng: -61.7966,
|
||||
description: 'Plage de Grande Anse - Cadre idyllique',
|
||||
features: ['Plage populaire', 'Montagnes en arrière-plan', 'Accès facile'],
|
||||
bestTime: '5h15 - 5h45',
|
||||
image: '🌅'
|
||||
},
|
||||
{
|
||||
id: 'sainte-anne',
|
||||
name: 'Sainte-Anne',
|
||||
lat: 16.2262,
|
||||
lng: -61.3809,
|
||||
description: 'Plage de Bois Jolan - Tranquillité garantie',
|
||||
features: ['Plage sauvage', 'Très peu fréquentée', 'Nature préservée'],
|
||||
bestTime: '5h00 - 5h30',
|
||||
image: '🌴'
|
||||
},
|
||||
{
|
||||
id: 'port-louis',
|
||||
name: 'Port-Louis',
|
||||
lat: 16.4180,
|
||||
lng: -61.5312,
|
||||
description: 'Plage du Souffleur - Vue panoramique',
|
||||
features: ['Vue à 180°', 'Site historique', 'Calme au petit matin'],
|
||||
bestTime: '5h00 - 5h30',
|
||||
image: '🏛️'
|
||||
},
|
||||
{
|
||||
id: 'marie-galante',
|
||||
name: 'Marie-Galante',
|
||||
lat: 15.9500,
|
||||
lng: -61.2667,
|
||||
description: 'Plage de la Feuillère - Horizon infini',
|
||||
features: ['Île préservée', 'Ciel très noir', 'Conditions optimales'],
|
||||
bestTime: '4h45 - 5h15',
|
||||
image: '🏝️'
|
||||
},
|
||||
{
|
||||
id: 'soufriere',
|
||||
name: 'Route de la Soufrière',
|
||||
lat: 16.0439,
|
||||
lng: -61.6639,
|
||||
description: 'Parking de Savane à Mulets - Altitude',
|
||||
features: ['1000m altitude', 'Air pur', 'Vue dégagée', 'Nuits fraîches'],
|
||||
bestTime: '5h00 - 5h30',
|
||||
image: '🌋'
|
||||
},
|
||||
{
|
||||
id: 'petite-terre',
|
||||
name: 'Petite-Terre',
|
||||
lat: 16.1700,
|
||||
lng: -61.1200,
|
||||
description: 'Réserve naturelle - Conditions parfaites',
|
||||
features: ['Aucune pollution lumineuse', 'Réserve protégée', 'Accès réglementé'],
|
||||
bestTime: '4h45 - 5h15',
|
||||
image: '🦎'
|
||||
}
|
||||
];
|
||||
|
||||
onMount(async () => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting && !ctx) {
|
||||
visible = true;
|
||||
initMap();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const section = document.getElementById('observation-map');
|
||||
if (section) observer.observe(section);
|
||||
|
||||
return () => observer.disconnect();
|
||||
});
|
||||
|
||||
async function initMap() {
|
||||
ctx = mapCanvas.getContext('2d');
|
||||
|
||||
// Charger les données GeoJSON de la Guadeloupe
|
||||
try {
|
||||
const response = await fetch('https://raw.githubusercontent.com/gregoiredavid/france-geojson/master/regions/guadeloupe/departements-guadeloupe.geojson');
|
||||
guadeloupeGeoJson = await response.json();
|
||||
|
||||
// Redimensionner le canvas
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
// Dessiner la carte
|
||||
drawMap();
|
||||
} catch (error) {
|
||||
console.error('Erreur de chargement GeoJSON:', error);
|
||||
// Fallback : dessiner une carte simple
|
||||
drawSimpleMap();
|
||||
}
|
||||
}
|
||||
|
||||
function resizeCanvas() {
|
||||
const container = mapCanvas.parentElement;
|
||||
mapCanvas.width = container.offsetWidth;
|
||||
mapCanvas.height = container.offsetWidth * 0.75; // Ratio 4:3
|
||||
if (guadeloupeGeoJson) drawMap();
|
||||
}
|
||||
|
||||
function drawMap() {
|
||||
if (!ctx || !guadeloupeGeoJson) return;
|
||||
|
||||
// Effacer le canvas
|
||||
ctx.clearRect(0, 0, mapCanvas.width, mapCanvas.height);
|
||||
|
||||
// Fond océan
|
||||
ctx.fillStyle = '#1a3a52';
|
||||
ctx.fillRect(0, 0, mapCanvas.width, mapCanvas.height);
|
||||
|
||||
// Calculer les limites et l'échelle
|
||||
const bounds = calculateBounds(guadeloupeGeoJson);
|
||||
const scale = calculateScale(bounds, mapCanvas.width, mapCanvas.height);
|
||||
|
||||
// Dessiner chaque feature
|
||||
guadeloupeGeoJson.features.forEach(feature => {
|
||||
drawFeature(feature, bounds, scale);
|
||||
});
|
||||
|
||||
// Dessiner les sites
|
||||
drawSites(bounds, scale);
|
||||
}
|
||||
|
||||
function drawSimpleMap() {
|
||||
if (!ctx) return;
|
||||
|
||||
// Effacer le canvas
|
||||
ctx.clearRect(0, 0, mapCanvas.width, mapCanvas.height);
|
||||
|
||||
// Fond océan
|
||||
ctx.fillStyle = '#1a3a52';
|
||||
ctx.fillRect(0, 0, mapCanvas.width, mapCanvas.height);
|
||||
|
||||
// Dessiner une forme approximative de la Guadeloupe
|
||||
const centerX = mapCanvas.width / 2;
|
||||
const centerY = mapCanvas.height / 2;
|
||||
const scale = mapCanvas.width / 4;
|
||||
|
||||
// Grande-Terre
|
||||
ctx.fillStyle = '#2d5a3d';
|
||||
ctx.strokeStyle = '#4CAF50';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX + scale * 0.5, centerY - scale * 0.2, scale * 0.4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
// Basse-Terre
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX - scale * 0.3, centerY, scale * 0.5, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
// Marie-Galante
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX + scale * 0.2, centerY + scale * 0.8, scale * 0.25, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
// Dessiner les sites avec des positions approximatives
|
||||
const bounds = { minLng: -62, maxLng: -61, minLat: 15.8, maxLat: 16.5 };
|
||||
const simpleScale = {
|
||||
x: mapCanvas.width / (bounds.maxLng - bounds.minLng),
|
||||
y: mapCanvas.height / (bounds.maxLat - bounds.minLat)
|
||||
};
|
||||
|
||||
drawSites(bounds, simpleScale);
|
||||
}
|
||||
|
||||
function calculateBounds(geoJson) {
|
||||
let minLng = Infinity, maxLng = -Infinity;
|
||||
let minLat = Infinity, maxLat = -Infinity;
|
||||
|
||||
function processBounds(coords) {
|
||||
if (Array.isArray(coords[0])) {
|
||||
coords.forEach(processBounds);
|
||||
} else {
|
||||
minLng = Math.min(minLng, coords[0]);
|
||||
maxLng = Math.max(maxLng, coords[0]);
|
||||
minLat = Math.min(minLat, coords[1]);
|
||||
maxLat = Math.max(maxLat, coords[1]);
|
||||
}
|
||||
}
|
||||
|
||||
geoJson.features.forEach(feature => {
|
||||
if (feature.geometry && feature.geometry.coordinates) {
|
||||
processBounds(feature.geometry.coordinates);
|
||||
}
|
||||
});
|
||||
|
||||
// Ajouter une marge
|
||||
const margin = 0.1;
|
||||
return {
|
||||
minLng: minLng - margin,
|
||||
maxLng: maxLng + margin,
|
||||
minLat: minLat - margin,
|
||||
maxLat: maxLat + margin
|
||||
};
|
||||
}
|
||||
|
||||
function calculateScale(bounds, width, height) {
|
||||
return {
|
||||
x: width / (bounds.maxLng - bounds.minLng),
|
||||
y: height / (bounds.maxLat - bounds.minLat)
|
||||
};
|
||||
}
|
||||
|
||||
function projectPoint(lng, lat, bounds, scale) {
|
||||
return {
|
||||
x: (lng - bounds.minLng) * scale.x,
|
||||
y: mapCanvas.height - (lat - bounds.minLat) * scale.y
|
||||
};
|
||||
}
|
||||
|
||||
function drawFeature(feature, bounds, scale) {
|
||||
ctx.fillStyle = '#2d5a3d';
|
||||
ctx.strokeStyle = '#4CAF50';
|
||||
ctx.lineWidth = 2;
|
||||
|
||||
function drawCoordinates(coords) {
|
||||
if (!coords || coords.length === 0) return;
|
||||
|
||||
if (Array.isArray(coords[0][0])) {
|
||||
coords.forEach(drawCoordinates);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
coords.forEach((coord, i) => {
|
||||
const point = projectPoint(coord[0], coord[1], bounds, scale);
|
||||
if (i === 0) {
|
||||
ctx.moveTo(point.x, point.y);
|
||||
} else {
|
||||
ctx.lineTo(point.x, point.y);
|
||||
}
|
||||
});
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
if (feature.geometry && feature.geometry.coordinates) {
|
||||
drawCoordinates(feature.geometry.coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
function drawSites(bounds, scale) {
|
||||
sites.forEach(site => {
|
||||
const point = projectPoint(site.lng, site.lat, bounds, scale);
|
||||
|
||||
// Cercle du marqueur
|
||||
ctx.beginPath();
|
||||
ctx.arc(point.x, point.y, selectedSite?.id === site.id ? 15 : 10, 0, Math.PI * 2);
|
||||
ctx.fillStyle = selectedSite?.id === site.id ? '#FFD700' : '#FF6B35';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = 'white';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
// Icône du site
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.font = selectedSite?.id === site.id ? '18px sans-serif' : '14px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(site.image, point.x, point.y);
|
||||
|
||||
// Nom du site si sélectionné
|
||||
if (selectedSite?.id === site.id) {
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.font = 'bold 14px sans-serif';
|
||||
ctx.fillText(site.name, point.x, point.y + 25);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleMapClick(event) {
|
||||
const rect = mapCanvas.getBoundingClientRect();
|
||||
const x = event.clientX - rect.left;
|
||||
const y = event.clientY - rect.top;
|
||||
|
||||
// Vérifier si on clique sur un site
|
||||
const bounds = guadeloupeGeoJson ?
|
||||
calculateBounds(guadeloupeGeoJson) :
|
||||
{ minLng: -62, maxLng: -61, minLat: 15.8, maxLat: 16.5 };
|
||||
|
||||
const scale = guadeloupeGeoJson ?
|
||||
calculateScale(bounds, mapCanvas.width, mapCanvas.height) :
|
||||
{ x: mapCanvas.width / (bounds.maxLng - bounds.minLng),
|
||||
y: mapCanvas.height / (bounds.maxLat - bounds.minLat) };
|
||||
|
||||
let clickedSite = null;
|
||||
sites.forEach(site => {
|
||||
const point = projectPoint(site.lng, site.lat, bounds, scale);
|
||||
const distance = Math.sqrt(Math.pow(x - point.x, 2) + Math.pow(y - point.y, 2));
|
||||
if (distance < 20) {
|
||||
clickedSite = site;
|
||||
}
|
||||
});
|
||||
|
||||
if (clickedSite) {
|
||||
selectedSite = selectedSite?.id === clickedSite.id ? null : clickedSite;
|
||||
drawMap();
|
||||
}
|
||||
}
|
||||
|
||||
function selectSite(site) {
|
||||
selectedSite = selectedSite?.id === site.id ? null : site;
|
||||
if (guadeloupeGeoJson) {
|
||||
drawMap();
|
||||
} else {
|
||||
drawSimpleMap();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section id="observation-map" class="map-section">
|
||||
<div class="container">
|
||||
<div class="section-header" class:visible>
|
||||
<h2>Carte des Sites d'Observation</h2>
|
||||
<p class="section-subtitle">
|
||||
Les meilleurs endroits en Guadeloupe pour observer le lever héliaque
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="map-wrapper" class:visible>
|
||||
<!-- Carte Canvas de la Guadeloupe -->
|
||||
<div class="map-container">
|
||||
<canvas
|
||||
bind:this={mapCanvas}
|
||||
class="guadeloupe-map"
|
||||
on:click={handleMapClick}
|
||||
></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Liste des sites -->
|
||||
<div class="sites-list">
|
||||
<h3>Sites d'Observation</h3>
|
||||
{#each sites as site}
|
||||
<div
|
||||
class="site-card"
|
||||
class:active={selectedSite?.id === site.id}
|
||||
on:click={() => selectSite(site)}
|
||||
>
|
||||
<div class="site-header">
|
||||
<span class="site-icon">{site.image}</span>
|
||||
<h4>{site.name}</h4>
|
||||
</div>
|
||||
<p class="site-description">{site.description}</p>
|
||||
|
||||
{#if selectedSite?.id === site.id}
|
||||
<div class="site-details">
|
||||
<div class="features">
|
||||
<h5>Caractéristiques :</h5>
|
||||
<ul>
|
||||
{#each site.features as feature}
|
||||
<li>{feature}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="observation-time">
|
||||
<h5>🕐 Meilleur créneau :</h5>
|
||||
<p>{site.bestTime}</p>
|
||||
</div>
|
||||
<div class="coordinates">
|
||||
<h5>📍 Coordonnées GPS :</h5>
|
||||
<p>{site.lat}°N, {Math.abs(site.lng)}°O</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Conseils d'observation -->
|
||||
<div class="observation-tips" class:visible>
|
||||
<h3>Conseils pour l'Observation</h3>
|
||||
<div class="tips-grid">
|
||||
<div class="tip-card">
|
||||
<span class="tip-icon">🌙</span>
|
||||
<h4>Phase Lunaire</h4>
|
||||
<p>Privilégiez les nuits sans Lune ou avec un fin croissant</p>
|
||||
</div>
|
||||
<div class="tip-card">
|
||||
<span class="tip-icon">☁️</span>
|
||||
<h4>Météo</h4>
|
||||
<p>Vérifiez l'absence de nuages à l'Est, surtout près de l'horizon</p>
|
||||
</div>
|
||||
<div class="tip-card">
|
||||
<span class="tip-icon">⏰</span>
|
||||
<h4>Horaire</h4>
|
||||
<p>Arrivez 30 minutes avant pour habituer vos yeux à l'obscurité</p>
|
||||
</div>
|
||||
<div class="tip-card">
|
||||
<span class="tip-icon">🔦</span>
|
||||
<h4>Équipement</h4>
|
||||
<p>Lampe rouge, jumelles optionnelles, vêtements chauds</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.map-section {
|
||||
padding: var(--spacing-xl) 0;
|
||||
background: var(--gradient-night);
|
||||
}
|
||||
|
||||
.map-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-lg);
|
||||
margin-top: var(--spacing-lg);
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: all 0.8s ease-out;
|
||||
}
|
||||
|
||||
.map-wrapper.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Carte Canvas */
|
||||
.map-container {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 20px;
|
||||
padding: 2rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.guadeloupe-map {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
cursor: pointer;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Liste des sites */
|
||||
.sites-list {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.sites-list h3 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.site-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 2px solid transparent;
|
||||
border-radius: 15px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.site-card:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.site-card.active {
|
||||
background: rgba(255, 215, 0, 0.1);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.site-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.site-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.site-header h4 {
|
||||
color: var(--color-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.site-description {
|
||||
color: var(--color-light);
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.site-details {
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
animation: slideDown 0.3s ease-out;
|
||||
}
|
||||
|
||||
.site-details h5 {
|
||||
color: var(--color-secondary);
|
||||
margin: 0.5rem 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.site-details ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.site-details li {
|
||||
padding: 0.25rem 0;
|
||||
padding-left: 1rem;
|
||||
position: relative;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.site-details li::before {
|
||||
content: "•";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.observation-time, .coordinates {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.observation-time p, .coordinates p {
|
||||
margin: 0;
|
||||
color: var(--color-light);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Conseils d'observation */
|
||||
.observation-tips {
|
||||
margin-top: var(--spacing-xl);
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: all 0.8s ease-out 0.2s;
|
||||
}
|
||||
|
||||
.observation-tips.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.observation-tips h3 {
|
||||
color: var(--color-primary);
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.tips-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.tip-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 15px;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.tip-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.tip-icon {
|
||||
font-size: 2.5rem;
|
||||
display: block;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tip-card h4 {
|
||||
color: var(--color-primary);
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
.tip-card p {
|
||||
color: var(--color-light);
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Scrollbar personnalisée */
|
||||
.sites-list::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.sites-list::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.sites-list::-webkit-scrollbar-thumb {
|
||||
background: var(--color-primary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
max-height: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.map-wrapper {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.map-container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.sites-list {
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.tips-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,435 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import anime from '../lib/anime.js';
|
||||
|
||||
let visible = false;
|
||||
let activePhase = 0;
|
||||
|
||||
const observationPhases = [
|
||||
{
|
||||
time: '04h30',
|
||||
title: 'Préparation',
|
||||
description: 'Arrivez sur site, laissez vos yeux s\'adapter',
|
||||
icon: '🌙'
|
||||
},
|
||||
{
|
||||
time: '05h00',
|
||||
title: 'Recherche',
|
||||
description: 'Cherchez vers l\'Est-Sud-Est, près de l\'horizon',
|
||||
icon: '🔭'
|
||||
},
|
||||
{
|
||||
time: '05h15',
|
||||
title: 'Apparition',
|
||||
description: 'Sirius émerge de la lueur de l\'aube',
|
||||
icon: '⭐'
|
||||
},
|
||||
{
|
||||
time: '05h30',
|
||||
title: 'Observation',
|
||||
description: 'Moment optimal pour contempler',
|
||||
icon: '✨'
|
||||
}
|
||||
];
|
||||
|
||||
const equipment = [
|
||||
{ item: 'Jumelles', essential: false, description: '7x50 ou 10x50 recommandées' },
|
||||
{ item: 'Lampe rouge', essential: true, description: 'Préserve la vision nocturne' },
|
||||
{ item: 'Boussole', essential: true, description: 'Pour trouver l\'Est' },
|
||||
{ item: 'Vêtements chauds', essential: true, description: 'Les matins sont frais' },
|
||||
{ item: 'Carnet de notes', essential: false, description: 'Pour documenter l\'observation' }
|
||||
];
|
||||
|
||||
onMount(() => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
visible = true;
|
||||
|
||||
// Animation d'entrée
|
||||
anime.timeline({
|
||||
easing: 'easeOutQuad'
|
||||
})
|
||||
.add({
|
||||
targets: '.section-header',
|
||||
translateY: [30, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 800
|
||||
})
|
||||
.add({
|
||||
targets: '.timeline-container',
|
||||
translateX: [-50, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 1000
|
||||
}, '-=400')
|
||||
.add({
|
||||
targets: '.timeline-phase',
|
||||
scale: [0.8, 1],
|
||||
opacity: [0, 1],
|
||||
duration: 600,
|
||||
delay: anime.stagger(150)
|
||||
}, '-=600')
|
||||
.add({
|
||||
targets: '.equipment-grid',
|
||||
translateY: [50, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 800
|
||||
}, '-=400');
|
||||
|
||||
// Animation de la timeline automatique
|
||||
startTimelineAnimation();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const section = document.getElementById('observatory');
|
||||
if (section) observer.observe(section);
|
||||
|
||||
return () => observer.disconnect();
|
||||
});
|
||||
|
||||
function startTimelineAnimation() {
|
||||
// Progression automatique de la timeline
|
||||
const timeline = anime.timeline({
|
||||
loop: true,
|
||||
autoplay: true
|
||||
});
|
||||
|
||||
observationPhases.forEach((phase, index) => {
|
||||
timeline.add({
|
||||
targets: {},
|
||||
duration: 3000,
|
||||
changeBegin: () => {
|
||||
activePhase = index;
|
||||
animatePhaseChange(index);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function animatePhaseChange(index) {
|
||||
// Animation de l'indicateur actif
|
||||
anime({
|
||||
targets: '.timeline-indicator',
|
||||
left: `${index * 25}%`,
|
||||
duration: 800,
|
||||
easing: 'easeInOutQuad'
|
||||
});
|
||||
|
||||
// Pulsation de la phase active
|
||||
anime({
|
||||
targets: `.timeline-phase:nth-child(${index + 1}) .phase-icon`,
|
||||
scale: [1, 1.2, 1],
|
||||
duration: 600,
|
||||
easing: 'easeInOutQuad'
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<section id="observatory" class="observatory">
|
||||
<div class="container">
|
||||
<div class="section-header" class:visible>
|
||||
<h2>Guide d'Observation</h2>
|
||||
<p class="section-subtitle">
|
||||
Tout ce qu'il faut savoir pour observer le lever héliaque
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="observatory-content" class:visible>
|
||||
<!-- Timeline d'observation -->
|
||||
<div class="timeline-container">
|
||||
<h3>Chronologie de l'Observation</h3>
|
||||
<div class="timeline">
|
||||
<div class="timeline-track"></div>
|
||||
<div class="timeline-indicator"></div>
|
||||
{#each observationPhases as phase, i}
|
||||
<div class="timeline-phase" class:active={activePhase === i}>
|
||||
<div class="phase-icon">{phase.icon}</div>
|
||||
<div class="phase-time">{phase.time}</div>
|
||||
<div class="phase-info">
|
||||
<h4>{phase.title}</h4>
|
||||
<p>{phase.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Équipement recommandé -->
|
||||
<div class="equipment-section">
|
||||
<h3>Équipement Recommandé</h3>
|
||||
<div class="equipment-grid">
|
||||
{#each equipment as item, i}
|
||||
<div
|
||||
class="equipment-card"
|
||||
class:essential={item.essential}
|
||||
style="animation-delay: {i * 0.1}s"
|
||||
>
|
||||
<div class="equipment-header">
|
||||
<span class="equipment-name">{item.item}</span>
|
||||
{#if item.essential}
|
||||
<span class="essential-badge">Essentiel</span>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="equipment-desc">{item.description}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Conseils pratiques -->
|
||||
<div class="tips-section">
|
||||
<h3>Conseils Pratiques</h3>
|
||||
<div class="tips-grid">
|
||||
<div class="tip-card">
|
||||
<div class="tip-icon">🌤️</div>
|
||||
<h4>Météo Idéale</h4>
|
||||
<p>Ciel dégagé à l'Est, pas de nuages bas, humidité modérée</p>
|
||||
</div>
|
||||
<div class="tip-card">
|
||||
<div class="tip-icon">🌑</div>
|
||||
<h4>Phase Lunaire</h4>
|
||||
<p>Nouvelle lune ou croissant fin pour un ciel plus sombre</p>
|
||||
</div>
|
||||
<div class="tip-card">
|
||||
<div class="tip-icon">📱</div>
|
||||
<h4>Applications Utiles</h4>
|
||||
<p>Stellarium, SkySafari, Star Walk pour repérer les constellations</p>
|
||||
</div>
|
||||
<div class="tip-card">
|
||||
<div class="tip-icon">👥</div>
|
||||
<h4>En Groupe</h4>
|
||||
<p>Partagez ce moment unique avec famille et amis</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.observatory {
|
||||
padding: var(--spacing-xl) 0;
|
||||
background: var(--gradient-dawn);
|
||||
}
|
||||
|
||||
.observatory-content {
|
||||
margin-top: var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* Timeline */
|
||||
.timeline-container {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 20px;
|
||||
padding: 2rem;
|
||||
margin-bottom: 3rem;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.timeline-container h3 {
|
||||
color: var(--color-primary);
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.timeline-track {
|
||||
position: absolute;
|
||||
top: 3rem;
|
||||
left: 5%;
|
||||
right: 5%;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.timeline-indicator {
|
||||
position: absolute;
|
||||
top: 3rem;
|
||||
left: 0;
|
||||
width: 25%;
|
||||
height: 4px;
|
||||
background: var(--color-primary);
|
||||
border-radius: 2px;
|
||||
transition: left 0.8s ease-in-out;
|
||||
}
|
||||
|
||||
.timeline-phase {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
.phase-icon {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
filter: grayscale(1);
|
||||
opacity: 0.5;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.timeline-phase.active .phase-icon {
|
||||
filter: grayscale(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.phase-time {
|
||||
font-weight: 600;
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.phase-info h4 {
|
||||
color: var(--color-light);
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.phase-info p {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Équipement */
|
||||
.equipment-section {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.equipment-section h3 {
|
||||
color: var(--color-primary);
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.equipment-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.equipment-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 15px;
|
||||
padding: 1.5rem;
|
||||
border: 2px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
animation: fadeInUp 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
.equipment-card.essential {
|
||||
border-color: var(--color-primary);
|
||||
background: rgba(255, 215, 0, 0.05);
|
||||
}
|
||||
|
||||
.equipment-card:hover {
|
||||
transform: translateY(-5px);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.equipment-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.equipment-name {
|
||||
font-weight: 600;
|
||||
color: var(--color-light);
|
||||
}
|
||||
|
||||
.essential-badge {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-dark);
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 20px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.equipment-desc {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Conseils */
|
||||
.tips-section h3 {
|
||||
color: var(--color-primary);
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.tips-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.tip-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 15px;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
animation: fadeInUp 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
.tip-card:hover {
|
||||
transform: translateY(-5px);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.tip-icon {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tip-card h4 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tip-card p {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.timeline {
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.timeline-track {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.timeline-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.equipment-grid, .tips-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,430 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from '../lib/i18n.js';
|
||||
import anime from '../lib/anime.js';
|
||||
|
||||
let visible = false;
|
||||
let selectedLocation = 'moule';
|
||||
let calculationInProgress = false;
|
||||
let results = null;
|
||||
|
||||
const locations = [
|
||||
{ id: 'moule', name: 'Le Moule', lat: 16.3333, lng: -61.3472 },
|
||||
{ id: 'deshaies', name: 'Deshaies', lat: 16.3039, lng: -61.7966 },
|
||||
{ id: 'sainte-anne', name: 'Sainte-Anne', lat: 16.2262, lng: -61.3809 },
|
||||
{ id: 'port-louis', name: 'Port-Louis', lat: 16.4180, lng: -61.5312 },
|
||||
{ id: 'marie-galante', name: 'Marie-Galante', lat: 15.9500, lng: -61.2667 },
|
||||
{ id: 'soufriere', name: 'Route de la Soufrière', lat: 16.0439, lng: -61.6639 },
|
||||
{ id: 'petite-terre', name: 'Petite-Terre', lat: 16.1700, lng: -61.1200 }
|
||||
];
|
||||
|
||||
onMount(() => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
visible = true;
|
||||
|
||||
// Animation d'entrée
|
||||
anime.timeline({
|
||||
easing: 'easeOutQuad'
|
||||
})
|
||||
.add({
|
||||
targets: '.section-header',
|
||||
translateY: [30, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 800
|
||||
})
|
||||
.add({
|
||||
targets: '.calculator-container',
|
||||
translateY: [50, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 1000
|
||||
}, '-=400')
|
||||
.add({
|
||||
targets: '.location-option',
|
||||
translateX: [-20, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 600,
|
||||
delay: anime.stagger(50)
|
||||
}, '-=600');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const section = document.getElementById('predictions');
|
||||
if (section) observer.observe(section);
|
||||
|
||||
return () => observer.disconnect();
|
||||
});
|
||||
|
||||
function selectLocation(id) {
|
||||
selectedLocation = id;
|
||||
calculate();
|
||||
|
||||
// Animation de sélection
|
||||
anime({
|
||||
targets: `.location-option`,
|
||||
scale: 1,
|
||||
duration: 300
|
||||
});
|
||||
|
||||
anime({
|
||||
targets: `[data-location="${id}"]`,
|
||||
scale: 1.05,
|
||||
duration: 300,
|
||||
easing: 'easeOutElastic(1, .5)'
|
||||
});
|
||||
}
|
||||
|
||||
function calculate() {
|
||||
calculationInProgress = true;
|
||||
results = null;
|
||||
|
||||
// Animation du calcul
|
||||
anime({
|
||||
targets: '.calculating-indicator',
|
||||
opacity: [0, 1],
|
||||
duration: 300
|
||||
});
|
||||
|
||||
// Animation des étoiles pendant le calcul
|
||||
anime({
|
||||
targets: '.star-indicator',
|
||||
rotate: 360,
|
||||
duration: 2000,
|
||||
easing: 'linear',
|
||||
loop: true
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
const location = locations.find(l => l.id === selectedLocation);
|
||||
|
||||
// Calculs simplifiés
|
||||
const baseDate = new Date('2025-07-22');
|
||||
const latitudeAdjustment = (location.lat - 16.25) * 0.5;
|
||||
const heliacalDate = new Date(baseDate);
|
||||
heliacalDate.setDate(baseDate.getDate() + Math.round(latitudeAdjustment));
|
||||
|
||||
results = {
|
||||
location: location.name,
|
||||
heliacalDate: heliacalDate.toLocaleDateString('fr-FR', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
}),
|
||||
bestTime: '05h00 - 05h30',
|
||||
duration: '15-20 minutes',
|
||||
azimuth: '117° (ESE)',
|
||||
altitude: '57.1°',
|
||||
magnitude: '-1.46'
|
||||
};
|
||||
|
||||
calculationInProgress = false;
|
||||
|
||||
// Animation des résultats
|
||||
anime.timeline({
|
||||
easing: 'easeOutQuad'
|
||||
})
|
||||
.add({
|
||||
targets: '.calculating-indicator',
|
||||
opacity: [1, 0],
|
||||
duration: 300
|
||||
})
|
||||
.add({
|
||||
targets: '.results-container',
|
||||
translateY: [20, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 600
|
||||
})
|
||||
.add({
|
||||
targets: '.result-item',
|
||||
translateX: [20, 0],
|
||||
opacity: [0, 1],
|
||||
duration: 400,
|
||||
delay: anime.stagger(100)
|
||||
}, '-=400')
|
||||
.add({
|
||||
targets: '.result-value',
|
||||
scale: [0.8, 1],
|
||||
duration: 600,
|
||||
delay: anime.stagger(100),
|
||||
easing: 'easeOutElastic(1, .5)'
|
||||
}, '-=600');
|
||||
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// Calculer automatiquement au chargement
|
||||
onMount(() => {
|
||||
setTimeout(() => {
|
||||
if (visible) calculate();
|
||||
}, 1000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<section id="predictions" class="predictions">
|
||||
<div class="container">
|
||||
<div class="section-header" class:visible>
|
||||
<h2>{$t('predictions_title')}</h2>
|
||||
<p class="section-subtitle">
|
||||
{$t('predictions_subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="calculator-container" class:visible>
|
||||
<div class="location-selector">
|
||||
<h3>{$t('predictions_select_location')}</h3>
|
||||
<div class="locations-grid">
|
||||
{#each locations as location}
|
||||
<button
|
||||
class="location-option"
|
||||
class:active={selectedLocation === location.id}
|
||||
data-location={location.id}
|
||||
on:click={() => selectLocation(location.id)}
|
||||
>
|
||||
<span class="location-name">{location.name}</span>
|
||||
<span class="location-coords">{location.lat}°N</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="results-section">
|
||||
{#if calculationInProgress}
|
||||
<div class="calculating-indicator">
|
||||
<div class="star-indicator">⭐</div>
|
||||
<p>Calcul en cours...</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if results && !calculationInProgress}
|
||||
<div class="results-container">
|
||||
<h3>{$t('predictions_results_for')} {results.location}</h3>
|
||||
|
||||
<div class="results-grid">
|
||||
<div class="result-item highlight">
|
||||
<div class="result-icon">🌅</div>
|
||||
<div class="result-label">{$t('predictions_heliacal_rising')}</div>
|
||||
<div class="result-value">{results.heliacalDate}</div>
|
||||
</div>
|
||||
|
||||
<div class="result-item">
|
||||
<div class="result-icon">⏰</div>
|
||||
<div class="result-label">{$t('predictions_best_observation')}</div>
|
||||
<div class="result-value">{results.bestTime}</div>
|
||||
</div>
|
||||
|
||||
<div class="result-item">
|
||||
<div class="result-icon">⏱️</div>
|
||||
<div class="result-label">{$t('predictions_visibility_duration')}</div>
|
||||
<div class="result-value">{results.duration}</div>
|
||||
</div>
|
||||
|
||||
<div class="result-item">
|
||||
<div class="result-icon">🧭</div>
|
||||
<div class="result-label">{$t('predictions_azimuth')}</div>
|
||||
<div class="result-value">{results.azimuth}</div>
|
||||
</div>
|
||||
|
||||
<div class="result-item">
|
||||
<div class="result-icon">📐</div>
|
||||
<div class="result-label">{$t('predictions_max_altitude')}</div>
|
||||
<div class="result-value">{results.altitude}</div>
|
||||
</div>
|
||||
|
||||
<div class="result-item">
|
||||
<div class="result-icon">✨</div>
|
||||
<div class="result-label">{$t('predictions_magnitude')}</div>
|
||||
<div class="result-value">{results.magnitude}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="observation-tips">
|
||||
<h4>💡 Conseils d'observation</h4>
|
||||
<ul>
|
||||
<li>Arrivez 30 minutes avant pour habituer vos yeux</li>
|
||||
<li>Cherchez vers l'Est-Sud-Est (ESE)</li>
|
||||
<li>Sirius apparaîtra comme l'étoile la plus brillante</li>
|
||||
<li>Conditions idéales : ciel dégagé, sans Lune</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.predictions {
|
||||
padding: var(--spacing-xl) 0;
|
||||
background: var(--gradient-night);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.calculator-container {
|
||||
opacity: 0;
|
||||
margin-top: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.location-selector {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 20px;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.location-selector h3 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.locations-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.location-option {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 2px solid transparent;
|
||||
border-radius: 15px;
|
||||
padding: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.location-option:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.location-option.active {
|
||||
background: rgba(255, 215, 0, 0.1);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.location-name {
|
||||
font-weight: 600;
|
||||
color: var(--color-light);
|
||||
}
|
||||
|
||||
.location-coords {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-primary);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.calculating-indicator {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.star-indicator {
|
||||
font-size: 3rem;
|
||||
display: inline-block;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.results-container {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 20px;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.results-container h3 {
|
||||
color: var(--color-primary);
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.results-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.result-item {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 15px;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.result-item.highlight {
|
||||
background: rgba(255, 215, 0, 0.1);
|
||||
border: 2px solid var(--color-primary);
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-light);
|
||||
opacity: 0.8;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.observation-tips {
|
||||
background: rgba(255, 215, 0, 0.05);
|
||||
border-radius: 15px;
|
||||
padding: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.observation-tips h4 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.observation-tips ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.observation-tips li {
|
||||
padding: 0.5rem 0;
|
||||
padding-left: 1.5rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.observation-tips li::before {
|
||||
content: "✦";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.locations-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.results-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.location-selector, .results-container {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,375 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let visible = false;
|
||||
let activeTab = 'mechanics';
|
||||
|
||||
onMount(() => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
visible = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const section = document.getElementById('science');
|
||||
if (section) observer.observe(section);
|
||||
|
||||
return () => observer.disconnect();
|
||||
});
|
||||
|
||||
const tabs = [
|
||||
{ id: 'mechanics', label: 'Mécanique Céleste', icon: '🌌' },
|
||||
{ id: 'arcus', label: 'Arcus Visionis', icon: '📐' },
|
||||
{ id: 'factors', label: 'Facteurs d\'Influence', icon: '🌡️' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<section id="science" class="science">
|
||||
<div class="container">
|
||||
<div class="section-header" class:visible>
|
||||
<h2>La Science du Lever Héliaque</h2>
|
||||
<p class="section-subtitle">
|
||||
Comprendre la danse cosmique entre Terre, Soleil et Sirius
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="science-content" class:visible>
|
||||
<div class="tabs">
|
||||
{#each tabs as tab}
|
||||
<button
|
||||
class="tab-button"
|
||||
class:active={activeTab === tab.id}
|
||||
on:click={() => activeTab = tab.id}
|
||||
>
|
||||
<span class="tab-icon">{tab.icon}</span>
|
||||
<span class="tab-label">{tab.label}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="tab-content">
|
||||
{#if activeTab === 'mechanics'}
|
||||
<div class="content-section">
|
||||
<h3>Le Ballet Cosmique</h3>
|
||||
<p>
|
||||
Le lever héliaque est le fruit d'une chorégraphie céleste précise.
|
||||
Pendant environ 70 jours, Sirius reste invisible, cachée par l'éclat du Soleil.
|
||||
Son retour à l'aube marque un moment unique où l'étoile se lève juste avant
|
||||
le Soleil, visible quelques instants dans les lueurs du crépuscule matinal.
|
||||
</p>
|
||||
|
||||
<div class="diagram card">
|
||||
<div class="celestial-body sun">
|
||||
<span>☀️</span>
|
||||
<label>Soleil</label>
|
||||
</div>
|
||||
<div class="orbit-path"></div>
|
||||
<div class="celestial-body earth">
|
||||
<span>🌍</span>
|
||||
<label>Terre</label>
|
||||
</div>
|
||||
<div class="celestial-body sirius">
|
||||
<span>⭐</span>
|
||||
<label>Sirius</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
La Terre, dans son mouvement orbital annuel, modifie notre perspective
|
||||
sur les étoiles. Quand le Soleil se trouve entre nous et Sirius,
|
||||
l'étoile devient invisible. C'est seulement quand l'angle devient
|
||||
favorable que Sirius réapparaît.
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if activeTab === 'arcus'}
|
||||
<div class="content-section">
|
||||
<h3>L'Arc de Visibilité</h3>
|
||||
<p>
|
||||
L'Arcus Visionis est l'angle critique : le Soleil doit être à environ
|
||||
9-10° sous l'horizon pour que Sirius devienne visible. C'est un équilibre
|
||||
délicat entre la brillance de l'étoile et la luminosité du ciel.
|
||||
</p>
|
||||
|
||||
<div class="arcus-diagram card">
|
||||
<div class="horizon-line">
|
||||
<span>Horizon</span>
|
||||
</div>
|
||||
<div class="angle-indicator">
|
||||
<div class="angle-arc"></div>
|
||||
<span class="angle-value">9-10°</span>
|
||||
</div>
|
||||
<div class="sirius-position">
|
||||
<span>⭐ Sirius</span>
|
||||
</div>
|
||||
<div class="sun-position">
|
||||
<span>☀️ Soleil</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Pour Sirius, avec sa magnitude exceptionnelle de -1.46, cet angle
|
||||
est relativement faible comparé à d'autres étoiles moins brillantes.
|
||||
C'est pourquoi elle est l'une des premières à être visible à l'aube.
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if activeTab === 'factors'}
|
||||
<div class="content-section">
|
||||
<h3>Les Variables en Jeu</h3>
|
||||
|
||||
<div class="factors-grid">
|
||||
<div class="factor-card card">
|
||||
<div class="factor-icon">🌟</div>
|
||||
<h4>Magnitude</h4>
|
||||
<p>
|
||||
Sirius brille à -1.46, la rendant visible même dans des conditions
|
||||
difficiles. Plus une étoile est brillante, plus tôt elle perce
|
||||
les lueurs de l'aube.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="factor-card card">
|
||||
<div class="factor-icon">🗺️</div>
|
||||
<h4>Latitude</h4>
|
||||
<p>
|
||||
À 16°N en Guadeloupe, les astres se lèvent presque verticalement.
|
||||
Cette géométrie tropicale influence la durée du crépuscule et
|
||||
la visibilité.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="factor-card card">
|
||||
<div class="factor-icon">💨</div>
|
||||
<h4>Atmosphère</h4>
|
||||
<p>
|
||||
L'humidité caribéenne et les brumes de sable peuvent retarder
|
||||
la visibilité de plusieurs jours. Un ciel sec et clair est idéal.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.science {
|
||||
padding: var(--spacing-xl) 0;
|
||||
background: linear-gradient(135deg, #0a0e27 0%, #1a1a2e 100%);
|
||||
}
|
||||
|
||||
.science-content {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: all 0.8s ease-out 0.3s;
|
||||
}
|
||||
|
||||
.science-content.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 2px solid rgba(255, 215, 0, 0.3);
|
||||
border-radius: 50px;
|
||||
color: var(--color-light);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-base);
|
||||
}
|
||||
|
||||
.tab-button:hover,
|
||||
.tab-button.active {
|
||||
background: rgba(255, 215, 0, 0.2);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.content-section {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.content-section h3 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.content-section p {
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
/* Diagramme orbital */
|
||||
.diagram {
|
||||
position: relative;
|
||||
height: 300px;
|
||||
margin: 2rem 0;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.celestial-body {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.celestial-body span {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.celestial-body label {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.sun {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.earth {
|
||||
top: 20%;
|
||||
left: 20%;
|
||||
}
|
||||
|
||||
.sirius {
|
||||
top: 20%;
|
||||
right: 20%;
|
||||
}
|
||||
|
||||
.orbit-path {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border: 2px dashed rgba(255, 215, 0, 0.3);
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
/* Diagramme Arcus */
|
||||
.arcus-diagram {
|
||||
position: relative;
|
||||
height: 250px;
|
||||
margin: 2rem 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.horizon-line {
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: var(--color-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.horizon-line span {
|
||||
background: var(--color-dark);
|
||||
padding: 0 1rem;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.sirius-position {
|
||||
position: absolute;
|
||||
top: 30%;
|
||||
left: 30%;
|
||||
}
|
||||
|
||||
.sun-position {
|
||||
position: absolute;
|
||||
bottom: 20%;
|
||||
right: 30%;
|
||||
}
|
||||
|
||||
.angle-indicator {
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
right: 40%;
|
||||
}
|
||||
|
||||
.angle-arc {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border: 2px solid var(--color-accent);
|
||||
border-radius: 50%;
|
||||
border-bottom: none;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.angle-value {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: -30px;
|
||||
color: var(--color-accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Grille des facteurs */
|
||||
.factors-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: var(--spacing-md);
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.factor-card {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.factor-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.factor-card h4 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tabs {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.diagram {
|
||||
height: 250px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,192 @@
|
||||
---
|
||||
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="card 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="card 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="card 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="card info-card">
|
||||
<h3>{{ "dogon_system_sigutolo_title" | t(locale) }}</h3>
|
||||
<p>{{ "dogon_system_sigutolo_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="card info-card">
|
||||
<h3>{{ "dogon_system_potolo_title" | t(locale) }}</h3>
|
||||
<p>{{ "dogon_system_potolo_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="card 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="card info-card">
|
||||
<h3>{{ "dogon_astronomy_geometry_title" | t(locale) }}</h3>
|
||||
<p>{{ "dogon_astronomy_geometry_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="card info-card">
|
||||
<h3>{{ "dogon_astronomy_calendar_title" | t(locale) }}</h3>
|
||||
<p>{{ "dogon_astronomy_calendar_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="card info-card">
|
||||
<h3>{{ "dogon_astronomy_planets_title" | t(locale) }}</h3>
|
||||
<p>{{ "dogon_astronomy_planets_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="card 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>
|
||||
@@ -1,90 +0,0 @@
|
||||
// Données des prédictions du lever héliaque
|
||||
export const predictionsData = {
|
||||
"Pointe des Châteaux": {
|
||||
coords: { lat: 16.2476, lon: -61.1771, alt: 30 },
|
||||
description: "Point de vue élevé avec panorama dégagé vers l'Est sur l'Atlantique",
|
||||
predictions: {
|
||||
"2025": { date: "22 juillet", time: "05:12" },
|
||||
"2026": { date: "22 juillet", time: "05:12" },
|
||||
"2027": { date: "22 juillet", time: "05:13" },
|
||||
"2028": { date: "21 juillet", time: "05:13" },
|
||||
"2029": { date: "22 juillet", time: "05:14" }
|
||||
}
|
||||
},
|
||||
"Pointe de la Grande Vigie": {
|
||||
coords: { lat: 16.5098, lon: -61.4666, alt: 81 },
|
||||
description: "Point le plus septentrional de la Guadeloupe, horizon marin parfait",
|
||||
predictions: {
|
||||
"2025": { date: "22 juillet", time: "05:11" },
|
||||
"2026": { date: "22 juillet", time: "05:12" },
|
||||
"2027": { date: "22 juillet", time: "05:12" },
|
||||
"2028": { date: "21 juillet", time: "05:12" },
|
||||
"2029": { date: "22 juillet", time: "05:13" }
|
||||
}
|
||||
},
|
||||
"Duzer (Sainte-Rose)": {
|
||||
coords: { lat: 16.3361, lon: -61.7430, alt: 20 },
|
||||
description: "Site nécessitant un point dégagé vers l'Est sur les mornes",
|
||||
predictions: {
|
||||
"2025": { date: "22 juillet", time: "05:13" },
|
||||
"2026": { date: "22 juillet", time: "05:14" },
|
||||
"2027": { date: "22 juillet", time: "05:14" },
|
||||
"2028": { date: "21 juillet", time: "05:14" },
|
||||
"2029": { date: "22 juillet", time: "05:15" }
|
||||
}
|
||||
},
|
||||
"Saint-Félix (Le Gosier)": {
|
||||
coords: { lat: 16.2005, lon: -61.4605, alt: 5 },
|
||||
description: "Plage avec observation au niveau de la mer",
|
||||
predictions: {
|
||||
"2025": { date: "22 juillet", time: "05:13" },
|
||||
"2026": { date: "22 juillet", time: "05:13" },
|
||||
"2027": { date: "22 juillet", time: "05:14" },
|
||||
"2028": { date: "21 juillet", time: "05:14" },
|
||||
"2029": { date: "22 juillet", time: "05:15" }
|
||||
}
|
||||
},
|
||||
"Grande Pointe (Trois-Rivières)": {
|
||||
coords: { lat: 15.9696, lon: -61.6308, alt: 42 },
|
||||
description: "Site côtier avec altitude intéressante pour l'observation",
|
||||
predictions: {
|
||||
"2025": { date: "22 juillet", time: "05:13" },
|
||||
"2026": { date: "22 juillet", time: "05:14" },
|
||||
"2027": { date: "22 juillet", time: "05:14" },
|
||||
"2028": { date: "21 juillet", time: "05:15" },
|
||||
"2029": { date: "22 juillet", time: "05:15" }
|
||||
}
|
||||
},
|
||||
"Petit-Pérou": {
|
||||
coords: { lat: 16.0517, lon: -61.5567, alt: 10 },
|
||||
description: "Site des roches gravées, observation depuis la côte",
|
||||
predictions: {
|
||||
"2025": { date: "22 juillet", time: "05:14" },
|
||||
"2026": { date: "22 juillet", time: "05:14" },
|
||||
"2027": { date: "22 juillet", time: "05:15" },
|
||||
"2028": { date: "21 juillet", time: "05:15" },
|
||||
"2029": { date: "22 juillet", time: "05:16" }
|
||||
}
|
||||
},
|
||||
"Pointe du Vieux-Fort": {
|
||||
coords: { lat: 15.9483, lon: -61.7072, alt: 20 },
|
||||
description: "Point le plus méridional de Basse-Terre, vue sur le canal des Saintes",
|
||||
predictions: {
|
||||
"2025": { date: "22 juillet", time: "05:14" },
|
||||
"2026": { date: "22 juillet", time: "05:14" },
|
||||
"2027": { date: "22 juillet", time: "05:15" },
|
||||
"2028": { date: "21 juillet", time: "05:15" },
|
||||
"2029": { date: "22 juillet", time: "05:16" }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const years = ["2025", "2026", "2027", "2028", "2029"];
|
||||
|
||||
export function calculateObservationWindow(dateStr) {
|
||||
const day = parseInt(dateStr.split(' ')[0]);
|
||||
const month = dateStr.split(' ')[1];
|
||||
const startDay = day - 2;
|
||||
const endDay = day + 2;
|
||||
return `${startDay} au ${endDay} ${month}`;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
// Données simplifiées des continents en GeoJSON
|
||||
// Source: Natural Earth (simplified)
|
||||
export const worldData = {
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": { "name": "Africa" },
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [[
|
||||
[-17.5, 35], [-5, 36], [10, 37], [35, 30], [42, 15],
|
||||
[51, 12], [51, -2], [41, -12], [35, -25], [28, -33],
|
||||
[20, -35], [18, -35], [12, -18], [10, -5], [8, 5],
|
||||
[-5, 5], [-17, 15], [-17.5, 35]
|
||||
]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": { "name": "Europe" },
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [[
|
||||
[-10, 36], [-5, 43], [0, 50], [10, 54], [20, 59],
|
||||
[30, 60], [40, 55], [30, 45], [20, 40], [10, 45],
|
||||
[0, 40], [-10, 36]
|
||||
]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": { "name": "Asia" },
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [[
|
||||
[26, 40], [40, 45], [60, 50], [80, 55], [100, 60],
|
||||
[120, 60], [140, 50], [145, 40], [135, 30], [120, 20],
|
||||
[100, 10], [80, 15], [70, 25], [60, 30], [45, 35],
|
||||
[26, 40]
|
||||
]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": { "name": "North America" },
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [[
|
||||
[-170, 65], [-165, 68], [-150, 70], [-130, 70], [-100, 70],
|
||||
[-80, 68], [-65, 60], [-55, 50], [-65, 45], [-75, 40],
|
||||
[-80, 35], [-85, 30], [-90, 25], [-95, 20], [-105, 20],
|
||||
[-110, 25], [-115, 30], [-120, 35], [-125, 40], [-130, 45],
|
||||
[-135, 50], [-140, 55], [-150, 58], [-160, 60], [-170, 65]
|
||||
]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": { "name": "South America" },
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [[
|
||||
[-35, -5], [-38, -15], [-40, -20], [-48, -25], [-58, -35],
|
||||
[-65, -45], [-70, -55], [-68, -55], [-73, -45], [-72, -35],
|
||||
[-70, -25], [-75, -15], [-78, -5], [-80, 0], [-77, 5],
|
||||
[-75, 10], [-70, 12], [-60, 8], [-50, 5], [-35, -5]
|
||||
]]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": { "name": "Australia" },
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [[
|
||||
[113, -22], [130, -12], [135, -12], [140, -18], [145, -20],
|
||||
[150, -25], [153, -30], [150, -35], [145, -38], [140, -38],
|
||||
[135, -35], [130, -32], [125, -32], [120, -35], [115, -35],
|
||||
[113, -30], [113, -22]
|
||||
]]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
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" 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="card feature-card">
|
||||
<h3>{{ "feature_brightest_title" | t(locale) }}</h3>
|
||||
<p>{{ "feature_brightest_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="card feature-card">
|
||||
<h3>{{ "feature_rising_title" | t(locale) }}</h3>
|
||||
<p>{{ "feature_rising_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="card feature-card">
|
||||
<h3>{{ "feature_heritage_title" | t(locale) }}</h3>
|
||||
<p>{{ "feature_heritage_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,107 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { worldData, waveCities } from "./world.js";
|
||||
|
||||
let canvas = $state();
|
||||
let activeCity = $state(null);
|
||||
|
||||
function project(lon, lat, width, height) {
|
||||
const x = ((lon + 180) / 360) * width;
|
||||
const y = ((90 - lat) / 180) * height;
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
function draw() {
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext("2d");
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const width = canvas.clientWidth;
|
||||
const height = canvas.clientHeight;
|
||||
canvas.width = width * dpr;
|
||||
canvas.height = height * dpr;
|
||||
ctx.scale(dpr, dpr);
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
const styles = getComputedStyle(canvas);
|
||||
const gold = styles.getPropertyValue("--gold-surface").trim() || "#e8c766";
|
||||
const teal = styles.getPropertyValue("--caribbean-teal").trim() || "#3a9088";
|
||||
const border = styles.getPropertyValue("--border").trim() || "#333";
|
||||
|
||||
ctx.fillStyle = border;
|
||||
worldData.features.forEach((feature) => {
|
||||
ctx.beginPath();
|
||||
feature.geometry.coordinates[0].forEach(([lon, lat], i) => {
|
||||
const { x, y } = project(lon, lat, width, height);
|
||||
if (i === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
});
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
});
|
||||
|
||||
waveCities.forEach((city) => {
|
||||
const { x, y } = project(city.lon, city.lat, width, height);
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, city.name === activeCity?.name ? 7 : 4, 0, Math.PI * 2);
|
||||
ctx.fillStyle = city.name === "Guadeloupe" || city.name === "Martinique" ? gold : teal;
|
||||
ctx.fill();
|
||||
});
|
||||
}
|
||||
|
||||
function pickCity(clientX, clientY) {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const x = clientX - rect.left;
|
||||
const y = clientY - rect.top;
|
||||
let closest = null;
|
||||
let closestDist = Infinity;
|
||||
waveCities.forEach((city) => {
|
||||
const p = project(city.lon, city.lat, rect.width, rect.height);
|
||||
const dist = Math.hypot(p.x - x, p.y - y);
|
||||
if (dist < closestDist) {
|
||||
closestDist = dist;
|
||||
closest = city;
|
||||
}
|
||||
});
|
||||
activeCity = closestDist < 30 ? closest : null;
|
||||
draw();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
draw();
|
||||
const onResize = () => draw();
|
||||
window.addEventListener("resize", onResize);
|
||||
return () => window.removeEventListener("resize", onResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="sirius-globe">
|
||||
<canvas
|
||||
bind:this={canvas}
|
||||
class="island-canvas"
|
||||
aria-label="Carte de la progression du lever héliaque à travers l'Afrique et les Caraïbes, touchez une ville pour voir sa date"
|
||||
tabindex="0"
|
||||
onmousemove={(e) => pickCity(e.clientX, e.clientY)}
|
||||
onmouseleave={() => { activeCity = null; draw(); }}
|
||||
onclick={(e) => pickCity(e.clientX, e.clientY)}
|
||||
></canvas>
|
||||
<p class="globe-caption" aria-live="polite">
|
||||
{#if activeCity}
|
||||
<strong>{activeCity.name}</strong> — {activeCity.date}
|
||||
{:else}
|
||||
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.island-canvas {
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.globe-caption {
|
||||
text-align: center;
|
||||
min-height: 1.5em;
|
||||
color: var(--gold);
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,24 @@
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Continents simplifiés (issus de Natural Earth, tracé approximatif) —
|
||||
// utilisés uniquement comme repère visuel derrière la vague du lever héliaque.
|
||||
export const worldData = {
|
||||
type: "FeatureCollection",
|
||||
features: [
|
||||
{
|
||||
type: "Feature",
|
||||
properties: { name: "Africa" },
|
||||
geometry: {
|
||||
type: "Polygon",
|
||||
coordinates: [[
|
||||
[-17.5, 35], [-5, 36], [10, 37], [35, 30], [42, 15],
|
||||
[51, 12], [51, -2], [41, -12], [35, -25], [28, -33],
|
||||
[20, -35], [18, -35], [12, -18], [10, -5], [8, 5],
|
||||
[-5, 5], [-17, 15], [-17.5, 35],
|
||||
]],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "Feature",
|
||||
properties: { name: "Europe" },
|
||||
geometry: {
|
||||
type: "Polygon",
|
||||
coordinates: [[
|
||||
[-10, 36], [-5, 43], [0, 50], [10, 54], [20, 59],
|
||||
[30, 60], [40, 55], [30, 45], [20, 40], [10, 45],
|
||||
[0, 40], [-10, 36],
|
||||
]],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "Feature",
|
||||
properties: { name: "North America" },
|
||||
geometry: {
|
||||
type: "Polygon",
|
||||
coordinates: [[
|
||||
[-170, 65], [-165, 68], [-150, 70], [-130, 70], [-100, 70],
|
||||
[-80, 68], [-65, 60], [-55, 50], [-65, 45], [-75, 40],
|
||||
[-80, 35], [-85, 30], [-90, 25], [-95, 20], [-105, 20],
|
||||
[-110, 25], [-115, 30], [-120, 35], [-125, 40], [-130, 45],
|
||||
[-135, 50], [-140, 55], [-150, 58], [-160, 60], [-170, 65],
|
||||
]],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "Feature",
|
||||
properties: { name: "South America" },
|
||||
geometry: {
|
||||
type: "Polygon",
|
||||
coordinates: [[
|
||||
[-35, -5], [-38, -15], [-40, -20], [-48, -25], [-58, -35],
|
||||
[-65, -45], [-70, -55], [-68, -55], [-73, -45], [-72, -35],
|
||||
[-70, -25], [-75, -15], [-78, -5], [-80, 0], [-77, 5],
|
||||
[-75, 10], [-70, 12], [-60, 8], [-50, 5], [-35, -5],
|
||||
]],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Progression du lever héliaque à travers la diaspora africaine et caribéenne
|
||||
// (fin juin à mi-août), du plus oriental au plus occidental.
|
||||
export const waveCities = [
|
||||
{ name: "La Réunion", lat: -21.1151, lon: 55.5364, date: "27 juin" },
|
||||
{ name: "Kinshasa", lat: -4.4419, lon: 15.2663, date: "16 juillet" },
|
||||
{ name: "Douala", lat: 4.0511, lon: 9.7679, date: "18 juillet" },
|
||||
{ name: "Yaoundé", lat: 3.848, lon: 11.5021, date: "19 juillet" },
|
||||
{ name: "Abidjan", lat: 5.36, lon: -4.0083, date: "20 juillet" },
|
||||
{ name: "Accra", lat: 5.6037, lon: -0.187, date: "21 juillet" },
|
||||
{ name: "Guadeloupe", lat: 16.265, lon: -61.551, date: "22 juillet" },
|
||||
{ name: "Martinique", lat: 14.6415, lon: -61.0242, date: "23 juillet" },
|
||||
{ name: "Dakar", lat: 14.7167, lon: -17.4677, date: "23 juillet" },
|
||||
{ name: "Cayenne", lat: 4.9346, lon: -52.3281, date: "24 juillet" },
|
||||
{ name: "Bamako", lat: 12.6392, lon: -8.0029, date: "25 juillet" },
|
||||
{ name: "Le Caire", lat: 30.0444, lon: 31.2357, date: "3 août" },
|
||||
{ name: "Paris", lat: 48.8566, lon: 2.3522, date: "15 août" },
|
||||
];
|
||||
@@ -0,0 +1,117 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let { sites, locale } = $props();
|
||||
|
||||
let canvas = $state();
|
||||
let activeId = $state(null);
|
||||
let active = $derived(sites.find((s) => s.id === activeId) ?? null);
|
||||
|
||||
let bounds = $derived.by(() => {
|
||||
const lats = sites.map((s) => s.lat);
|
||||
const lons = sites.map((s) => s.lon);
|
||||
return {
|
||||
minLat: Math.min(...lats) - 0.15,
|
||||
maxLat: Math.max(...lats) + 0.15,
|
||||
minLon: Math.min(...lons) - 0.15,
|
||||
maxLon: Math.max(...lons) + 0.15,
|
||||
};
|
||||
});
|
||||
|
||||
function project(site, width, height) {
|
||||
const x = ((site.lon - bounds.minLon) / (bounds.maxLon - bounds.minLon)) * width;
|
||||
const y = height - ((site.lat - bounds.minLat) / (bounds.maxLat - bounds.minLat)) * height;
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
function draw() {
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext("2d");
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const width = canvas.clientWidth;
|
||||
const height = canvas.clientHeight;
|
||||
canvas.width = width * dpr;
|
||||
canvas.height = height * dpr;
|
||||
ctx.scale(dpr, dpr);
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
const styles = getComputedStyle(canvas);
|
||||
const gold = styles.getPropertyValue("--gold-surface").trim() || "#e8c766";
|
||||
const teal = styles.getPropertyValue("--caribbean-teal").trim() || "#3a9088";
|
||||
const ink = styles.getPropertyValue("--ink-soft").trim() || "#888";
|
||||
|
||||
sites.forEach((site) => {
|
||||
const { x, y } = project(site, width, height);
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, site.id === activeId ? 9 : 6, 0, Math.PI * 2);
|
||||
ctx.fillStyle = site.id === activeId ? teal : gold;
|
||||
ctx.fill();
|
||||
|
||||
ctx.font = "12px sans-serif";
|
||||
ctx.fillStyle = ink;
|
||||
ctx.textAlign = x > width * 0.7 ? "right" : "left";
|
||||
ctx.fillText(site.name, x + (x > width * 0.7 ? -12 : 12), y + 4);
|
||||
});
|
||||
}
|
||||
|
||||
function pickSite(clientX, clientY) {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const x = clientX - rect.left;
|
||||
const y = clientY - rect.top;
|
||||
let closest = null;
|
||||
let closestDist = Infinity;
|
||||
sites.forEach((site) => {
|
||||
const p = project(site, rect.width, rect.height);
|
||||
const dist = Math.hypot(p.x - x, p.y - y);
|
||||
if (dist < closestDist) {
|
||||
closestDist = dist;
|
||||
closest = site;
|
||||
}
|
||||
});
|
||||
if (closest && closestDist < 40) {
|
||||
activeId = closest.id;
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
draw();
|
||||
const onResize = () => draw();
|
||||
window.addEventListener("resize", onResize);
|
||||
return () => window.removeEventListener("resize", onResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="observation-map">
|
||||
<canvas
|
||||
bind:this={canvas}
|
||||
class="island-canvas"
|
||||
aria-label="Carte schématique des sites d'observation, cliquez sur un point pour voir ses détails"
|
||||
tabindex="0"
|
||||
onclick={(e) => pickSite(e.clientX, e.clientY)}
|
||||
></canvas>
|
||||
|
||||
{#if active}
|
||||
<div class="card map-detail">
|
||||
<h3>{active.name}</h3>
|
||||
<p>{active[`description_${locale}`] ?? active.description_fr}</p>
|
||||
<p style="color: var(--ink-soft); font-size: var(--text-0)">{active.lat}°N, {active.lon}°O — {active.alt} m</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.observation-map {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.island-canvas {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.map-detail h3 {
|
||||
color: var(--gold);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,31 @@
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<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>
|
||||
@@ -0,0 +1,24 @@
|
||||
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");
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
// Import anime.js v3 for Vite
|
||||
import anime from 'animejs/lib/anime.es.js';
|
||||
|
||||
export default anime;
|
||||
-286
@@ -1,286 +0,0 @@
|
||||
import { writable, derived } from 'svelte/store';
|
||||
|
||||
// Store pour la langue actuelle
|
||||
export const currentLanguage = writable('fr');
|
||||
|
||||
// Traductions
|
||||
const translations = {
|
||||
fr: {
|
||||
// Navigation
|
||||
nav_home: 'Accueil',
|
||||
nav_about: 'À Propos',
|
||||
nav_predictions: 'Prédictions',
|
||||
nav_science: 'Science',
|
||||
nav_culture: 'Culture',
|
||||
nav_observatory: 'Observatoire',
|
||||
nav_associations: 'Associations',
|
||||
|
||||
// Hero
|
||||
hero_title1: 'Sirius',
|
||||
hero_title2: 'en Guadeloupe',
|
||||
hero_subtitle: 'Wep Ronpet - L\'Ouverture de l\'Année Cosmique',
|
||||
hero_description: 'Le rendez-vous millénaire entre Ciel, Science et Culture',
|
||||
hero_date: 'Lever héliaque : ',
|
||||
hero_date_value: '22 Juillet 2025',
|
||||
hero_cta_predictions: 'Découvrir les Prédictions',
|
||||
hero_cta_learn: 'En Savoir Plus',
|
||||
|
||||
// About
|
||||
about_title: 'Le Lever Héliaque de Sirius',
|
||||
about_subtitle: 'Un phénomène astronomique millénaire',
|
||||
about_intro: 'Le lever héliaque de Sirius est un événement céleste qui a façonné les civilisations depuis des millénaires.',
|
||||
// Features
|
||||
feature_astronomy_title: 'Astronomie',
|
||||
feature_astronomy_desc: 'Sirius, l\'étoile la plus brillante, guide de navigation céleste',
|
||||
feature_culture_title: 'Culture',
|
||||
feature_culture_desc: 'De l\'Égypte aux Dogons, un héritage universel',
|
||||
feature_spirituality_title: 'Spiritualité',
|
||||
feature_spirituality_desc: 'Renaissance, renouveau et connexion cosmique',
|
||||
|
||||
// Predictions
|
||||
predictions_title: 'Prédictions 2025',
|
||||
predictions_subtitle: 'Calculez la date exacte pour votre lieu d\'observation',
|
||||
predictions_select_location: 'Sélectionnez votre lieu',
|
||||
predictions_results_for: 'Résultats pour',
|
||||
predictions_heliacal_rising: 'Lever héliaque',
|
||||
predictions_best_observation: 'Meilleure observation',
|
||||
predictions_visibility_duration: 'Durée de visibilité',
|
||||
predictions_azimuth: 'Azimut au lever',
|
||||
predictions_max_altitude: 'Altitude maximale',
|
||||
predictions_magnitude: 'Magnitude apparente',
|
||||
|
||||
// Science
|
||||
science_title: 'La Science du Lever Héliaque',
|
||||
science_subtitle: 'Comprendre le phénomène astronomique',
|
||||
|
||||
// Culture
|
||||
culture_title: 'Héritage Culturel',
|
||||
culture_subtitle: 'Sirius à travers les civilisations',
|
||||
culture_timeline_title: 'Chronologie du Savoir',
|
||||
|
||||
// Observatory
|
||||
observatory_title: 'Guide d\'Observation',
|
||||
observatory_subtitle: 'Tout pour observer Sirius en Guadeloupe',
|
||||
|
||||
// Associations
|
||||
associations_title: 'Associations qui fêtent le Wep Ronpet',
|
||||
associations_subtitle: 'Découvrez les événements et communautés qui célèbrent le Nouvel An Kamit',
|
||||
associations_events_title: 'Événements Wep Ronpet 2025',
|
||||
associations_community_title: 'Communauté & Ressources',
|
||||
associations_video_title: 'Découvrir le Wep Ronpet',
|
||||
associations_event1_title: 'Nouvel An Kamite',
|
||||
associations_event1_organizer: 'Afrocentricity International & Famille Muntu • GRATUIT - de 16 ans',
|
||||
associations_event1_desc: 'Temps sacré de passage vers une nouvelle rotation cosmique, vécu en harmonie avec les lois de l\'univers. Renouons avec nos racines spirituelles, honorons nos ancêtres, et réalignons nos vies sur les principes de Maât. Cérémonie et rituels, gastronomie kamite, marché artisanal.',
|
||||
associations_event2_title: 'Nouvel An Kamit 6262',
|
||||
associations_event2_organizer: 'Mouvement Politique Spirituel MUN • Le Lamentin, Martinique',
|
||||
associations_event2_desc: 'Célébration avec la Nouvelle Lune au KAY NZINGHA. Cérémonie, rituels, conférences, dîner kamit, soirée dansante pour la puissance et la confiance du pays. De 15h à minuit.',
|
||||
associations_link1_title: '14ème Membre',
|
||||
associations_link1_desc: 'Site web - Événements',
|
||||
associations_link2_title: 'Mouvement Politique Spirituel MUN',
|
||||
associations_link2_desc: 'Page Facebook officielle',
|
||||
|
||||
// Footer
|
||||
footer_copyright: 'Projet réalisé avec ❤️ par',
|
||||
footer_and: 'et'
|
||||
},
|
||||
|
||||
en: {
|
||||
// Navigation
|
||||
nav_home: 'Home',
|
||||
nav_about: 'About',
|
||||
nav_predictions: 'Predictions',
|
||||
nav_science: 'Science',
|
||||
nav_culture: 'Culture',
|
||||
nav_observatory: 'Observatory',
|
||||
nav_associations: 'Associations',
|
||||
|
||||
// Hero
|
||||
hero_title1: 'Sirius',
|
||||
hero_title2: 'in Guadeloupe',
|
||||
hero_subtitle: 'Wep Ronpet - The Opening of the Cosmic Year',
|
||||
hero_description: 'The millennial meeting between Sky, Science and Culture',
|
||||
hero_date: 'Heliacal rising: ',
|
||||
hero_date_value: 'July 22, 2025',
|
||||
hero_cta_predictions: 'Discover Predictions',
|
||||
hero_cta_learn: 'Learn More',
|
||||
|
||||
// About about_title: 'The Heliacal Rising of Sirius',
|
||||
about_subtitle: 'A millennial astronomical phenomenon',
|
||||
about_intro: 'The heliacal rising of Sirius is a celestial event that has shaped civilizations for millennia.',
|
||||
|
||||
// Features
|
||||
feature_astronomy_title: 'Astronomy',
|
||||
feature_astronomy_desc: 'Sirius, the brightest star, celestial navigation guide',
|
||||
feature_culture_title: 'Culture',
|
||||
feature_culture_desc: 'From Egypt to the Dogon, a universal heritage',
|
||||
feature_spirituality_title: 'Spirituality',
|
||||
feature_spirituality_desc: 'Renaissance, renewal and cosmic connection',
|
||||
|
||||
// Predictions
|
||||
predictions_title: 'Predictions 2025',
|
||||
predictions_subtitle: 'Calculate the exact date for your observation location',
|
||||
predictions_select_location: 'Select your location',
|
||||
predictions_results_for: 'Results for',
|
||||
predictions_heliacal_rising: 'Heliacal rising',
|
||||
predictions_best_observation: 'Best observation',
|
||||
predictions_visibility_duration: 'Visibility duration',
|
||||
predictions_azimuth: 'Azimuth at rising',
|
||||
predictions_max_altitude: 'Maximum altitude',
|
||||
predictions_magnitude: 'Apparent magnitude',
|
||||
|
||||
// Science
|
||||
science_title: 'The Science of Heliacal Rising',
|
||||
science_subtitle: 'Understanding the astronomical phenomenon',
|
||||
|
||||
// Culture
|
||||
culture_title: 'Cultural Heritage',
|
||||
culture_subtitle: 'Sirius through civilizations',
|
||||
culture_timeline_title: 'Timeline of Knowledge',
|
||||
|
||||
// Observatory
|
||||
observatory_title: 'Observation Guide',
|
||||
observatory_subtitle: 'Everything to observe Sirius in Guadeloupe',
|
||||
|
||||
// Associations
|
||||
associations_title: 'Associations celebrating Wep Ronpet',
|
||||
associations_subtitle: 'Discover events and communities celebrating the Kamit New Year',
|
||||
associations_events_title: 'Wep Ronpet 2025 Events',
|
||||
associations_community_title: 'Community & Resources',
|
||||
associations_video_title: 'Discover Wep Ronpet',
|
||||
associations_event1_title: 'Kamite New Year',
|
||||
associations_event1_organizer: 'Afrocentricity International & Famille Muntu • FREE under 16',
|
||||
associations_event1_desc: 'Sacred time of passage to a new cosmic rotation, lived in harmony with the laws of the universe. Let us reconnect with our spiritual roots, honor our ancestors, and realign our lives with the principles of Maat. Ceremony and rituals, Kamite gastronomy, artisan market.',
|
||||
associations_event2_title: 'Kamit New Year 6262',
|
||||
associations_event2_organizer: 'Mouvement Politique Spirituel MUN • Le Lamentin, Martinique',
|
||||
associations_event2_desc: 'Celebration with the New Moon at KAY NZINGHA. Ceremony, rituals, conferences, Kamit dinner, dance evening for the power and confidence of the country. From 3pm to midnight.',
|
||||
associations_link1_title: '14th Member',
|
||||
associations_link1_desc: 'Website - Events',
|
||||
associations_link2_title: 'Mouvement Politique Spirituel MUN',
|
||||
associations_link2_desc: 'Official Facebook page',
|
||||
|
||||
// Footer
|
||||
footer_copyright: 'Project made with ❤️ by',
|
||||
footer_and: 'and'
|
||||
},
|
||||
|
||||
ht: {
|
||||
// Navigation
|
||||
nav_home: 'Akèy',
|
||||
nav_about: 'Konsènan',
|
||||
nav_predictions: 'Prediksyon',
|
||||
nav_science: 'Syans',
|
||||
nav_culture: 'Kilti',
|
||||
nav_observatory: 'Obsèvatwa',
|
||||
nav_associations: 'Asosyasyon',
|
||||
|
||||
// Hero
|
||||
hero_title1: 'Sirius',
|
||||
hero_title2: 'nan Gwadloup',
|
||||
hero_subtitle: 'Wep Ronpet - Ouvèti Ane Kosmik la',
|
||||
hero_description: 'Randevou milenè ant Syèl, Syans ak Kilti',
|
||||
hero_date: 'Leve elyak: ',
|
||||
hero_date_value: '22 Jiyè 2025',
|
||||
hero_cta_predictions: 'Dekouvri Prediksyon yo', hero_cta_learn: 'Aprann Plis',
|
||||
|
||||
// About
|
||||
about_title: 'Leve Elyak Sirius la',
|
||||
about_subtitle: 'Yon fenomèn astwonomik milenè',
|
||||
about_intro: 'Leve elyak Sirius se yon evènman selès ki fòme sivilizasyon yo depi plizyè milye ane.',
|
||||
|
||||
// Features
|
||||
feature_astronomy_title: 'Astwonomi',
|
||||
feature_astronomy_desc: 'Sirius, zetwal ki pi klere a, gid navigasyon selès',
|
||||
feature_culture_title: 'Kilti',
|
||||
feature_culture_desc: 'Depi Lejip rive Dogon yo, yon eritaj inivèsèl',
|
||||
feature_spirituality_title: 'Espiritwalite',
|
||||
feature_spirituality_desc: 'Renesans, renouvèlman ak koneksyon kosmik',
|
||||
|
||||
// Predictions
|
||||
predictions_title: 'Prediksyon 2025',
|
||||
predictions_subtitle: 'Kalkile dat egzak pou kote w ap obsève a',
|
||||
predictions_select_location: 'Chwazi kote ou ye a',
|
||||
predictions_results_for: 'Rezilta pou',
|
||||
predictions_heliacal_rising: 'Leve elyak',
|
||||
predictions_best_observation: 'Pi bon obsèvasyon',
|
||||
predictions_visibility_duration: 'Dire vizibilite',
|
||||
predictions_azimuth: 'Azimit nan leve',
|
||||
predictions_max_altitude: 'Altitid maksimòm',
|
||||
predictions_magnitude: 'Mayitid aparan',
|
||||
|
||||
// Science
|
||||
science_title: 'Syans Leve Elyak la',
|
||||
science_subtitle: 'Konprann fenomèn astwonomik la',
|
||||
|
||||
// Culture
|
||||
culture_title: 'Eritaj Kiltirèl',
|
||||
culture_subtitle: 'Sirius atravè sivilizasyon yo',
|
||||
culture_timeline_title: 'Kwonoloji Konesans lan',
|
||||
|
||||
// Observatory
|
||||
observatory_title: 'Gid Obsèvasyon',
|
||||
observatory_subtitle: 'Tout bagay pou obsève Sirius nan Gwadloup',
|
||||
|
||||
// Associations
|
||||
associations_title: 'Asosyasyon ki fète Wep Ronpet',
|
||||
associations_subtitle: 'Dekouvri evènman ak kominote ki selebre Nouvel An Kamit la',
|
||||
associations_events_title: 'Evènman Wep Ronpet 2025',
|
||||
associations_community_title: 'Kominote & Resous',
|
||||
associations_video_title: 'Dekouvri Wep Ronpet',
|
||||
associations_event1_title: 'Nouvel An Kamite',
|
||||
associations_event1_organizer: 'Afrocentricity International & Famille Muntu • GRATIS - nan 16 an',
|
||||
associations_event1_desc: 'Tan sakre pasaj nan yon nouvo wotasyon kosmik, yo viv nan amoni ak lwa inivè yo. Ann rekonekte ak rasin espirituyèl nou yo, onore zansèt nou yo, ak realinye lavi nou yo ak prensip Maât yo. Seremoni ak rituyèl, gastromi kamite, mache atizan.',
|
||||
associations_event2_title: 'Nouvel An Kamit 6262',
|
||||
associations_event2_organizer: 'Mouvement Politique Spirituel MUN • Le Lamentin, Matinik',
|
||||
associations_event2_desc: 'Selebrasyon ak Nouvo Lalin nan nan KAY NZINGHA. Seremoni, rituyèl, konferans, dine kamit, sware danse pou pouvwa ak konfyans peyi a. Depi 3è nan midi.',
|
||||
associations_link1_title: '14yèm Manm',
|
||||
associations_link1_desc: 'Sit entènèt - Evènman',
|
||||
associations_link2_title: 'Mouvement Politique Spirituel MUN',
|
||||
associations_link2_desc: 'Paj Facebook ofisyèl',
|
||||
|
||||
// Footer
|
||||
footer_copyright: 'Pwojè realize avèk ❤️ pa',
|
||||
footer_and: 'ak'
|
||||
}
|
||||
};
|
||||
|
||||
// Store dérivé pour obtenir les traductions de la langue actuelle
|
||||
export const t = derived(
|
||||
currentLanguage,
|
||||
$currentLanguage => key => {
|
||||
return translations[$currentLanguage]?.[key] || translations['fr'][key] || key;
|
||||
}
|
||||
);
|
||||
|
||||
// Fonction pour changer de langue
|
||||
export function setLanguage(lang) {
|
||||
if (translations[lang]) {
|
||||
currentLanguage.set(lang);
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('preferred-language', lang);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Initialiser avec la langue sauvegardée ou détecter la langue du navigateur
|
||||
export function initLanguage() {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
const saved = localStorage.getItem('preferred-language');
|
||||
if (saved && translations[saved]) {
|
||||
currentLanguage.set(saved);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
const browserLang = navigator.language.slice(0, 2);
|
||||
if (translations[browserLang]) {
|
||||
currentLanguage.set(browserLang);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Liste des langues disponibles
|
||||
export const availableLanguages = [
|
||||
{ code: 'fr', name: 'Français', flag: '🇫🇷' },
|
||||
{ code: 'en', name: 'English', flag: '🇬🇧' },
|
||||
{ code: 'ht', name: 'Kreyòl Ayisyen', flag: '🇭🇹' }
|
||||
];
|
||||
@@ -1,8 +0,0 @@
|
||||
import './app.css'
|
||||
import App from './App.svelte'
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById('app'),
|
||||
})
|
||||
|
||||
export default app
|
||||
@@ -0,0 +1,180 @@
|
||||
---
|
||||
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-soft)">{{ 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="card 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="card 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="card 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="card 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,114 @@
|
||||
---
|
||||
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="card info-card">
|
||||
<h3>{{ "science_factor_magnitude_title" | t(locale) }}</h3>
|
||||
<p>{{ "science_factor_magnitude_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="card info-card">
|
||||
<h3>{{ "science_factor_latitude_title" | t(locale) }}</h3>
|
||||
<p>{{ "science_factor_latitude_desc" | t(locale) }}</p>
|
||||
</div>
|
||||
<div class="card 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>
|
||||
@@ -0,0 +1,18 @@
|
||||
@layer reset, tokens, base, layouts, components, utilities;
|
||||
|
||||
@import "./reset.css" layer(reset);
|
||||
@import "./tokens.css" layer(tokens);
|
||||
@import "./themes/oki.css" layer(tokens);
|
||||
@import "./base.css" layer(base);
|
||||
@import "./layouts.css" layer(layouts);
|
||||
@import "./patterns.css" layer(components);
|
||||
@import "./components/nav.css" layer(components);
|
||||
@import "./components/footer.css" layer(components);
|
||||
@import "./components/buttons.css" layer(components);
|
||||
@import "./components/hero.css" layer(components);
|
||||
@import "./components/cards.css" layer(components);
|
||||
@import "./components/tabs.css" layer(components);
|
||||
@import "./components/timeline.css" layer(components);
|
||||
@import "./components/diagrams.css" layer(components);
|
||||
@import "./components/associations.css" layer(components);
|
||||
@import "./components/islands.css" layer(components);
|
||||
@@ -0,0 +1,87 @@
|
||||
@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;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--surface);
|
||||
color: var(--ink);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-1);
|
||||
line-height: var(--leading-normal);
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
font-family: var(--font-display);
|
||||
line-height: var(--leading-tight);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
h1 { font-size: var(--text-6); }
|
||||
h2 { font-size: var(--text-5); }
|
||||
h3 { font-size: var(--text-3); }
|
||||
h4 { font-size: var(--text-2); }
|
||||
|
||||
p {
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
a {
|
||||
text-underline-offset: 0.2em;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 3px solid var(--focus-ring);
|
||||
outline-offset: 3px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
top: var(--space-2);
|
||||
left: var(--space-2);
|
||||
z-index: 100;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: var(--surface-raised);
|
||||
color: var(--ink);
|
||||
border-radius: var(--radius-sm);
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-150%);
|
||||
transition: transform var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.skip-link:focus-visible {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
.event-link,
|
||||
.community-link {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
align-items: flex-start;
|
||||
padding-block: var(--space-3);
|
||||
border-bottom: 1px solid var(--border);
|
||||
text-decoration: none;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.event-link:last-child,
|
||||
.community-link:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.event-icon,
|
||||
.community-icon {
|
||||
font-size: var(--text-3);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.event-info,
|
||||
.community-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
|
||||
.event-title,
|
||||
.community-title {
|
||||
font-weight: 700;
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
.event-platform {
|
||||
font-size: var(--text-0);
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.video-frame {
|
||||
position: relative;
|
||||
aspect-ratio: 16 / 9;
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.video-frame iframe {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.associations-note {
|
||||
font-size: var(--text-0);
|
||||
color: var(--ink-soft);
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: var(--tap-target);
|
||||
padding-inline: var(--space-4);
|
||||
border-radius: var(--radius-pill);
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
transition: transform var(--duration-fast) var(--ease-out),
|
||||
background var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--gold-surface);
|
||||
color: oklch(20% 0.03 265);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
border: 2px solid var(--gold);
|
||||
color: var(--gold);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
.feature-card,
|
||||
.info-card {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.feature-card h3,
|
||||
.info-card h3 {
|
||||
color: var(--gold);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.info-card.essential {
|
||||
border-color: var(--gold);
|
||||
background: light-dark(oklch(96% 0.04 85), oklch(24% 0.04 85));
|
||||
}
|
||||
|
||||
.info-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: var(--space-2);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding-inline: var(--space-2);
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--gold-surface);
|
||||
color: oklch(20% 0.03 265);
|
||||
font-size: var(--text-0);
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
.diagram-orbit {
|
||||
position: relative;
|
||||
height: 16rem;
|
||||
margin-block: var(--space-4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.diagram-orbit .ring {
|
||||
position: absolute;
|
||||
width: 12rem;
|
||||
height: 12rem;
|
||||
border: 2px dashed var(--border);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.diagram-orbit .body {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
font-size: var(--text-4);
|
||||
}
|
||||
|
||||
.diagram-orbit .body span {
|
||||
font-size: var(--text-0);
|
||||
font-weight: 700;
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
.diagram-orbit .sun { top: 50%; left: 50%; transform: translate(-50%, -50%); }
|
||||
.diagram-orbit .earth { top: 15%; left: 15%; }
|
||||
.diagram-orbit .sirius { top: 15%; right: 15%; }
|
||||
|
||||
.diagram-arcus {
|
||||
position: relative;
|
||||
height: 12rem;
|
||||
margin-block: var(--space-4);
|
||||
border-bottom: 2px solid var(--gold);
|
||||
}
|
||||
|
||||
.diagram-arcus .horizon-label {
|
||||
position: absolute;
|
||||
bottom: -1.6rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: var(--gold);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.diagram-arcus .angle {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 30%;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
border: 2px solid var(--caribbean-teal);
|
||||
border-bottom: none;
|
||||
border-left: none;
|
||||
border-radius: 0 100% 0 0;
|
||||
}
|
||||
|
||||
.diagram-arcus .angle-label {
|
||||
position: absolute;
|
||||
bottom: 4.2rem;
|
||||
left: calc(30% + 4.2rem);
|
||||
color: var(--caribbean-teal);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.diagram-arcus .sirius-mark,
|
||||
.diagram-arcus .sun-mark {
|
||||
position: absolute;
|
||||
bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.diagram-arcus .sirius-mark { left: 25%; }
|
||||
.diagram-arcus .sun-mark { right: 25%; bottom: -0.6rem; }
|
||||
@@ -0,0 +1,80 @@
|
||||
.site-footer {
|
||||
background: var(--surface-sunk);
|
||||
border-top: 1px solid var(--border);
|
||||
padding-block: var(--space-6) var(--space-4);
|
||||
margin-top: var(--space-7);
|
||||
}
|
||||
|
||||
.footer-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
|
||||
gap: var(--space-5);
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
|
||||
.footer-section h2 {
|
||||
font-size: var(--text-2);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.footer-section h3 {
|
||||
font-size: var(--text-1);
|
||||
color: var(--gold);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.footer-section ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.footer-section a {
|
||||
text-decoration: none;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.footer-section a:hover {
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
.countdown-item {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
background: var(--surface-raised);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-3);
|
||||
}
|
||||
|
||||
.countdown-value {
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--text-2);
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
.countdown-label {
|
||||
color: var(--ink-soft);
|
||||
font-size: var(--text-0);
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
text-align: center;
|
||||
border-top: 1px solid var(--border);
|
||||
padding-top: var(--space-4);
|
||||
}
|
||||
|
||||
.footer-bottom .pattern-strip {
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.footer-credits a {
|
||||
color: var(--gold);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer-quote {
|
||||
margin-top: var(--space-2);
|
||||
font-style: italic;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
.hero {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding-block: var(--space-7) var(--space-6);
|
||||
background: light-dark(
|
||||
linear-gradient(180deg, oklch(96% 0.02 85), oklch(92% 0.03 60)),
|
||||
linear-gradient(180deg, oklch(14% 0.03 265), oklch(10% 0.02 265))
|
||||
);
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1em;
|
||||
}
|
||||
|
||||
.hero-title .accent {
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--text-3);
|
||||
color: var(--gold);
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.hero-description {
|
||||
font-size: var(--text-2);
|
||||
color: var(--ink-soft);
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.hero-date {
|
||||
margin-top: var(--space-4);
|
||||
font-size: var(--text-1);
|
||||
}
|
||||
|
||||
.hero-date strong {
|
||||
display: block;
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--text-3);
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
justify-content: center;
|
||||
margin-top: var(--space-5);
|
||||
}
|
||||
|
||||
/* Système solaire décoratif, en CSS pure — masqué aux lecteurs d'écran */
|
||||
.hero-orbits {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hero-orbits .sun {
|
||||
position: absolute;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: 50%;
|
||||
background: var(--gold-surface);
|
||||
box-shadow: 0 0 40px 10px var(--gold-surface);
|
||||
}
|
||||
|
||||
.hero-orbits .orbit {
|
||||
position: absolute;
|
||||
border: 1px solid light-dark(oklch(50% 0.05 85 / 30%), oklch(80% 0.05 85 / 20%));
|
||||
border-radius: 50%;
|
||||
animation: orbit-spin linear infinite;
|
||||
}
|
||||
|
||||
.hero-orbits .orbit span {
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
left: 50%;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--caribbean-teal);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.hero-orbits .orbit.sirius span {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
background: var(--gold-surface);
|
||||
box-shadow: 0 0 12px var(--gold-surface);
|
||||
}
|
||||
|
||||
@keyframes orbit-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@media (max-width: 47.99em) {
|
||||
.hero-orbits { opacity: 0.25; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/* Îlots Svelte : le contenu statique (.island-fallback) est toujours présent
|
||||
dans le HTML ; s'il s'hydrate, le script de montage ajoute la classe
|
||||
.is-enhanced sur le conteneur [data-island], qui masque alors le repli. */
|
||||
.island-app:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-island].is-enhanced .island-fallback {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.island-canvas {
|
||||
width: 100%;
|
||||
height: 22rem;
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--surface-sunk);
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
.nav {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
background: light-dark(oklch(96% 0.015 85 / 90%), oklch(16% 0.03 265 / 88%));
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.nav-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
min-height: var(--tap-target);
|
||||
padding-block: var(--space-2);
|
||||
}
|
||||
|
||||
.nav-brand {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 600;
|
||||
font-size: var(--text-2);
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nav-brand span {
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
.nav-disclosure {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.nav-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--tap-target);
|
||||
height: var(--tap-target);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.nav-toggle::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-toggle-icon,
|
||||
.nav-toggle-icon::before,
|
||||
.nav-toggle-icon::after {
|
||||
display: block;
|
||||
width: 22px;
|
||||
height: 2px;
|
||||
background: var(--ink);
|
||||
border-radius: var(--radius-pill);
|
||||
position: relative;
|
||||
transition: transform var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.nav-toggle-icon::before,
|
||||
.nav-toggle-icon::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.nav-toggle-icon::before { top: -7px; }
|
||||
.nav-toggle-icon::after { top: 7px; }
|
||||
|
||||
.nav-disclosure[open] .nav-toggle-icon {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.nav-disclosure[open] .nav-toggle-icon::before {
|
||||
transform: translateY(7px) rotate(45deg);
|
||||
}
|
||||
|
||||
.nav-disclosure[open] .nav-toggle-icon::after {
|
||||
transform: translateY(-7px) rotate(-45deg);
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
position: absolute;
|
||||
inset-inline: 0;
|
||||
top: 100%;
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: var(--space-2) var(--space-4) var(--space-4);
|
||||
}
|
||||
|
||||
.nav-menu a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: var(--tap-target);
|
||||
text-decoration: none;
|
||||
color: var(--ink);
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-menu a[aria-current="page"] {
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
.nav-lang {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--text-0);
|
||||
}
|
||||
|
||||
.nav-lang a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: var(--tap-target);
|
||||
padding-inline: var(--space-2);
|
||||
border-radius: var(--radius-pill);
|
||||
text-decoration: none;
|
||||
color: var(--ink-soft);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-lang a.active {
|
||||
background: var(--surface-sunk);
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
@media (min-width: 60em) {
|
||||
.nav-toggle { display: none; }
|
||||
|
||||
.nav-menu {
|
||||
position: static;
|
||||
flex-direction: row;
|
||||
gap: var(--space-4);
|
||||
border-bottom: none;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.nav-menu a {
|
||||
min-height: auto;
|
||||
border-bottom: none;
|
||||
padding-block: var(--space-1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/* Onglets 100% CSS (radio + label), sans JavaScript.
|
||||
Structure attendue, tous enfants directs de fieldset.tabs, dans cet ordre :
|
||||
input.tab-input (un par onglet, id unique)
|
||||
.tab-list > label[for=...] (un par onglet)
|
||||
.tab-panels > [data-panel="..."] (un par onglet)
|
||||
Chaque input:checked révèle le panneau dont data-panel correspond à son id
|
||||
et met en avant le label associé — déclaré une fois par page appelante
|
||||
via un attribut [data-tab-id] passé en contexte (voir pages). */
|
||||
|
||||
.tabs {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tab-input {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
}
|
||||
|
||||
.tab-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: var(--tap-target);
|
||||
padding-inline: var(--space-3);
|
||||
border-radius: var(--radius-pill);
|
||||
border: 2px solid var(--border);
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tab-panel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Sélection par position (1er input coché -> 1er panneau/label), générique :
|
||||
fonctionne quels que soient les id choisis, tant que l'ordre DOM est
|
||||
input*, puis .tab-list, puis .tab-panels. */
|
||||
.tab-input:nth-of-type(1):checked ~ .tab-panels .tab-panel:nth-of-type(1),
|
||||
.tab-input:nth-of-type(2):checked ~ .tab-panels .tab-panel:nth-of-type(2),
|
||||
.tab-input:nth-of-type(3):checked ~ .tab-panels .tab-panel:nth-of-type(3),
|
||||
.tab-input:nth-of-type(4):checked ~ .tab-panels .tab-panel:nth-of-type(4) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tab-input:nth-of-type(1):checked ~ .tab-list .tab-label:nth-of-type(1),
|
||||
.tab-input:nth-of-type(2):checked ~ .tab-list .tab-label:nth-of-type(2),
|
||||
.tab-input:nth-of-type(3):checked ~ .tab-list .tab-label:nth-of-type(3),
|
||||
.tab-input:nth-of-type(4):checked ~ .tab-list .tab-label:nth-of-type(4) {
|
||||
background: var(--gold-surface);
|
||||
border-color: var(--gold-surface);
|
||||
color: oklch(20% 0.03 265);
|
||||
}
|
||||
|
||||
@media (max-width: 47.99em) {
|
||||
.tab-list {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/* Timeline verticale (Culture, Dogon, héritage diaspora) */
|
||||
.timeline-v {
|
||||
position: relative;
|
||||
max-width: var(--measure);
|
||||
margin-inline: auto;
|
||||
padding-left: var(--space-5);
|
||||
}
|
||||
|
||||
.timeline-v::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0.4rem;
|
||||
top: 0.4rem;
|
||||
bottom: 0.4rem;
|
||||
width: 2px;
|
||||
background: var(--gold-surface);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.timeline-v-item {
|
||||
position: relative;
|
||||
padding-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.timeline-v-item::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -1.65rem;
|
||||
top: 0.3rem;
|
||||
width: 0.8rem;
|
||||
height: 0.8rem;
|
||||
border-radius: 50%;
|
||||
background: var(--gold-surface);
|
||||
}
|
||||
|
||||
.timeline-v-date {
|
||||
font-weight: 700;
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
/* Timeline horizontale à 4 étapes (Observer) */
|
||||
.timeline-h {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: var(--space-3);
|
||||
counter-reset: step;
|
||||
}
|
||||
|
||||
.timeline-h-step {
|
||||
text-align: center;
|
||||
padding-top: var(--space-3);
|
||||
border-top: 4px solid var(--gold-surface);
|
||||
}
|
||||
|
||||
.timeline-h-icon {
|
||||
font-size: var(--text-4);
|
||||
}
|
||||
|
||||
.timeline-h-time {
|
||||
font-weight: 700;
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
@media (max-width: 47.99em) {
|
||||
.timeline-h {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/* Primitives de composition — inspirées de Every Layout (every-layout.dev) */
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 75rem;
|
||||
margin-inline: auto;
|
||||
padding-inline: var(--space-4);
|
||||
}
|
||||
|
||||
.prose {
|
||||
max-width: var(--measure);
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stack > * + * {
|
||||
margin-block-start: var(--stack-space, var(--space-4));
|
||||
}
|
||||
|
||||
.cluster {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--cluster-space, var(--space-3));
|
||||
}
|
||||
|
||||
.center {
|
||||
max-width: var(--center-measure, 65ch);
|
||||
margin-inline: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.sidebar > :last-child {
|
||||
flex-basis: 0;
|
||||
flex-grow: 999;
|
||||
min-width: 60%;
|
||||
}
|
||||
|
||||
.grid-auto {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--grid-min, 16rem)), 1fr));
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.section {
|
||||
padding-block: var(--space-7);
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.section-eyebrow {
|
||||
display: inline-block;
|
||||
font-family: var(--font-body);
|
||||
font-weight: 700;
|
||||
font-size: var(--text-0);
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--gold);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
color: var(--ink-soft);
|
||||
font-size: var(--text-2);
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--surface-raised);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-4);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/* Motifs panafricains — utilisés en accents ponctuels (un par section, effet
|
||||
Von Restorff), jamais en texture de fond généralisée. */
|
||||
|
||||
.pattern-strip {
|
||||
display: block;
|
||||
height: 0.5rem;
|
||||
width: 100%;
|
||||
max-width: 8rem;
|
||||
border-radius: var(--radius-pill);
|
||||
margin-block: var(--space-3);
|
||||
}
|
||||
|
||||
.pattern-strip.kente {
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
var(--gold-surface) 0 10px,
|
||||
var(--kente-red) 10px 20px
|
||||
);
|
||||
}
|
||||
|
||||
.pattern-strip.bogolan {
|
||||
background-image: radial-gradient(circle, var(--gold-surface) 30%, transparent 31%);
|
||||
background-size: 12px 12px;
|
||||
background-color: var(--bogolan-green);
|
||||
}
|
||||
|
||||
.pattern-strip.adinkra {
|
||||
background: repeating-conic-gradient(
|
||||
var(--gold-surface) 0deg 90deg,
|
||||
var(--caribbean-teal) 90deg 180deg
|
||||
);
|
||||
background-size: 14px 14px;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
hanging-punctuation: first last;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100svh;
|
||||
}
|
||||
|
||||
img, picture, svg, canvas {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
input, button, textarea, select {
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
ul[class], ol[class] {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/* Thème OKI — identité par défaut de Gwada-Sirius.
|
||||
Un fichier de thème ne redéfinit que des tokens (jamais de sélecteurs de
|
||||
composants), pour rester échangeable avec d'autres identités visuelles OKI. */
|
||||
[data-theme="oki"] {
|
||||
--brand-name: "Sirius Gwada";
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
|
||||
/* === Couleur — oklch, contraste d'abord (méthode modus-vivendi) ===
|
||||
Deux thèmes conceptuellement liés au sujet : Aube (clair) / Nuit (sombre). */
|
||||
--ink: light-dark(oklch(23% 0.03 265), oklch(95% 0.015 85));
|
||||
--ink-soft: light-dark(oklch(38% 0.03 265), oklch(80% 0.02 85));
|
||||
--surface: light-dark(oklch(96% 0.015 85), oklch(16% 0.03 265));
|
||||
--surface-raised: light-dark(oklch(99% 0.008 85), oklch(21% 0.03 265));
|
||||
--surface-sunk: light-dark(oklch(91% 0.02 85), oklch(12% 0.025 265));
|
||||
--border: light-dark(oklch(85% 0.02 85), oklch(32% 0.035 265));
|
||||
|
||||
/* Or de Sirius : variante lisible (texte/liens) + variante décorative (glows) */
|
||||
--gold: light-dark(oklch(45% 0.15 75), oklch(82% 0.15 87));
|
||||
--gold-surface: oklch(80% 0.16 85);
|
||||
--kente-red: light-dark(oklch(46% 0.18 25), oklch(70% 0.19 25));
|
||||
--bogolan-green: light-dark(oklch(40% 0.09 145), oklch(68% 0.12 145));
|
||||
--caribbean-teal: light-dark(oklch(43% 0.09 195), oklch(74% 0.11 195));
|
||||
|
||||
--focus-ring: var(--caribbean-teal);
|
||||
|
||||
/* === Typographie === */
|
||||
--font-display: "Fraunces Variable", "Fraunces", ui-serif, Georgia, serif;
|
||||
--font-body: "Atkinson Hyperlegible", ui-sans-serif, system-ui, sans-serif;
|
||||
|
||||
--text-0: clamp(0.8rem, 0.78rem + 0.1vw, 0.875rem);
|
||||
--text-1: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
|
||||
--text-2: clamp(1.125rem, 1.05rem + 0.4vw, 1.375rem);
|
||||
--text-3: clamp(1.375rem, 1.2rem + 0.75vw, 1.75rem);
|
||||
--text-4: clamp(1.75rem, 1.4rem + 1.5vw, 2.5rem);
|
||||
--text-5: clamp(2.25rem, 1.7rem + 2.5vw, 3.5rem);
|
||||
--text-6: clamp(2.75rem, 1.8rem + 4.5vw, 5.5rem);
|
||||
|
||||
--leading-tight: 1.15;
|
||||
--leading-normal: 1.6;
|
||||
--measure: 65ch;
|
||||
|
||||
/* === Espacement (échelle modulaire) === */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 1rem;
|
||||
--space-4: 1.5rem;
|
||||
--space-5: 2.5rem;
|
||||
--space-6: 4rem;
|
||||
--space-7: 6rem;
|
||||
|
||||
/* === Formes, ombres, mouvement === */
|
||||
--radius-sm: 0.5rem;
|
||||
--radius-md: 1rem;
|
||||
--radius-lg: 1.5rem;
|
||||
--radius-pill: 999px;
|
||||
|
||||
--shadow-sm: 0 1px 3px light-dark(oklch(23% 0.03 265 / 12%), oklch(0% 0 0 / 30%));
|
||||
--shadow-md: 0 8px 30px light-dark(oklch(23% 0.03 265 / 12%), oklch(0% 0 0 / 45%));
|
||||
|
||||
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--duration-fast: 200ms;
|
||||
--duration-base: 400ms;
|
||||
--duration-slow: 700ms;
|
||||
|
||||
/* Fitts : cible tactile minimale */
|
||||
--tap-target: 48px;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user