diff --git a/README.md b/README.md
index e69de29..12f7e26 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1,14 @@
+Site statique one-page.
+
+Le projet ne dépend pas de Node.js, ni d'un build, ni d'un bundler.
+
+Utilisation:
+
+1. Ouvrir `index.html` dans un navigateur, ou
+2. Le servir avec n'importe quel serveur statique local, comme Five Server.
+
+Navigation:
+
+- `/?p/dl/` charge `p/dl/index.html` dans le `main`
+- Chaque sous-dossier de `p/` peut devenir une route, par exemple `p/contact/index.html` pour `/?p/contact/`
+- Le header et le footer restent communs à toutes les vues
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..da5cbf5
--- /dev/null
+++ b/index.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+ Fws Web
+
+
+
+
+
+
+
+ Ce site a besoin de JavaScript pour charger les pages dans la zone principale.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/p/assets/main-nav/app.js b/p/assets/main-nav/app.js
new file mode 100644
index 0000000..05ed466
--- /dev/null
+++ b/p/assets/main-nav/app.js
@@ -0,0 +1,166 @@
+(() => {
+ const main = document.getElementById("app-main");
+
+ const links = Array.from(document.querySelectorAll("[data-route]"));
+
+ function normalizeRoute(value) {
+ const raw = (value ?? "").toString().trim().toLowerCase();
+ if (!raw || raw === "/" || raw === "home" || raw === "index" || raw === "accueil") {
+ return "home";
+ }
+ return raw.replace(/^\/+|\/+$/g, "") || "home";
+ }
+
+ function routeFromLocation() {
+ const params = new URLSearchParams(window.location.search);
+
+ if (params.has("p")) {
+ return normalizeRoute(params.get("p"));
+ }
+
+ const rawSearch = window.location.search.replace(/^\?/, "");
+ if (rawSearch.startsWith("p/")) {
+ return normalizeRoute(rawSearch.slice(2));
+ }
+
+ if (rawSearch.startsWith("p=")) {
+ return normalizeRoute(rawSearch.slice(2));
+ }
+
+ const path = window.location.pathname.replace(/^\/+|\/+$/g, "");
+ if (path) {
+ const parts = path.split("/");
+ if (parts[0] === "p" && parts[1]) {
+ return normalizeRoute(parts.slice(1).join("/"));
+ }
+ return normalizeRoute(parts[0]);
+ }
+
+ return "home";
+ }
+
+ function setActiveRoute(route) {
+ links.forEach((link) => {
+ const isActive = link.dataset.route === route;
+ if (isActive) {
+ link.setAttribute("aria-current", "page");
+ } else {
+ link.removeAttribute("aria-current");
+ }
+ });
+ }
+
+ function setCanonicalUrl(route, replace = false) {
+ const url = route === "home" ? "/" : `/?p/${route}/`;
+ if (replace) {
+ history.replaceState({ route }, "", url);
+ return;
+ }
+ history.pushState({ route }, "", url);
+ }
+
+ function routeToSource(route) {
+ return route === "home" ? null : `p/${route}/index.html`;
+ }
+
+ function routeToTitle(route) {
+ if (route === "home") {
+ return "Fws Web";
+ }
+
+ const label = route
+ .split("/")
+ .filter(Boolean)
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
+ .join(" / ");
+
+ return `Fws Web - ${label}`;
+ }
+
+ async function loadRoute(route) {
+ if (route === "home") {
+ main.innerHTML = `
+
+ Navigation one-page
+ Le site change de contenu sans changer de page.
+
+ Les liens du menu modifient l'URL, puis le script charge le HTML correspondant depuis
+ p/... et l'injecte dans le main.
+
+
+
+
+
+
+ Header commun
+ Le bandeau du haut reste identique sur chaque vue.
+
+
+ Footer commun
+ Le pied de page reste fixe, quelle que soit la section chargée.
+
+
+ Injection réelle
+ Chaque route peut venir d'un vrai fichier index.html dans p/ .
+
+
+ `;
+ return;
+ }
+
+ const source = routeToSource(route);
+ if (!source) {
+ throw new Error(`Route inconnue: ${route}`);
+ }
+
+ const response = await fetch(source, { cache: "no-store" });
+ if (!response.ok) {
+ throw new Error(`Impossible de charger ${source} (${response.status})`);
+ }
+
+ const html = await response.text();
+ const parsed = new DOMParser().parseFromString(html, "text/html");
+ const injected = parsed.querySelector("[data-page-content]") ?? parsed.body;
+ main.innerHTML = injected.innerHTML.trim();
+ }
+
+ function render(route, { replaceHistory = false } = {}) {
+ const safeRoute = route || "home";
+
+ loadRoute(safeRoute)
+ .then(() => {
+ setActiveRoute(safeRoute);
+ setCanonicalUrl(safeRoute, replaceHistory);
+ document.title = routeToTitle(safeRoute);
+ window.scrollTo({ top: 0, behavior: "smooth" });
+ })
+ .catch((error) => {
+ main.innerHTML = `Erreur de chargement ${error.message}
`;
+ setActiveRoute("home");
+ });
+ }
+
+ document.addEventListener("click", (event) => {
+ const link = event.target.closest("[data-route]");
+ if (!link) {
+ return;
+ }
+
+ const route = normalizeRoute(link.dataset.route);
+ if (!route) {
+ return;
+ }
+
+ event.preventDefault();
+ render(route);
+ });
+
+ window.addEventListener("popstate", () => {
+ render(routeFromLocation(), { replaceHistory: true });
+ });
+
+ render(routeFromLocation(), { replaceHistory: true });
+})();
\ No newline at end of file
diff --git a/p/assets/main-styles/styles.css b/p/assets/main-styles/styles.css
new file mode 100644
index 0000000..3908f9c
--- /dev/null
+++ b/p/assets/main-styles/styles.css
@@ -0,0 +1,289 @@
+:root {
+ color-scheme: dark;
+ --bg: #0b1020;
+ --bg-soft: rgba(11, 16, 32, 0.72);
+ --panel: rgba(17, 25, 49, 0.82);
+ --panel-border: rgba(148, 163, 184, 0.18);
+ --text: #e5eefc;
+ --muted: #9fb0cc;
+ --accent: #7dd3fc;
+ --accent-strong: #38bdf8;
+ --shadow: 0 24px 70px rgba(0, 0, 0, 0.35);
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+body {
+ margin: 0;
+ min-height: 100vh;
+ color: var(--text);
+ font-family: "Trebuchet MS", "Gill Sans", "Segoe UI", sans-serif;
+ background:
+ radial-gradient(circle at top left, rgba(56, 189, 248, 0.24), transparent 28%),
+ radial-gradient(circle at right center, rgba(14, 165, 233, 0.14), transparent 32%),
+ linear-gradient(160deg, #050816 0%, #0b1020 52%, #08101d 100%);
+}
+
+body::before,
+body::after {
+ content: "";
+ position: fixed;
+ inset: auto;
+ border-radius: 999px;
+ pointer-events: none;
+ filter: blur(18px);
+ opacity: 0.6;
+}
+
+body::before {
+ width: 22rem;
+ height: 22rem;
+ top: -6rem;
+ left: -8rem;
+ background: rgba(56, 189, 248, 0.16);
+}
+
+body::after {
+ width: 18rem;
+ height: 18rem;
+ right: -4rem;
+ bottom: 2rem;
+ background: rgba(125, 211, 252, 0.12);
+}
+
+.page-shell {
+ position: relative;
+ z-index: 1;
+ width: min(1120px, calc(100% - 2rem));
+ margin: 0 auto;
+ min-height: 100vh;
+ display: grid;
+ grid-template-rows: auto 1fr auto;
+ gap: 1.25rem;
+ padding: 1rem 0 1.25rem;
+}
+
+.site-header,
+.site-footer,
+.hero,
+.card {
+ backdrop-filter: blur(18px);
+ background: var(--bg-soft);
+ border: 1px solid var(--panel-border);
+ box-shadow: var(--shadow);
+}
+
+.site-header,
+.site-footer {
+ border-radius: 1.5rem;
+}
+
+.site-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ padding: 1rem 1.25rem;
+}
+
+.brand {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.9rem;
+ color: inherit;
+ text-decoration: none;
+}
+
+.brand-mark {
+ display: grid;
+ place-items: center;
+ width: 2.75rem;
+ height: 2.75rem;
+ border-radius: 0.95rem;
+ background: linear-gradient(145deg, rgba(125, 211, 252, 0.95), rgba(56, 189, 248, 0.45));
+ color: #08101d;
+ font-weight: 800;
+}
+
+.brand-text {
+ display: grid;
+ line-height: 1.05;
+}
+
+.brand-text strong,
+.hero h1,
+.card h2 {
+ font-family: "Georgia", "Palatino Linotype", serif;
+}
+
+.brand-text small {
+ color: var(--muted);
+}
+
+.site-nav {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.6rem;
+}
+
+.site-nav a,
+.button {
+ border-radius: 999px;
+ transition:
+ transform 160ms ease,
+ background 160ms ease,
+ color 160ms ease,
+ border-color 160ms ease;
+}
+
+.site-nav a {
+ padding: 0.7rem 1rem;
+ color: var(--muted);
+ text-decoration: none;
+ border: 1px solid transparent;
+}
+
+.site-nav a:hover,
+.site-nav a[aria-current="page"] {
+ color: var(--text);
+ border-color: rgba(125, 211, 252, 0.4);
+ background: rgba(125, 211, 252, 0.08);
+}
+
+.site-main {
+ display: grid;
+ gap: 1.25rem;
+ align-content: start;
+}
+
+.hero {
+ border-radius: 2rem;
+ padding: clamp(1.5rem, 3vw, 2.75rem);
+}
+
+.eyebrow {
+ margin: 0 0 0.75rem;
+ color: var(--accent);
+ text-transform: uppercase;
+ letter-spacing: 0.22em;
+ font-size: 0.78rem;
+ font-weight: 700;
+}
+
+.hero h1 {
+ margin: 0;
+ font-size: clamp(2.2rem, 5vw, 4.5rem);
+ line-height: 0.98;
+ max-width: 12ch;
+}
+
+.lead {
+ max-width: 62ch;
+ color: var(--muted);
+ font-size: 1.08rem;
+ line-height: 1.7;
+ margin: 1rem 0 0;
+}
+
+.lead span,
+.card span {
+ color: var(--text);
+}
+
+.hero-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+ margin-top: 1.5rem;
+}
+
+.button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0.85rem 1.2rem;
+ text-decoration: none;
+ border: 1px solid rgba(148, 163, 184, 0.22);
+ font-weight: 700;
+}
+
+.button:hover,
+.site-nav a:hover {
+ transform: translateY(-1px);
+}
+
+.button-primary {
+ color: #08101d;
+ background: linear-gradient(145deg, var(--accent), var(--accent-strong));
+}
+
+.button-secondary {
+ color: var(--text);
+ background: rgba(255, 255, 255, 0.04);
+}
+
+.grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 1rem;
+}
+
+.card {
+ border-radius: 1.4rem;
+ padding: 1.25rem;
+}
+
+.card h2 {
+ margin-top: 0;
+ margin-bottom: 0.75rem;
+ font-size: 1.45rem;
+}
+
+.card p {
+ margin: 0;
+ color: var(--muted);
+ line-height: 1.65;
+}
+
+.site-footer {
+ padding: 1rem 1.25rem;
+ color: var(--muted);
+}
+
+.site-footer p {
+ margin: 0;
+}
+
+@media (max-width: 900px) {
+ .grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 700px) {
+ .page-shell {
+ width: min(100% - 1rem, 1120px);
+ gap: 0.9rem;
+ padding-top: 0.5rem;
+ }
+
+ .site-header {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .site-nav {
+ justify-content: space-between;
+ }
+
+ .site-nav a,
+ .button {
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/p/dl/index.html b/p/dl/index.html
new file mode 100644
index 0000000..3417dca
--- /dev/null
+++ b/p/dl/index.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+ DL
+
+
+
+
+ Route DL
+ Le contenu de la section DL charge ici.
+
+ Cette page vit dans p/dl/index.html et son contenu est injecté dans le
+ main quand l'URL pointe vers /?p/dl/ .
+
+
+
+
+
+
+ Vrai fichier
+ Le HTML est bien stocké dans le dossier p/ , pas dans le fichier principal.
+
+
+ Injection
+ Le script lit ce fichier puis injecte uniquement le bloc portant data-page-content .
+
+
+ Extensible
+ Tu peux créer d'autres dossiers comme p/contact/ ou p/about/ .
+
+
+
+
+
\ No newline at end of file