diff --git a/src/app/globals.css b/src/app/globals.css
index 83ca72f..63f3cb9 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -47,8 +47,31 @@ body[data-theme="guyane"] {
radial-gradient(ellipse at bottom, rgba(196, 100, 52, 0.04) 0%, transparent 60%);
}
+/* === Theme Aquarelle (plugin theme-aquarelle) === */
+/* Direction artistique « carnet naturaliste XIXᵉ ». Mutuellement exclusif
+ avec theme-guyane (le hook onEnable du plugin garantit qu'un seul est
+ actif à la fois). */
+body[data-theme="aquarelle"] {
+ --background: #faf5e9; /* papier crème teinté */
+ --foreground: #2a2418; /* encre sépia foncée */
+ font-family: var(--font-serif), Georgia, serif;
+ /* Texture grain de papier subtile via SVG inline (~1.5 KB, pas de fetch). */
+ background-image:
+ radial-gradient(ellipse at 25% 15%, rgba(196, 100, 52, 0.05) 0%, transparent 50%),
+ radial-gradient(ellipse at 75% 85%, rgba(94, 94, 50, 0.05) 0%, transparent 50%),
+ url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' seed='17'/%3E%3CfeColorMatrix values='0 0 0 0 0.65 0 0 0 0 0.55 0 0 0 0 0.40 0 0 0 0.18 0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
+ background-attachment: fixed;
+}
+
+/* Surcharges visuelles aquarelle : hairlines sépia partout en remplacement
+ des borders zinc/gray du theme-guyane. */
+body[data-theme="aquarelle"] [class*="border-zinc-"],
+body[data-theme="aquarelle"] [class*="border-gray-"] {
+ border-color: rgba(140, 61, 24, 0.25);
+}
+
@media (prefers-color-scheme: dark) {
- :root:not([data-theme="guyane"]) {
+ :root:not([data-theme="guyane"]):not([data-theme="aquarelle"]) {
--background: #0a0a0a;
--foreground: #ededed;
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 54c6919..30c807f 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,5 +1,5 @@
import type { Metadata } from "next";
-import { Geist, Geist_Mono, Cormorant_Garamond } from "next/font/google";
+import { Geist, Geist_Mono, Cormorant_Garamond, PT_Serif } from "next/font/google";
import "./globals.css";
import { PluginProvider } from "@/lib/plugins/client";
import { getEnabledPluginKeys, syncPluginsFromRegistry } from "@/lib/plugins/server";
@@ -32,6 +32,15 @@ const cormorant = Cormorant_Garamond({
display: "swap",
});
+// PT Serif : typographie display pour le theme Aquarelle (carnet naturaliste).
+// Plus dense, plus encrée, parfaite pour les planches d'illustration.
+const ptSerif = PT_Serif({
+ variable: "--font-serif-aquarelle",
+ subsets: ["latin"],
+ weight: ["400", "700"],
+ display: "swap",
+});
+
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000";
export const metadata: Metadata = {
@@ -68,17 +77,26 @@ export default async function RootLayout({
enabledKeys = [];
}
- const themeGuyane = enabledKeys.includes("theme-guyane");
+ // Aquarelle > Guyane si les deux activés (mutual exclusion garantie par
+ // le hook plugin, mais on est défensif au cas où).
+ const themeAquarelle = enabledKeys.includes("theme-aquarelle");
+ const themeGuyane = !themeAquarelle && enabledKeys.includes("theme-guyane");
+ const dataTheme = themeAquarelle ? "aquarelle" : themeGuyane ? "guyane" : undefined;
+
+ // En thème aquarelle, on substitue la variable --font-serif par PT Serif
+ // (au lieu de Cormorant) pour coller à l'esthétique carnet.
+ const serifVariable = themeAquarelle ? ptSerif.variable : cormorant.variable;
+
const locale = await getLocale();
const messages = await dict(locale);
return (
diff --git a/src/components/landing/HeroSection.tsx b/src/components/landing/HeroSection.tsx
index 162319a..49ab4db 100644
--- a/src/components/landing/HeroSection.tsx
+++ b/src/components/landing/HeroSection.tsx
@@ -3,13 +3,20 @@ import { CarbetRiver } from "@/components/illustrations/CarbetRiver";
import { LocaleSwitcher } from "@/components/LocaleSwitcher";
import { isPluginEnabled } from "@/lib/plugins/server";
import { t } from "@/lib/i18n/server";
+import { aquarelleUrl, getActiveTheme } from "@/lib/theme";
/**
* Hero plein écran. Plugin `landing-hero`. Texte i18n via t() server.
- * Affiche le LocaleSwitcher en haut à droite si le plugin i18n est activé.
+ * Selon le theme actif :
+ * - aquarelle : illustration MinIO `01-hero-fleuve-maroni` en fond, ambiance
+ * carnet de voyage, texte sépia sur papier teinté, ornement palmier en
+ * coin et bordure hairline sépia
+ * - guyane : SVG vectoriel CarbetRiver, palette tropicale moderne
+ * - none : retombe sur le SVG
*/
export async function HeroSection() {
const i18nOn = await isPluginEnabled("i18n-fr-en");
+ const theme = await getActiveTheme();
const eyebrow = await t("hero.eyebrow");
const titleLine1 = await t("hero.titleLine1");
const titleAccent = await t("hero.titleAccent");
@@ -17,6 +24,69 @@ export async function HeroSection() {
const ctaDiscover = await t("hero.ctaDiscover");
const ctaPropose = await t("hero.ctaPropose");
+ if (theme === "aquarelle") {
+ return (
+
+
+ {/* voile crème pour lisibilité texte sépia sur l'aquarelle */}
+
+
+
+ {i18nOn ? (
+
+
+
+ ) : null}
+
+
+
+ ~
+ {eyebrow}
+ ~
+
+
+
+ {titleLine1}
+
+ {titleAccent}.
+
+
+
+ {subtitle}
+
+
+
+
+ {ctaDiscover}
+
+
+ {ctaPropose}
+
+
+
+
+ — planche I, carnet d'expédition Karbé —
+
+
+
+ );
+ }
+
+ // Theme guyane (default actuel) ou pas de theme
return (
diff --git a/src/lib/plugins/hooks.ts b/src/lib/plugins/hooks.ts
index 0bbedbd..20c7f87 100644
--- a/src/lib/plugins/hooks.ts
+++ b/src/lib/plugins/hooks.ts
@@ -30,6 +30,21 @@ import {
seedPirogueProviders,
} from "./seeds/pirogue-providers-default";
import { seedEnglishContentPages } from "./seeds/content-pages-en";
+import { prisma } from "@/lib/prisma";
+
+// Mutuelle exclusion theme-guyane / theme-aquarelle : activer l'un
+// désactive automatiquement l'autre.
+async function disableOtherTheme(currentKey: string): Promise
{
+ const other = currentKey === "theme-guyane" ? "theme-aquarelle" : "theme-guyane";
+ const row = await prisma.plugin.findUnique({ where: { key: other } });
+ if (row?.enabled) {
+ await prisma.plugin.update({
+ where: { key: other },
+ data: { enabled: false, lastDisabledAt: new Date() },
+ });
+ console.log(`[plugin ${currentKey}] désactive ${other} (mutual exclusion)`);
+ }
+}
export const pluginHooks: Record = {
"demo-carbets-seed": {
@@ -93,4 +108,15 @@ export const pluginHooks: Record = {
console.log(`[plugin i18n-fr-en] seed: ${count} pages EN`);
},
},
+ // Themes : mutuellement exclusifs (un seul actif à la fois).
+ "theme-guyane": {
+ onEnable: async () => {
+ await disableOtherTheme("theme-guyane");
+ },
+ },
+ "theme-aquarelle": {
+ onEnable: async () => {
+ await disableOtherTheme("theme-aquarelle");
+ },
+ },
};
diff --git a/src/lib/plugins/registry.ts b/src/lib/plugins/registry.ts
index 402ac92..d5ee90b 100644
--- a/src/lib/plugins/registry.ts
+++ b/src/lib/plugins/registry.ts
@@ -27,6 +27,14 @@ export const PLUGINS: PluginDescriptor[] = [
category: "visual",
version: "0.1.0",
},
+ {
+ key: "theme-aquarelle",
+ name: "Thème Aquarelle (carnet naturaliste)",
+ description:
+ "Direction artistique « carnet de voyage XIXᵉ » : papier teinté crème, traits sépia fins, aquarelles ocres+verts délavés, typographie display PT Serif. Active automatiquement les illustrations aquarelle si présentes. Mutuellement exclusif avec theme-guyane.",
+ category: "visual",
+ version: "0.1.0",
+ },
{
key: "landing-hero",
name: "Hero d'accueil",
@@ -51,6 +59,14 @@ export const PLUGINS: PluginDescriptor[] = [
category: "visual",
version: "0.1.0",
},
+ {
+ key: "image-gallery-aquarelle-seed",
+ name: "Galerie aquarelles seed",
+ description:
+ "14 illustrations aquarelle (6 planches naturalistes carbets, 7 scènes carnet de voyage, 1 ornement palmier) stockées dans MinIO/karbe-medias/seed/aquarelle/. Création des Media liés aux 6 carbets démo. Désactivation : suppression des Media seedés (les fichiers MinIO restent).",
+ category: "visual",
+ version: "0.1.0",
+ },
{
key: "demo-carbets-seed",
name: "Carbets de démo",
diff --git a/src/lib/theme.ts b/src/lib/theme.ts
new file mode 100644
index 0000000..bf8c755
--- /dev/null
+++ b/src/lib/theme.ts
@@ -0,0 +1,28 @@
+/**
+ * Helpers theme — server-side.
+ *
+ * Centralise la résolution du theme actif (guyane | aquarelle | none) pour
+ * que chaque composant qui veut un rendu spécifique au theme appelle un seul
+ * helper plutôt que de checker `isPluginEnabled("theme-...")` individuellement.
+ */
+
+import "server-only";
+import { isPluginEnabled } from "@/lib/plugins/server";
+
+export type ActiveTheme = "guyane" | "aquarelle" | "none";
+
+export async function getActiveTheme(): Promise {
+ if (await isPluginEnabled("theme-aquarelle")) return "aquarelle";
+ if (await isPluginEnabled("theme-guyane")) return "guyane";
+ return "none";
+}
+
+/**
+ * URL publique d'une illustration aquarelle hébergée dans MinIO.
+ * Les fichiers sont uploadés dans karbe-medias/seed/aquarelle/ et servis via
+ * media.karbe.cosmolan.fr (bucket public-download).
+ */
+export function aquarelleUrl(filename: string): string {
+ const base = process.env.S3_PUBLIC_URL?.replace(/\/+$/, "") ?? "https://media.karbe.cosmolan.fr/karbe-medias";
+ return `${base}/seed/aquarelle/${filename}`;
+}