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}`; +}