diff --git a/p/assets/img/hero/gnome.png b/p/assets/img/hero/gnome.png
new file mode 100644
index 0000000..e81bbf4
Binary files /dev/null and b/p/assets/img/hero/gnome.png differ
diff --git a/p/assets/img/hero/install.png b/p/assets/img/hero/install.png
new file mode 100644
index 0000000..b2ee28f
Binary files /dev/null and b/p/assets/img/hero/install.png differ
diff --git a/p/assets/img/hero/xfce.png b/p/assets/img/hero/xfce.png
new file mode 100644
index 0000000..bd6bb09
Binary files /dev/null and b/p/assets/img/hero/xfce.png differ
diff --git a/p/assets/js/hero-carousel.js b/p/assets/js/hero-carousel.js
new file mode 100644
index 0000000..fd64fb5
--- /dev/null
+++ b/p/assets/js/hero-carousel.js
@@ -0,0 +1,155 @@
+(() => {
+ "use strict";
+
+ const existingCleanup = window.__heroCarouselCleanup;
+ if (typeof existingCleanup === "function") {
+ existingCleanup();
+ }
+
+ const hero = document.querySelector(".hero-carousel");
+ if (!hero) {
+ return;
+ }
+
+ const slides = Array.from(hero.querySelectorAll("[data-hero-slide]"));
+ const prevButton = hero.querySelector("[data-hero-prev]");
+ const nextButton = hero.querySelector("[data-hero-next]");
+ const dotsContainer = hero.querySelector("[data-hero-dots]");
+
+ if (!slides.length || !prevButton || !nextButton || !dotsContainer) {
+ return;
+ }
+
+ let currentIndex = Math.max(
+ 0,
+ slides.findIndex((slide) => slide.classList.contains("is-active"))
+ );
+ let autoRotateTimer = null;
+ const rotateDelay = 5500;
+ const abortController = new AbortController();
+ const { signal } = abortController;
+
+ const dots = slides.map((_, index) => {
+ const dot = document.createElement("button");
+ dot.type = "button";
+ dot.className = "hero-dot";
+ dot.setAttribute("role", "tab");
+ dot.setAttribute("aria-label", `Aller a l'image ${index + 1}`);
+ dot.dataset.index = index.toString();
+ dotsContainer.appendChild(dot);
+ return dot;
+ });
+
+ function updateUI() {
+ slides.forEach((slide, index) => {
+ const isActive = index === currentIndex;
+ slide.classList.toggle("is-active", isActive);
+ slide.setAttribute("aria-hidden", String(!isActive));
+ });
+
+ dots.forEach((dot, index) => {
+ const isActive = index === currentIndex;
+ dot.classList.toggle("is-active", isActive);
+ dot.setAttribute("aria-selected", String(isActive));
+ dot.tabIndex = isActive ? 0 : -1;
+ });
+ }
+
+ function goTo(index) {
+ if (index < 0) {
+ currentIndex = slides.length - 1;
+ } else if (index >= slides.length) {
+ currentIndex = 0;
+ } else {
+ currentIndex = index;
+ }
+ updateUI();
+ }
+
+ function stopAutoRotate() {
+ if (autoRotateTimer !== null) {
+ window.clearInterval(autoRotateTimer);
+ autoRotateTimer = null;
+ }
+ }
+
+ function startAutoRotate() {
+ stopAutoRotate();
+ autoRotateTimer = window.setInterval(() => {
+ goTo(currentIndex + 1);
+ }, rotateDelay);
+ }
+
+ prevButton.addEventListener(
+ "click",
+ () => {
+ goTo(currentIndex - 1);
+ startAutoRotate();
+ },
+ { signal }
+ );
+
+ nextButton.addEventListener(
+ "click",
+ () => {
+ goTo(currentIndex + 1);
+ startAutoRotate();
+ },
+ { signal }
+ );
+
+ dotsContainer.addEventListener(
+ "click",
+ (event) => {
+ const button = event.target.closest(".hero-dot");
+ if (!button) {
+ return;
+ }
+
+ const targetIndex = Number.parseInt(button.dataset.index || "0", 10);
+ goTo(targetIndex);
+ startAutoRotate();
+ },
+ { signal }
+ );
+
+ hero.addEventListener(
+ "mouseenter",
+ () => {
+ stopAutoRotate();
+ },
+ { signal }
+ );
+
+ hero.addEventListener(
+ "mouseleave",
+ () => {
+ startAutoRotate();
+ },
+ { signal }
+ );
+
+ document.addEventListener(
+ "visibilitychange",
+ () => {
+ if (document.hidden) {
+ stopAutoRotate();
+ } else {
+ startAutoRotate();
+ }
+ },
+ { signal }
+ );
+
+ updateUI();
+ startAutoRotate();
+
+ window.__heroCarouselCleanup = () => {
+ stopAutoRotate();
+ abortController.abort();
+ };
+
+ document.addEventListener("page:before-route-change", window.__heroCarouselCleanup, {
+ once: true,
+ });
+})();
diff --git a/p/assets/main-nav/app.js b/p/assets/main-nav/app.js
index 3173bb4..e94c0aa 100644
--- a/p/assets/main-nav/app.js
+++ b/p/assets/main-nav/app.js
@@ -2,6 +2,7 @@
const main = document.getElementById("app-main");
const footerYear = document.getElementById("footer-year");
const pageStyleSelectors = "[data-page-style='true']";
+ const pageScriptSelectors = "script[data-page-script='true']";
const links = Array.from(document.querySelectorAll("[data-route]"));
@@ -13,6 +14,11 @@
document.querySelectorAll(pageStyleSelectors).forEach((node) => node.remove());
}
+ function clearInjectedPageScripts() {
+ document.dispatchEvent(new CustomEvent("page:before-route-change"));
+ document.querySelectorAll(pageScriptSelectors).forEach((node) => node.remove());
+ }
+
function injectPageStyles(parsedDocument, sourceUrl) {
clearInjectedPageStyles();
@@ -38,6 +44,37 @@
});
}
+ async function injectPageScripts(parsedDocument, sourceUrl) {
+ clearInjectedPageScripts();
+
+ const scriptNodes = Array.from(parsedDocument.querySelectorAll("script"));
+
+ for (const node of scriptNodes) {
+ const scriptElement = document.createElement("script");
+ scriptElement.setAttribute("data-page-script", "true");
+
+ const src = node.getAttribute("src");
+ if (src) {
+ scriptElement.src = new URL(src, sourceUrl).toString();
+ scriptElement.async = false;
+ if (node.defer) {
+ scriptElement.defer = true;
+ }
+
+ await new Promise((resolve, reject) => {
+ scriptElement.onload = () => resolve();
+ scriptElement.onerror = () => reject(new Error(`Impossible de charger le script: ${src}`));
+ document.body.appendChild(scriptElement);
+ });
+
+ continue;
+ }
+
+ scriptElement.textContent = node.textContent;
+ document.body.appendChild(scriptElement);
+ }
+ }
+
function normalizeRoute(value) {
const raw = (value ?? "").toString().trim().toLowerCase();
if (!raw || raw === "/" || raw === "home" || raw === "index" || raw === "accueil") {
@@ -117,6 +154,8 @@
}
async function loadRoute(route) {
+ clearInjectedPageScripts();
+
const source = routeToSource(route);
const response = await fetch(source, { cache: "no-store" });
if (!response.ok) {
@@ -128,6 +167,7 @@
injectPageStyles(parsed, response.url);
const injected = parsed.querySelector("[data-page-content]") ?? parsed.body;
main.innerHTML = injected.innerHTML.trim();
+ await injectPageScripts(parsed, response.url);
}
function render(route, { replaceHistory = false } = {}) {
diff --git a/p/assets/styles/main.css b/p/assets/styles/main.css
index cac8f71..8ec2813 100644
--- a/p/assets/styles/main.css
+++ b/p/assets/styles/main.css
@@ -16,14 +16,51 @@ main[data-page-content] {
.hero {
- background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));
+ position: relative;
+ overflow: hidden;
+ background: #0b1324;
box-shadow: var(--shadow);
border-radius: 1rem;
padding: clamp(2rem, 4.5vw, 3.5rem);
color: var(--text);
+ min-height: clamp(24rem, 56vw, 34rem);
}
-.hero-inner { width: 100%; }
+.hero-carousel-track {
+ position: absolute;
+ inset: 0;
+}
+
+.hero-slide {
+ position: absolute;
+ inset: 0;
+ background-size: cover;
+ background-position: center;
+ opacity: 0;
+ transform: scale(1.02);
+ transition: opacity 700ms ease, transform 1000ms ease;
+}
+
+.hero-slide::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background:
+ linear-gradient(110deg, rgba(2, 6, 23, 0.78) 18%, rgba(2, 6, 23, 0.28) 58%, rgba(2, 6, 23, 0.66) 100%),
+ radial-gradient(circle at 72% 20%, rgba(56, 189, 248, 0.2), transparent 48%);
+}
+
+.hero-slide.is-active {
+ opacity: 1;
+ transform: scale(1);
+}
+
+.hero-inner {
+ width: 100%;
+ max-width: 52rem;
+ position: relative;
+ z-index: 2;
+}
.eyebrow {
color: var(--accent);
@@ -36,22 +73,26 @@ main[data-page-content] {
.hero-title {
font-family: "Georgia", "Palatino Linotype", serif;
font-size: clamp(3rem, 8vw, 5rem);
- line-height: 0.95;
+ line-height: 1;
margin: 0 0 0.25rem;
- color: var(--text);
+ color: #f8fbff;
+ text-shadow: 0 10px 28px rgba(2, 6, 23, 0.38);
}
.hero-subtitle {
font-size: clamp(1rem, 2.2vw, 1.3rem);
- color: var(--muted);
+ color: #e7edf9;
margin: 0 0 1rem;
font-weight: 600;
+ text-shadow: 0 4px 18px rgba(2, 6, 23, 0.35);
}
.lead {
- color: var(--muted);
+ color: #d8e1f3;
max-width: 68ch;
margin: 0 0 1.25rem;
+ text-wrap: pretty;
+ text-shadow: 0 2px 14px rgba(2, 6, 23, 0.34);
}
.hero-actions {
@@ -60,6 +101,62 @@ main[data-page-content] {
flex-wrap: wrap;
}
+.hero-controls {
+ position: absolute;
+ right: clamp(1rem, 2.5vw, 2rem);
+ bottom: clamp(1rem, 2.8vw, 1.75rem);
+ z-index: 3;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.45rem;
+ padding: 0.4rem 0.55rem;
+ border-radius: 999px;
+ backdrop-filter: blur(10px);
+ background: rgba(2, 6, 23, 0.46);
+ border: 1px solid rgba(148, 163, 184, 0.28);
+}
+
+.hero-control {
+ width: 2.2rem;
+ height: 2.2rem;
+ border: 0;
+ border-radius: 999px;
+ display: grid;
+ place-items: center;
+ font-size: 1.32rem;
+ color: #e2e8f0;
+ background: rgba(148, 163, 184, 0.22);
+ cursor: pointer;
+ transition: transform 180ms ease, background 180ms ease;
+}
+
+.hero-control:hover {
+ transform: translateY(-1px);
+ background: rgba(125, 211, 252, 0.36);
+}
+
+.hero-dots {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
+ margin: 0 0.15rem;
+}
+
+.hero-dot {
+ width: 0.6rem;
+ height: 0.6rem;
+ border-radius: 999px;
+ border: 0;
+ background: rgba(226, 232, 240, 0.55);
+ cursor: pointer;
+ transition: transform 180ms ease, background 180ms ease;
+}
+
+.hero-dot.is-active {
+ background: #7dd3fc;
+ transform: scale(1.18);
+}
+
.button {
display: inline-flex;
@@ -178,6 +275,15 @@ main[data-page-content] {
@media (max-width: 980px) {
.hero-title { font-size: clamp(2.2rem, 7vw, 4rem); }
+ .hero {
+ min-height: 27rem;
+ }
+
+ .hero-controls {
+ left: 1rem;
+ right: auto;
+ }
+
.features {
flex-direction: column;
}
@@ -202,4 +308,21 @@ main[data-page-content] {
box-shadow: none;
pointer-events: auto;
}
+}
+
+@media (max-width: 640px) {
+ .hero {
+ min-height: 29rem;
+ padding: 1.5rem;
+ }
+
+ .hero-inner {
+ max-width: 100%;
+ }
+
+ .hero-controls {
+ left: 0.8rem;
+ right: 0.8rem;
+ justify-content: space-between;
+ }
}
\ No newline at end of file
diff --git a/p/index/index.html b/p/index/index.html
index c8ff671..d75f572 100644
--- a/p/index/index.html
+++ b/p/index/index.html
@@ -8,18 +8,28 @@
-
+
+
+
FWS Linux
-
un système léger, libre et compatible
-
-
Open-source basé sur Arch Linux, Fws facilite la transition depuis Windows sans vous déposséder de vos données. Installation simplifiée, interface épurée, et compatibilité avec vos applications Windows grâce à Wine et Winboat — tout en restant respectueux de votre vie privée.
-
+
Un systeme leger, libre et compatible
+
Open-source base sur Arch Linux, FWS facilite la transition depuis Windows sans vous deposseder de vos donnees. Installation simplifiee, interface epuree, et compatibilite avec vos applications Windows grace a Wine et Winboat.
+
+
+
+