/* lawandeconomics.ch — v4
   ============================================================
   1.  Tokens & thème
   2.  Helpers (PlusPattern, ArtPattern, icônes)
   3.  AuroraBackground
   4.  ContentContext (charge /content.json)
   5.  Header (logo, nav, search, theme, finder)
   6.  Hero
   7.  Categories
   8.  Filters (style Linear)
   9.  Publications + PubCard (style InfoCard hover)
   10. Manifeste
   11. Footer
   12. Modales : CarouselViewer, FinderExplorer (Arbre + Colonnes)
   13. App
   ============================================================ */

   const { useState, useEffect, useRef, useMemo, useCallback,
    createContext, useContext } = React;

/* ============================================================
1. TOKENS
============================================================ */

const PALETTE = {
fiscalite:     { base: "#dc2626", soft: "#fef2f2", dark: "#f87171" }, // 20 CHF
international: { base: "#ea8a1f", soft: "#fef7ec", dark: "#fbbf24" }, // 10 CHF
politiques:    { base: "#16a34a", soft: "#f0fdf4", dark: "#4ade80" }, // 50 CHF
economie:      { base: "#2563eb", soft: "#eff6ff", dark: "#60a5fa" }, // 100 CHF
droit:         { base: "#92400e", soft: "#fef3c7", dark: "#d97706" }, // 200 CHF
arbitrage:     { base: "#7c3aed", soft: "#f5f3ff", dark: "#a78bfa" }, // 1000 CHF
};

const CATEGORIES = [
{ key: "fiscalite",     label: "Fiscalité",            icon: "fiscalite" },
{ key: "international", label: "International",        icon: "international" },
{ key: "economie",      label: "Économie",             icon: "economie" },
{ key: "politiques",    label: "Politiques publiques", icon: "politiques" },
{ key: "droit",         label: "Droit",                icon: "droit" },
{ key: "arbitrage",     label: "Arbitrage",            icon: "arbitrage" },
];
const CAT_BY_KEY = Object.fromEntries(CATEGORIES.map(c => [c.key, c]));
const colorOf = (key, isDark) => isDark ? PALETTE[key]?.dark : PALETTE[key]?.base;

const LIGHT = {
paper:    "#fafaf7",
surface:  "#ffffff",
surface2: "#f4f4f0",
gutter:   "#ededea",
ink:      "#0e0e12",
inkSoft:  "#52525b",
inkMute:  "#a1a1aa",
line:     "#e5e5e1",
lineSoft: "#efefec",
accent:   "#4f46e5",
accentSoft: "#eef2ff",
shadow:   "0 1px 2px rgba(15,15,20,.04), 0 8px 24px rgba(15,15,20,.06)",
shadowHi: "0 2px 4px rgba(15,15,20,.06), 0 24px 60px rgba(15,15,20,.13)",
isDark:   false,
// aurora colors
aurora1:  "#dbeafe", aurora2: "#fef3c7", aurora3: "#ede9fe",
};

const DARK = {
paper:    "#0a0a0e",
surface:  "#101015",
surface2: "#16161d",
gutter:   "#06060a",
ink:      "#ececf0",
inkSoft:  "#a1a1aa",
inkMute:  "#5e5e68",
line:     "#26262e",
lineSoft: "#1a1a22",
accent:   "#818cf8",
accentSoft: "#1c1d2e",
shadow:   "0 1px 2px rgba(0,0,0,.4), 0 12px 32px rgba(0,0,0,.4)",
shadowHi: "0 2px 6px rgba(0,0,0,.5), 0 28px 72px rgba(0,0,0,.6)",
isDark:   true,
aurora1:  "#1e1b4b", aurora2: "#3b0764", aurora3: "#0c4a6e",
};

const TYPE = {
serif: `"Fraunces", "Times New Roman", Georgia, serif`,
sans:  `"Inter", "Helvetica Neue", Helvetica, Arial, sans-serif`,
mono:  `"JetBrains Mono", ui-monospace, "SF Mono", monospace`,
};

const R = { sm: 4, md: 6, lg: 8, xl: 12 };

/* ============================================================
2. HELPERS — patterns & icônes
============================================================ */

function PlusPattern({ density = 14, opacity = 0.06, color = "#000" }) {
const id = `plus-${density}-${color.replace("#","")}-${(opacity*100)|0}`;
return (
<svg width="100%" height="100%" style={{ display: "block", opacity }}>
  <defs>
    <pattern id={id} width={density} height={density} patternUnits="userSpaceOnUse">
      <path d={`M ${density/2} ${density/2 - 1.6} V ${density/2 + 1.6} M ${density/2 - 1.6} ${density/2} H ${density/2 + 1.6}`}
        stroke={color} strokeWidth="0.55" strokeLinecap="round"/>
    </pattern>
  </defs>
  <rect width="100%" height="100%" fill={`url(#${id})`}/>
</svg>
);
}

/* 4 motifs de gravure simples pour fallback (concept sans image) */
function ArtPattern({ variant = 0, color, opacity = 0.7 }) {
const v = variant % 4;
if (v === 0) return (
<svg viewBox="0 0 200 140" width="100%" height="100%" preserveAspectRatio="xMidYMid meet" style={{opacity}}>
  {Array.from({length:9}, (_,i) => (
    <circle key={i} cx="100" cy="70" r={(i+1)*7} fill="none" stroke={color} strokeWidth="0.4" opacity={1-i*0.09}/>
  ))}
  <circle cx="100" cy="70" r="3" fill={color}/>
</svg>
);
if (v === 1) return (
<svg viewBox="0 0 200 140" width="100%" height="100%" preserveAspectRatio="xMidYMid meet" style={{opacity}}>
  {Array.from({length:14}, (_,i) => (
    <line key={i} x1={20+i*12} y1={120} x2={100} y2={20} stroke={color} strokeWidth="0.4" opacity={0.3+(i/14)*0.5}/>
  ))}
  <path d="M20 120 L100 20 L180 120" stroke={color} strokeWidth="0.7" fill="none"/>
</svg>
);
if (v === 2) return (
<svg viewBox="0 0 200 140" width="100%" height="100%" preserveAspectRatio="xMidYMid meet" style={{opacity}}>
  {Array.from({length:8}, (_,i) => (
    <rect key={i} x={20+i*8} y={20+i*6} width={160-i*16} height={100-i*12} fill="none" stroke={color} strokeWidth="0.4" opacity={1-i*0.1}/>
  ))}
</svg>
);
return (
<svg viewBox="0 0 200 140" width="100%" height="100%" preserveAspectRatio="xMidYMid meet" style={{opacity}}>
  <path d="M20 110 Q100 20 180 110" stroke={color} strokeWidth="0.8" fill="none"/>
  {Array.from({length:5}, (_,i) => (
    <path key={i} d={`M ${30+i*8} ${110-i*4} Q 100 ${30+i*15} ${170-i*8} ${110-i*4}`} stroke={color} strokeWidth="0.35" fill="none" opacity={0.5-i*0.08}/>
  ))}
  <line x1="20" y1="110" x2="180" y2="110" stroke={color} strokeWidth="0.5"/>
</svg>
);
}

function CategoryIcon({ kind, color, animated }) {
const sw = 1.3;
const style = { transition: "transform 420ms cubic-bezier(.34,1.4,.64,1)", transformOrigin: "center" };
if (kind === "fiscalite") {
const dots = [];
for (let y=0; y<3; y++) for (let x=0; x<5; x++) {
  dots.push(<circle key={`${x}-${y}`} cx={5+x*7} cy={5+y*7} r={animated?1.4:1.1} fill={color}
    style={{transition:`r 280ms ${(x+y)*15}ms cubic-bezier(.34,1.4,.64,1)`}}/>);
}
return <svg viewBox="0 0 38 22" width="32" height="20" style={style}>{dots}</svg>;
}
if (kind === "economie") return (
<svg viewBox="0 0 38 22" width="32" height="20" style={style}>
  <rect x="3"  y={animated?12:14} width="6" height={animated?8:6}  fill="none" stroke={color} strokeWidth={sw} style={{transition:"all 260ms cubic-bezier(.34,1.4,.64,1)"}}/>
  <rect x="13" y={animated?7:10}  width="6" height={animated?13:10} fill="none" stroke={color} strokeWidth={sw} style={{transition:"all 260ms 60ms cubic-bezier(.34,1.4,.64,1)"}}/>
  <rect x="23" y={animated?2:6}   width="6" height={animated?18:14} fill="none" stroke={color} strokeWidth={sw} style={{transition:"all 260ms 120ms cubic-bezier(.34,1.4,.64,1)"}}/>
</svg>
);
if (kind === "droit") return (
<svg viewBox="0 0 38 22" width="32" height="20" style={style}>
  <line x1="4" y1="6"  x2={animated?28:32} y2="6"  stroke={color} strokeWidth={sw} style={{transition:"all 260ms cubic-bezier(.34,1.4,.64,1)"}}/>
  <line x1="4" y1="11" x2={animated?24:32} y2="11" stroke={color} strokeWidth={sw} style={{transition:"all 260ms 80ms cubic-bezier(.34,1.4,.64,1)"}}/>
  <line x1="4" y1="16" x2={animated?30:32} y2="16" stroke={color} strokeWidth={sw} style={{transition:"all 260ms 140ms cubic-bezier(.34,1.4,.64,1)"}}/>
</svg>
);
if (kind === "international") return (
<svg viewBox="0 0 38 22" width="32" height="20" style={style}>
  <circle cx="19" cy="11" r="9" fill="none" stroke={color} strokeWidth={sw}/>
  <ellipse cx="19" cy="11" rx="4" ry="9" fill="none" stroke={color} strokeWidth={sw}
    style={{transformOrigin:"19px 11px", transform: animated?"rotateY(40deg)":"rotateY(0)", transition:"transform 460ms cubic-bezier(.4,0,.2,1)"}}/>
  <line x1="10" y1="11" x2="28" y2="11" stroke={color} strokeWidth={sw}/>
</svg>
);
if (kind === "politiques") {
const dots = [];
for (let y=0; y<3; y++) for (let x=0; x<3; x++) {
  dots.push(<circle key={`${x}-${y}`} cx={11+x*7} cy={4+y*7} r={animated?1.4:1.1} fill={color}
    style={{transition:`r 280ms ${(2-x+y)*15}ms cubic-bezier(.34,1.4,.64,1)`}}/>);
}
return <svg viewBox="0 0 38 22" width="32" height="20" style={style}>{dots}</svg>;
}
if (kind === "arbitrage") return (
<svg viewBox="0 0 38 22" width="32" height="20" style={style}>
  <line x1="19" y1="2"  x2="19" y2="20" stroke={color} strokeWidth={sw}/>
  <line x1="6"  y1="9"  x2="32" y2="9"  stroke={color} strokeWidth={sw}/>
  <polygon points="6,9 3,15 9,15" fill={animated?color:"none"} stroke={color} strokeWidth={sw} style={{transition:"fill 240ms"}}/>
  <polygon points="32,9 29,15 35,15" fill="none" stroke={color} strokeWidth={sw}/>
</svg>
);
return null;
}

function FolderIcon({ size = 16, color = "currentColor", filled = false }) {
return (
<svg width={size} height={size} viewBox="0 0 16 16" fill={filled ? color : "none"} stroke={color} strokeWidth={filled ? 0 : 1.4}>
  <path d="M1.5 4 a 1 1 0 0 1 1 -1 H 6 l 1.5 1.5 H 13.5 a 1 1 0 0 1 1 1 V 12 a 1 1 0 0 1 -1 1 H 2.5 a 1 1 0 0 1 -1 -1 Z" strokeLinejoin="round"/>
</svg>
);
}

function FileIcon({ size = 14, color }) {
return (
<svg width={size} height={size+2} viewBox="0 0 14 16" fill="none">
  <path d="M2 1 H 9 L 12 4 V 14 a 1 1 0 0 1 -1 1 H 2 a 1 1 0 0 1 -1 -1 V 2 a 1 1 0 0 1 1 -1 Z"
        fill={color} fillOpacity="0.16" stroke={color} strokeWidth="1"/>
  <path d="M9 1 V 4 H 12" stroke={color} strokeWidth="1" fill="none"/>
</svg>
);
}

/* ============================================================
3. AURORA BACKGROUND — radiaux flous animés très subtils
============================================================ */

function AuroraBackground({ t }) {
const opacity = t.isDark ? 0.5 : 0.55;
return (
<div style={{ position: "fixed", inset: 0, zIndex: 0, pointerEvents: "none", overflow: "hidden" }}>
  <div style={{ position: "absolute", inset: 0, background: t.paper, transition: "background 400ms" }}/>
  <div className="aurora-blob" style={{
    position: "absolute", top: "-20%", left: "-10%", width: "70vw", height: "70vw",
    background: `radial-gradient(circle at center, ${t.aurora1} 0%, transparent 60%)`,
    opacity, filter: "blur(80px)", animation: "auroraFloat1 36s ease-in-out infinite",
  }}/>
  <div className="aurora-blob" style={{
    position: "absolute", bottom: "-30%", right: "-15%", width: "75vw", height: "75vw",
    background: `radial-gradient(circle at center, ${t.aurora2} 0%, transparent 60%)`,
    opacity: opacity*0.85, filter: "blur(80px)", animation: "auroraFloat2 44s ease-in-out infinite",
  }}/>
  <div className="aurora-blob" style={{
    position: "absolute", top: "30%", right: "20%", width: "50vw", height: "50vw",
    background: `radial-gradient(circle at center, ${t.aurora3} 0%, transparent 60%)`,
    opacity: opacity*0.7, filter: "blur(80px)", animation: "auroraFloat3 52s ease-in-out infinite",
  }}/>
</div>
);
}

/* ============================================================
4. CONTEXTE CONTENU
============================================================ */

const ContentCtx = createContext({ items: [], byCat: {}, tree: {}, ready: false });
function useContent() { return useContext(ContentCtx); }

function ContentProvider({ children }) {
const [state, setState] = useState({ items: [], byCat: {}, tree: {}, ready: false });

useEffect(() => {
let alive = true;
fetch("/content.json", { cache: "no-cache" })
  .then(r => r.ok ? r.json() : { items: [] })
  .catch(() => ({ items: [] }))
  .then(json => {
    if (!alive) return;
    const items = (json.items || []).map(it => ({
      ...it,
      categoryLabel: CAT_BY_KEY[it.category]?.label || it.category,
    }));
    const byCat = {};
    for (const it of items) (byCat[it.category] = byCat[it.category] || []).push(it);
    const tree = {};
    for (const it of items) {
      let node = tree;
      const parts = [it.category, ...(it.topicPath || []), it.slug];
      parts.forEach((p, idx) => {
        node[p] = node[p] || { _children: {}, _key: p };
        if (idx === parts.length - 1) node[p]._concept = it;
        node = node[p]._children;
      });
    }
    setState({ items, byCat, tree, ready: true });
  });
return () => { alive = false; };
}, []);

return <ContentCtx.Provider value={state}>{children}</ContentCtx.Provider>;
}

/* ============================================================
5. HEADER
============================================================ */

function ThemeToggler({ isDark, onToggle, t }) {
return (
<button onClick={onToggle} className="hdr-icon" aria-label={isDark ? "Mode jour" : "Mode nuit"}
  style={{ background: "transparent", border: 0, borderLeft: `1px solid ${t.line}`,
           width: 52, color: t.ink, display: "flex", alignItems: "center", justifyContent: "center" }}>
  <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"
       style={{ transform: isDark ? "rotate(210deg)" : "rotate(0deg)", transition: "transform 600ms cubic-bezier(.34,1.3,.64,1)" }}>
    <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" style={{ opacity: isDark?1:0, transition: "opacity 350ms" }}/>
    <circle cx="12" cy="12" r="4.5" fill="currentColor" stroke="none" style={{ opacity: isDark?0:1, transition: "opacity 350ms" }}/>
    {[0,45,90,135,180,225,270,315].map((deg, i) => {
      const r = Math.PI*deg/180;
      return <line key={i} x1={12+Math.cos(r)*7} y1={12+Math.sin(r)*7} x2={12+Math.cos(r)*9.2} y2={12+Math.sin(r)*9.2}
        style={{ opacity: isDark?0:1, transition: `opacity 300ms ${i*20}ms` }}/>;
    })}
  </svg>
</button>
);
}

function Header({ t, isDark, onToggleTheme, onOpenFinder, onScrollTo }) {
const iconBtn = { background: "transparent", border: 0, borderLeft: `1px solid ${t.line}`,
width: 52, color: t.ink, display: "flex", alignItems: "center", justifyContent: "center" };
return (
<header style={{
  borderBottom: `1px solid ${t.line}`,
  display: "grid", gridTemplateColumns: "1fr auto 52px 52px 52px",
  alignItems: "stretch", background: `${t.surface}cc`,
  position: "sticky", top: 0, zIndex: 50,
  backdropFilter: "saturate(140%) blur(10px)", WebkitBackdropFilter: "saturate(140%) blur(10px)",
}}>
  <div style={{ padding: "20px 28px", display: "flex", alignItems: "center" }}>
    <a href="#top" style={{
      textDecoration: "none", color: t.ink, fontSize: 15, letterSpacing: "-0.01em",
      fontWeight: 500, display: "inline-flex", alignItems: "baseline",
    }}>
      <span style={{ position: "relative" }}>
        lawandeconomics
        <span className="logo-line" style={{
          position: "absolute", left: 0, bottom: -2, height: 1, width: "100%",
          background: t.accent, transform: "scaleX(0)", transformOrigin: "left",
          transition: "transform 320ms cubic-bezier(.4,0,.2,1)",
        }}/>
      </span>
      <span style={{ opacity: 0.4 }}>.ch</span>
    </a>
  </div>
  <nav style={{ display: "flex", alignItems: "center", gap: 30, padding: "0 28px" }}>
    {[
      { label: "Concepts", target: "concepts" },
      { label: "Thématiques", target: "thematiques" },
      { label: "Manifeste", target: "manifeste" },
    ].map(n => (
      <a key={n.label} href={`#${n.target}`} className="nav-link" style={{
        textDecoration: "none", color: t.ink, fontSize: 10.5, letterSpacing: "0.18em",
        textTransform: "uppercase", fontWeight: 500, position: "relative", padding: "4px 0",
      }}>{n.label}</a>
    ))}
  </nav>
  <button className="hdr-icon" style={iconBtn} aria-label="Recherche">
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
      <circle cx="10.5" cy="10.5" r="6.5"/><path d="M15 15 L20 20" strokeLinecap="round"/>
    </svg>
  </button>
  <ThemeToggler isDark={isDark} onToggle={onToggleTheme} t={t}/>
  <button className="hdr-icon" style={iconBtn} aria-label="Explorer" onClick={onOpenFinder}>
    <FolderIcon size={17}/>
  </button>
</header>
);
}

/* ============================================================
6. HERO
============================================================ */

function FeaturedCard({ item, t, idx, total, hasMultiple }) {
const color = colorOf(item.category, t.isDark);
const hasImg = item.images && item.images.length > 0;
return (
<div key={item.id} style={{
  background: t.surface, border: `1px solid ${t.line}`,
  borderTop: `2px solid ${color}`, borderRadius: R.lg,
  padding: "22px 24px 18px", display: "flex", flexDirection: "column",
  height: "100%", boxShadow: t.shadowHi, position: "relative", overflow: "hidden",
  animation: "fadeInUp 540ms cubic-bezier(.4,0,.2,1) both",
}}>
  <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 16 }}>
    <span style={{ color, fontSize: 10.5, letterSpacing: "0.22em", textTransform: "uppercase", fontWeight: 600 }}>
      {item.categoryLabel}
    </span>
    {hasMultiple && (
      <span style={{ color: t.inkMute, fontSize: 11, fontFamily: TYPE.mono }}>
        {String(idx+1).padStart(2,"0")} / {String(total).padStart(2,"0")}
      </span>
    )}
  </div>
  <h3 style={{
    fontFamily: TYPE.serif, fontWeight: 400, fontSize: 26, lineHeight: 1.14,
    letterSpacing: "-0.02em", margin: 0, color: t.ink, whiteSpace: "pre-line",
  }}>{item.title}</h3>
  {item.summary && (
    <p style={{ marginTop: 10, marginBottom: 0, color: t.inkSoft, fontSize: 13, lineHeight: 1.6, maxWidth: 320 }}>
      {item.summary}
    </p>
  )}
  <div style={{ flex: 1, minHeight: 160, marginTop: 20, display: "flex", alignItems: "center", justifyContent: "center" }}>
    {hasImg ? (
      <img src={item.images[0]} alt="" style={{ maxWidth: "100%", maxHeight: 200, objectFit: "contain" }}/>
    ) : (
      <div style={{ width: "75%", height: 160 }}>
        <ArtPattern variant={item.artVariant ?? 0} color={color} opacity={0.55}/>
      </div>
    )}
  </div>
  <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center",
                paddingTop: 12, borderTop: `1px solid ${t.line}`, fontFamily: TYPE.mono, fontSize: 10.5, color: t.inkMute }}>
    <span>{hasImg ? `${item.images.length} slides` : "Concept"}</span>
    <span style={{ letterSpacing: "0.08em", textTransform: "uppercase" }}>Carrousel ↗</span>
  </div>
</div>
);
}

function HeroEmptyState({ t }) {
return (
<div style={{
  background: t.surface, border: `1px dashed ${t.line}`, borderRadius: R.lg,
  padding: 32, height: "100%", display: "flex", flexDirection: "column",
  alignItems: "center", justifyContent: "center", textAlign: "center", gap: 16,
}}>
  <div style={{ width: 64, height: 64, borderRadius: R.md, background: t.surface2,
                display: "flex", alignItems: "center", justifyContent: "center" }}>
    <FolderIcon size={28} color={t.inkMute}/>
  </div>
  <div>
    <div style={{ fontFamily: TYPE.serif, fontSize: 19, color: t.ink, marginBottom: 6 }}>
      Bibliothèque vide
    </div>
    <div style={{ color: t.inkSoft, fontSize: 12.5, lineHeight: 1.6, maxWidth: 280 }}>
      Aucun concept publié pour l'instant. Ajoutez un dossier dans{" "}
      <code style={{ fontFamily: TYPE.mono, fontSize: 11.5,
                     background: t.surface2, padding: "1px 6px", borderRadius: R.sm }}>public/content/</code>{" "}
      puis rebuild.
    </div>
  </div>
</div>
);
}

function Hero({ t, isDark }) {
const { items, ready } = useContent();

return (
<section style={{
  borderBottom: `1px solid ${t.line}`,
  minHeight: "50vh",
  position: "relative",
  background: t.surface,
}}>

  {/* Panneau décoratif droit — lignes rayonnantes style gravure */}
  <div style={{
    position: "absolute", right: 0, top: 0, bottom: 0, width: "50%",
    pointerEvents: "none", overflow: "hidden",
  }}>
    {/* Fondu gauche */}
    <div style={{
      position: "absolute", left: 0, top: 0, bottom: 0, width: 260, zIndex: 3,
      background: `linear-gradient(to right, ${t.surface} 10%, transparent 100%)`,
    }}/>
    {/* Gradient wash accent */}
    <div style={{
      position: "absolute", inset: 0,
      background: `radial-gradient(ellipse 90% 110% at 70% 45%, ${t.accent}12 0%, transparent 62%)`,
      animation: "hBlob1 28s ease-in-out infinite",
    }}/>
    {/* Lignes rayonnantes — esthétique gravure banknote */}
    <svg
      style={{ position: "absolute", inset: 0, width: "100%", height: "100%", opacity: isDark ? 0.08 : 0.06 }}
      viewBox="0 0 700 500" preserveAspectRatio="xMidYMid slice"
    >
      <g transform="translate(380,250)" fill="none" stroke={t.ink} strokeWidth="0.55">
        {Array.from({length: 52}, (_, i) => {
          const a = (i / 52) * Math.PI * 2;
          const x1 = Math.cos(a) * 28, y1 = Math.sin(a) * 28;
          const x2 = Math.cos(a) * 380, y2 = Math.sin(a) * 380;
          return <line key={i} x1={x1} y1={y1} x2={x2} y2={y2} opacity={0.5 + 0.5 * Math.abs(Math.cos(a * 2))}/>;
        })}
        {[55, 100, 160, 230, 310].map(r => (
          <circle key={r} cx={0} cy={0} r={r} strokeOpacity="0.55" strokeWidth="0.4"/>
        ))}
      </g>
    </svg>
    {/* Grain */}
    <svg style={{ position: "absolute", inset: 0, width: "100%", height: "100%", opacity: isDark ? 0.12 : 0.055, zIndex: 2 }}>
      <filter id="hgrain">
        <feTurbulence type="fractalNoise" baseFrequency="0.68" numOctaves="4" stitchTiles="stitch"/>
        <feColorMatrix type="saturate" values="0"/>
      </filter>
      <rect width="100%" height="100%" filter="url(#hgrain)"/>
    </svg>
  </div>

  {/* Contenu */}
  <div style={{
    position: "relative", zIndex: 2,
    padding: "64px 56px 52px",
    display: "flex", flexDirection: "column",
    maxWidth: 760,
  }}>
    <div style={{ flex: 1, display: "flex", flexDirection: "column", justifyContent: "center", marginBottom: 32 }}>
      <div style={{ display: "inline-flex", alignItems: "center", gap: 10, marginBottom: 26 }}>
        <span style={{
          width: 6, height: 6, borderRadius: "50%", background: t.accent,
          boxShadow: `0 0 0 4px ${t.accent}22`,
          animation: "pulse 2.4s cubic-bezier(.4,0,.6,1) infinite",
        }}/>
        <span style={{ color: t.accent, fontSize: 10.5, letterSpacing: "0.22em", textTransform: "uppercase", fontWeight: 600 }}>
          Plateforme académique · 2026
        </span>
      </div>
      <h1 style={{
        fontFamily: TYPE.serif, fontWeight: 400,
        fontSize: "clamp(42px, 5vw, 68px)", lineHeight: 1.02,
        letterSpacing: "-0.028em", margin: 0, color: t.ink,
        whiteSpace: "nowrap",
      }}>
        <div style={{ animation: "lineUp 700ms cubic-bezier(.4,0,.2,1) both" }}>Idée complexe,</div>
        <div style={{ animation: "lineUp 700ms 100ms cubic-bezier(.4,0,.2,1) both" }}>
          expliquer <span style={{ color: t.accent, fontStyle: "italic" }}>facilement.</span>
        </div>
      </h1>
      <div style={{ width: 56, height: 2, background: t.accent, marginTop: 20, marginBottom: 16,
                    borderRadius: 1, animation: "barIn 600ms 380ms cubic-bezier(.4,0,.2,1) both",
                    transformOrigin: "left" }}/>
      <p style={{ maxWidth: 460, color: t.inkSoft, fontSize: 14.5, lineHeight: 1.7, margin: 0,
                  animation: "fadeUp 700ms 240ms cubic-bezier(.4,0,.2,1) both" }}>
        Des notions clés du droit, de l'économie et de la fiscalité expliquées en carrousels visuels et interactifs.
        Six familles thématiques, des centaines de concepts.
      </p>
      <div style={{ display: "flex", gap: 12, marginTop: 28, flexWrap: "wrap",
                    animation: "fadeUp 700ms 380ms cubic-bezier(.4,0,.2,1) both" }}>
        <a href="#concepts" className="cta-primary" style={{
          display: "inline-flex", alignItems: "center", gap: 14, background: t.ink, color: t.surface,
          textDecoration: "none", padding: "13px 22px", fontSize: 10.5,
          letterSpacing: "0.22em", textTransform: "uppercase", fontWeight: 600,
          borderRadius: R.md, border: `1px solid ${t.ink}`,
        }}>
          <span>Explorer les concepts</span>
          <svg className="cta-arrow" width="20" height="10" viewBox="0 0 22 10" fill="none" stroke="currentColor" strokeWidth="1.3">
            <line x1="0" y1="5" x2="20" y2="5"/><polyline points="16,1 20,5 16,9" fill="none"/>
          </svg>
        </a>
        <a href="#manifeste" className="cta-secondary" style={{
          display: "inline-flex", alignItems: "center", gap: 10, color: t.ink,
          textDecoration: "none", padding: "13px 18px", fontSize: 10.5,
          letterSpacing: "0.22em", textTransform: "uppercase", fontWeight: 600,
          borderRadius: R.md, border: `1px solid ${t.line}`, background: "transparent",
        }}>Manifeste</a>
      </div>
    </div>
    <div style={{
      display: "flex", gap: 0, paddingTop: 22,
      borderTop: `1px solid ${t.line}`, color: t.inkMute, fontSize: 11,
      fontFamily: TYPE.mono, letterSpacing: "0.04em",
    }}>
      <Stat label="Concepts" value={ready ? items.length : "—"} t={t}/>
      <Stat label="Thématiques" value="6" t={t}/>
      <Stat label="Langue" value="FR" t={t}/>
    </div>
  </div>
</section>
);
}
const navBtn = (t) => ({
width: 38, height: 30, background: "transparent", border: 0,
display: "flex", alignItems: "center", justifyContent: "center", color: t.ink,
});
const Stat = ({ label, value, t }) => (
<div style={{ flex: 1, display: "flex", flexDirection: "column", gap: 4 }}>
<span style={{ color: t.inkMute, fontSize: 9.5, letterSpacing: "0.16em", textTransform: "uppercase" }}>{label}</span>
<span style={{ color: t.ink, fontSize: 18, fontFamily: TYPE.serif, fontWeight: 400 }}>{value}</span>
</div>
);

/* ============================================================
7. CATEGORIES
============================================================ */

function Categories({ t }) {
const { byCat, ready } = useContent();
const [hoverKey, setHoverKey] = useState(null);
return (
<section id="thematiques" style={{
  background: t.isDark ? `${t.surface2}b0` : "#f5f5f3", borderBottom: `1px solid ${t.line}`,
  display: "grid", gridTemplateColumns: `repeat(${CATEGORIES.length}, 1fr)`,
}}>
  {CATEGORIES.map((c, i) => {
    const count = byCat[c.key]?.length || 0;
    const hovered = hoverKey === c.key;
    const color = colorOf(c.key, t.isDark);
    return (
      <a key={c.key} href={`#cat-${c.key}`}
        onMouseEnter={() => setHoverKey(c.key)} onMouseLeave={() => setHoverKey(null)}
        className="cat-tile" style={{
          padding: "40px 24px 32px", textDecoration: "none", color: t.ink,
          borderRight: i < CATEGORIES.length-1 ? `1px solid ${t.line}` : 0,
          display: "flex", flexDirection: "column", gap: 22,
          position: "relative", overflow: "hidden",
          background: hovered ? t.surface : "transparent",
          transition: "background 280ms ease",
        }}>
        <CategoryIcon kind={c.icon} color={hovered ? color : t.ink} animated={hovered}/>
        <div>
          <div style={{ fontSize: 14.5, fontWeight: 500,
                        color: hovered ? color : t.ink, transition: "color 240ms" }}>{c.label}</div>
          <div style={{ fontSize: 10.5, color: t.inkMute, marginTop: 6,
                        fontFamily: TYPE.mono, letterSpacing: "0.04em" }}>
            {ready ? (count > 0 ? `${count} concept${count > 1 ? "s" : ""}` : "0 concept") : "—"}
          </div>
        </div>
        <div style={{
          position: "absolute", left: 0, bottom: 0, height: 2,
          width: hovered ? "100%" : 0, background: color,
          transition: "width 380ms cubic-bezier(.4,0,.2,1)",
        }}/>
        <div style={{ position: "absolute", inset: 0, opacity: hovered ? 0.04 : 0,
                      transition: "opacity 380ms", pointerEvents: "none" }}>
          <PlusPattern density={14} opacity={1} color={color}/>
        </div>
      </a>
    );
  })}
</section>
);
}

/* ============================================================
8. FILTRES (style Linear)
============================================================ */

function Filters({ t, filter, setFilter, items }) {
const allCount = items.length;
return (
<div style={{ display: "flex", gap: 6, flexWrap: "wrap", alignItems: "center" }}>
  <FilterChip active={filter === "all"} onClick={() => setFilter("all")} t={t}
    label="Toutes" color={t.ink} count={allCount}/>
  {CATEGORIES.map(c => {
    const cnt = items.filter(it => it.category === c.key).length;
    if (cnt === 0) return null;
    return (
      <FilterChip key={c.key} active={filter === c.key} onClick={() => setFilter(c.key)} t={t}
        label={c.label} color={colorOf(c.key, t.isDark)} count={cnt}/>
    );
  })}
</div>
);
}

function FilterChip({ active, onClick, t, label, color, count }) {
return (
<button onClick={onClick} className="filter-chip" style={{
  padding: "6px 10px 6px 8px", fontSize: 11, fontWeight: 500,
  background: active ? t.ink : "transparent", color: active ? t.surface : t.ink,
  border: `1px solid ${active ? t.ink : t.line}`, borderRadius: 999,
  display: "inline-flex", alignItems: "center", gap: 7, transition: "all 200ms",
}}>
  <span style={{ width: 7, height: 7, background: color, borderRadius: "50%", opacity: active ? 1 : 0.9 }}/>
  <span>{label}</span>
  {count != null && <span style={{ opacity: 0.55, fontFamily: TYPE.mono, fontSize: 10 }}>{count}</span>}
</button>
);
}

/* ============================================================
9. PUBLICATIONS — InfoCard-style hover (height + image fan)
============================================================ */

function PubCard({ item, t, onOpen, animDelay = 0 }) {
const [hover, setHover] = useState(false);
const color = colorOf(item.category, t.isDark);
const hasImg = item.images && item.images.length > 0;
const previews = hasImg ? item.images.slice(0, 3) : [];

// height expansion
const baseHeight = 320;
const expHeight = hasImg ? 380 : 320;

return (
<div style={{
  animation: `cardIn 540ms ${animDelay}ms cubic-bezier(.4,0,.2,1) both`,
}}>
  <button
    onClick={() => onOpen(item)}
    onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
    className="pub-card" style={{
      textAlign: "left", color: t.ink, fontFamily: TYPE.sans, font: "inherit",
      background: t.surface, border: `1px solid ${hover ? t.ink : t.line}`,
      borderTop: `2px solid ${color}`, borderRadius: R.lg,
      padding: "22px 24px 52px", display: "flex", flexDirection: "column", gap: 10,
      height: hover ? expHeight : baseHeight, position: "relative",
      boxShadow: hover ? t.shadowHi : t.shadow,
      transform: hover ? "translateY(-3px)" : "translateY(0)",
      transition: "height 380ms cubic-bezier(.4,0,.2,1), transform 320ms cubic-bezier(.4,0,.2,1), border-color 200ms, box-shadow 320ms",
      width: "100%", overflow: "hidden",
    }}>
    <div style={{ color, fontSize: 10, letterSpacing: "0.22em",
                  textTransform: "uppercase", fontWeight: 600 }}>
      {item.categoryLabel}
    </div>
    <h3 style={{
      fontFamily: TYPE.serif, fontWeight: 400, fontSize: 21, lineHeight: 1.16,
      letterSpacing: "-0.012em", margin: 0, whiteSpace: "pre-line", color: t.ink,
    }}>{item.title}</h3>
    {item.summary && (
      <p style={{ margin: 0, color: t.inkSoft, fontSize: 12.5, lineHeight: 1.55 }}>
        {item.summary}
      </p>
    )}

    <div style={{ flex: 1, marginTop: 12, position: "relative",
                  display: "flex", alignItems: "flex-end", justifyContent: "center", minHeight: 100 }}>
      <div style={{ position: "absolute", inset: 0, opacity: hasImg && hover ? 0 : 1,
                    transition: "opacity 280ms", display: "flex", alignItems: "flex-end" }}>
        <div style={{ width: "100%", aspectRatio: "1.6/1" }}>
          <ArtPattern variant={item.artVariant ?? 0} color={color} opacity={0.6}/>
        </div>
      </div>
      {hasImg && (
        <div style={{
          position: "relative", width: "100%", aspectRatio: "1.6/1",
          opacity: hover ? 1 : 0, transition: "opacity 280ms",
          pointerEvents: hover ? "auto" : "none",
        }}>
          {previews.map((src, i) => {
            const center = (previews.length - 1) / 2;
            const off = i - center;
            const rot = hover ? off * 5 : 0;
            const tx = hover ? off * 18 : 0;
            const ty = hover ? -Math.abs(off) * 4 - 6 : 0;
            const sc = hover ? 1 - Math.abs(off) * 0.025 : 1;
            return (
              <img key={src} src={src} alt="" style={{
                position: "absolute", left: "10%", right: "10%", top: "5%", bottom: "5%",
                width: "80%", height: "90%", objectFit: "cover", borderRadius: R.sm,
                border: `1px solid ${t.line}`, background: t.surface2,
                boxShadow: t.shadow,
                transform: `translate(${tx}px, ${ty}px) rotate(${rot}deg) scale(${sc})`,
                transition: `transform 420ms cubic-bezier(.34,1.3,.64,1) ${i*40}ms`,
                zIndex: 10 - Math.abs(off),
              }}/>
            );
          })}
        </div>
      )}
    </div>

    <div style={{
      position: "absolute", left: 24, right: 24, bottom: 16,
      display: "flex", justifyContent: "space-between", alignItems: "center",
    }}>
      <span style={{ fontSize: 10, fontFamily: TYPE.mono, color: t.inkMute, letterSpacing: "0.06em" }}>
        {hasImg ? `${item.images.length} slides` : "Concept"}
      </span>
      <div style={{ transform: hover ? "translateX(5px)" : "translateX(0)",
                    transition: "transform 240ms cubic-bezier(.4,0,.2,1)",
                    color: hover ? color : t.ink }}>
        <svg width="20" height="10" viewBox="0 0 22 10" fill="none" stroke="currentColor" strokeWidth="1.3">
          <line x1="0" y1="5" x2="20" y2="5"/><polyline points="16,1 20,5 16,9"/>
        </svg>
      </div>
    </div>
  </button>
</div>
);
}

function PubsEmpty({ t }) {
return (
<div style={{
  gridColumn: "1 / -1", padding: "60px 32px",
  border: `1px dashed ${t.line}`, borderRadius: R.lg,
  display: "flex", flexDirection: "column", alignItems: "center", textAlign: "center", gap: 14,
  background: t.surface,
}}>
  <div style={{ width: 56, height: 56, borderRadius: R.md, background: t.surface2,
                display: "flex", alignItems: "center", justifyContent: "center" }}>
    <FolderIcon size={24} color={t.inkMute}/>
  </div>
  <div style={{ fontFamily: TYPE.serif, fontSize: 19, color: t.ink }}>
    Aucune publication
  </div>
  <p style={{ margin: 0, fontSize: 13, color: t.inkSoft, maxWidth: 460, lineHeight: 1.6 }}>
    Pour publier un concept, créez un dossier dans{" "}
    <code style={{ fontFamily: TYPE.mono, fontSize: 11.5,
                   background: t.surface2, padding: "2px 6px", borderRadius: R.sm }}>
      public/content/[catégorie]/[slug]/
    </code>{" "}
    avec un <code style={{ fontFamily: TYPE.mono, fontSize: 11.5,
                            background: t.surface2, padding: "2px 6px", borderRadius: R.sm }}>meta.json</code>{" "}
    et vos images, puis exécutez <code style={{ fontFamily: TYPE.mono, fontSize: 11.5,
                            background: t.surface2, padding: "2px 6px", borderRadius: R.sm }}>npm run build:content</code>.
  </p>
</div>
);
}

function Publications({ t, onOpenItem }) {
const { items, ready } = useContent();
const [filter, setFilter] = useState("all");
const list = filter === "all" ? items : items.filter(it => it.category === filter);

return (
<section id="concepts" style={{ background: `${t.surface}f0`, borderBottom: `1px solid ${t.line}` }}>
  <div style={{
    padding: "56px 32px 28px",
    display: "flex", justifyContent: "space-between", alignItems: "flex-end",
    flexWrap: "wrap", gap: 24,
  }}>
    <div>
      <div style={{ fontSize: 10.5, color: t.accent, letterSpacing: "0.22em",
                    textTransform: "uppercase", fontWeight: 600, marginBottom: 12 }}>
        Bibliothèque
      </div>
      <h2 style={{
        fontFamily: TYPE.serif, fontWeight: 400, fontSize: 38, lineHeight: 1.05,
        letterSpacing: "-0.022em", margin: 0, color: t.ink,
      }}>Publications</h2>
    </div>
    {ready && items.length > 0 && (
      <Filters t={t} filter={filter} setFilter={setFilter} items={items}/>
    )}
  </div>

  <div style={{
    display: "grid",
    gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))",
    gap: 18, padding: "0 32px 56px",
  }}>
    {!ready ? null : list.length === 0 ? (
      <PubsEmpty t={t}/>
    ) : list.map((p, i) => (
      <PubCard key={p.id} item={p} t={t} onOpen={onOpenItem} animDelay={i*50}/>
    ))}
  </div>
</section>
);
}

/* ============================================================
10. MANIFESTE
============================================================ */

const MANIFESTE_POINTS = [
{ num: "01", title: "Densité visuelle",   body: "Une diapositive condense ce qu'un long article dilue. La cognition spatiale fait le reste — une image vaut mille mots de doctrine." },
{ num: "02", title: "Mémorisation accrue", body: "Le format séquentiel mobilise la mémoire de travail bien mieux qu'un texte continu. Une notion par slide, ancrée par le visuel." },
{ num: "03", title: "Référence rapide",    body: "Chaque fiche est une unité atomique citable, partageable, archivable. Pas de scroll fatigue, pas de contenu dilué." },
{ num: "04", title: "Rigueur académique",  body: "Sources primaires citées, doctrine référencée, jurisprudence indexée. Le fond sans le format blog." },
];

function Manifeste({ t }) {
return (
<section id="manifeste" style={{ background: t.isDark ? `${t.surface2}c0` : "#f5f5f3" }}>
  <div style={{
    display: "grid", gridTemplateColumns: "1fr 1.875fr",
    borderBottom: `1px solid ${t.line}`,
  }}>
    <div style={{
      padding: "72px 56px", borderRight: `1px solid ${t.line}`,
      display: "flex", flexDirection: "column", justifyContent: "center", gap: 24,
    }}>
      <div style={{ fontSize: 10.5, color: t.accent, letterSpacing: "0.22em",
                    textTransform: "uppercase", fontWeight: 600 }}>Manifeste</div>
      <h2 style={{
        fontFamily: TYPE.serif, fontWeight: 400, fontSize: 42, lineHeight: 1.04,
        letterSpacing: "-0.025em", margin: 0, color: t.ink,
      }}>
        Pourquoi le format<br/>
        <span style={{ color: t.accent, fontStyle: "italic" }}>carrousel</span> ?
      </h2>
      <p style={{ margin: 0, fontSize: 14.5, lineHeight: 1.72, color: t.inkSoft, maxWidth: 380 }}>
        Les longs articles ne sont plus lus. Les fiches visuelles, oui. Le format carrousel impose la rigueur :
        une idée par slide, une source par citation, une notion par fiche.
      </p>
      <blockquote style={{
        margin: "8px 0 0", padding: "16px 0 0", borderTop: `1px solid ${t.line}`,
        fontSize: 12.5, fontFamily: TYPE.mono, color: t.inkMute,
      }}>« La densité visuelle est l'arme moderne de la doctrine. »</blockquote>
    </div>
    <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
      {MANIFESTE_POINTS.map((pt, i) => (
        <div key={pt.num} style={{
          padding: "44px 36px",
          borderRight: i % 2 === 0 ? `1px solid ${t.line}` : 0,
          borderBottom: i < 2 ? `1px solid ${t.line}` : 0,
          background: t.surface,
        }}>
          <div style={{ fontSize: 10, fontFamily: TYPE.mono, color: t.accent,
                        letterSpacing: "0.1em", marginBottom: 18 }}>{pt.num} —</div>
          <h3 style={{
            fontFamily: TYPE.serif, fontWeight: 400, fontSize: 21, lineHeight: 1.18,
            letterSpacing: "-0.012em", margin: "0 0 12px", color: t.ink,
          }}>{pt.title}</h3>
          <p style={{ margin: 0, fontSize: 13, lineHeight: 1.65, color: t.inkSoft }}>{pt.body}</p>
        </div>
      ))}
    </div>
  </div>
</section>
);
}

/* ============================================================
11. FOOTER
============================================================ */

const FOOTER_COLS = [
{ label: "Thématiques", links: ["Fiscalité internationale","Arbitrage & MAP","Analyse économique du droit","Droit international","Politiques publiques","Droit suisse"] },
{ label: "Ressources",  links: ["Index des concepts","Bibliographie","Citations & DOI","Publications LinkedIn","Glossaire"] },
{ label: "À propos",    links: ["Le projet","Recherche doctorale","Méthodologie","Contact","Mentions légales","Licence CC BY-NC 4.0"] },
];

function Footer({ t }) {
return (
<footer style={{ background: t.surface, borderTop: `1px solid ${t.line}` }}>
  <div style={{ display: "grid", gridTemplateColumns: "1.6fr 1fr 1fr 1fr", borderBottom: `1px solid ${t.line}` }}>
    <div style={{ padding: "56px 36px", borderRight: `1px solid ${t.line}`, display: "flex", flexDirection: "column" }}>
      <div style={{ fontSize: 15, fontWeight: 500, color: t.ink, marginBottom: 6 }}>
        lawandeconomics<span style={{ opacity: 0.4 }}>.ch</span>
      </div>
      <p style={{ margin: "14px 0 0", fontSize: 13, lineHeight: 1.7, color: t.inkSoft, maxWidth: 320 }}>
        Plateforme de vulgarisation juridique et économique. Notions clés du droit et de l'économie
        expliquées en carrousels visuels, en appui à la recherche doctorale.
      </p>
      <div style={{ display: "flex", gap: 14, marginTop: 26 }}>
        {["linkedin","instagram","x","mail"].map(k => <SocialIcon key={k} kind={k} t={t}/>)}
      </div>
      <div style={{ marginTop: "auto", paddingTop: 36 }}>
        <a href="#concepts" className="cta-primary" style={{
          background: t.ink, color: t.surface, textDecoration: "none",
          padding: "12px 18px", fontSize: 10, letterSpacing: "0.2em", textTransform: "uppercase", fontWeight: 600,
          display: "inline-flex", alignItems: "center", gap: 12, borderRadius: R.md, border: `1px solid ${t.ink}`,
        }}>
          <span>Explorer les concepts</span>
          <svg className="cta-arrow" width="16" height="8" viewBox="0 0 18 8" fill="none" stroke="currentColor" strokeWidth="1.2">
            <line x1="0" y1="4" x2="14" y2="4"/><polyline points="10,1 14,4 10,7"/>
          </svg>
        </a>
      </div>
    </div>
    {FOOTER_COLS.map((col, ci) => (
      <div key={ci} style={{ padding: "56px 30px",
        borderRight: ci < FOOTER_COLS.length-1 ? `1px solid ${t.line}` : 0 }}>
        <div style={{ fontSize: 9.5, letterSpacing: "0.18em", textTransform: "uppercase", fontWeight: 600,
                      color: t.inkMute, fontFamily: TYPE.mono, marginBottom: 22 }}>{col.label}</div>
        <nav>
          {col.links.map((l, li) => (
            <a key={li} href="#" className="footer-link" style={{
              display: "block", textDecoration: "none", color: t.ink,
              fontSize: 13, lineHeight: 1, paddingBottom: 12, opacity: 0.62,
            }}>{l}</a>
          ))}
        </nav>
      </div>
    ))}
  </div>
  <div style={{ padding: "16px 36px", display: "flex", justifyContent: "space-between", alignItems: "center",
                fontSize: 10.5, color: t.inkMute, fontFamily: TYPE.mono, letterSpacing: "0.04em" }}>
    <span>© 2026 LAWANDECONOMICS.CH — GENÈVE, SUISSE</span>
    <span>LICENCE CC BY-NC 4.0</span>
  </div>
</footer>
);
}

function SocialIcon({ kind, t }) {
const wrap = (svg) => <a href="#" className="soc" aria-label={kind} style={{
display: "inline-flex", alignItems: "center", justifyContent: "center",
width: 30, height: 30, color: t.ink, opacity: 0.65,
border: `1px solid ${t.line}`, borderRadius: R.sm,
}}>{svg}</a>;
if (kind === "linkedin") return wrap(<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><rect x="3" y="3" width="18" height="18" rx="1"/><path d="M7 10v7 M7 7v.01" strokeLinecap="round"/><path d="M11 17v-4 a2 2 0 0 1 4 0 v4 M11 10v7"/></svg>);
if (kind === "instagram") return wrap(<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><rect x="3" y="3" width="18" height="18" rx="4"/><circle cx="12" cy="12" r="4"/><circle cx="17.5" cy="6.5" r="0.8" fill="currentColor"/></svg>);
if (kind === "x") return wrap(<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><line x1="4" y1="4" x2="20" y2="20"/><line x1="20" y1="4" x2="4" y2="20"/></svg>);
return wrap(<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><rect x="3" y="5" width="18" height="14" rx="0.5"/><polyline points="3,7 12,14 21,7"/></svg>);
}

/* ============================================================
12. MODALES
============================================================ */

function Modal({ open, onClose, t, children, maxWidth = 1200 }) {
useEffect(() => {
if (!open) return;
const onKey = (e) => { if (e.key === "Escape") onClose(); };
document.addEventListener("keydown", onKey);
document.body.style.overflow = "hidden";
return () => { document.removeEventListener("keydown", onKey); document.body.style.overflow = ""; };
}, [open, onClose]);
if (!open) return null;
return (
<div onClick={onClose} style={{
  position: "fixed", inset: 0, zIndex: 100, background: "rgba(8,8,12,0.65)",
  backdropFilter: "blur(10px)", WebkitBackdropFilter: "blur(10px)",
  display: "flex", alignItems: "center", justifyContent: "center", padding: 32,
  animation: "fadeIn 240ms ease both",
}}>
  <div onClick={e => e.stopPropagation()} style={{
    width: "100%", maxWidth, maxHeight: "92vh",
    background: t.surface, color: t.ink, border: `1px solid ${t.line}`,
    borderRadius: R.xl, boxShadow: "0 30px 90px rgba(0,0,0,.45)",
    display: "flex", flexDirection: "column", overflow: "hidden",
    animation: "modalIn 380ms cubic-bezier(.34,1.2,.64,1) both",
  }}>{children}</div>
</div>
);
}

function CarouselViewer({ open, item, onClose, t }) {
const [idx, setIdx] = useState(0);
useEffect(() => { setIdx(0); }, [item?.id]);
useEffect(() => {
if (!open || !item) return;
const onKey = (e) => {
  if (e.key === "ArrowLeft") setIdx(i => Math.max(0, i-1));
  if (e.key === "ArrowRight") setIdx(i => Math.min((item.images?.length||1)-1, i+1));
};
document.addEventListener("keydown", onKey);
return () => document.removeEventListener("keydown", onKey);
}, [open, item]);
if (!item) return null;
const imgs = item.images || [];
const total = imgs.length;
const hasImg = total > 0;
const color = colorOf(item.category, t.isDark);

return (
<Modal open={open} onClose={onClose} t={t} maxWidth={1100}>
  <div style={{ padding: "16px 24px", borderBottom: `1px solid ${t.line}`,
                display: "flex", justifyContent: "space-between", alignItems: "center" }}>
    <div style={{ display: "flex", alignItems: "center", gap: 14 }}>
      <span style={{ width: 8, height: 8, background: color, borderRadius: "50%" }}/>
      <span style={{ color, fontSize: 10.5, letterSpacing: "0.22em",
                     textTransform: "uppercase", fontWeight: 600 }}>{item.categoryLabel}</span>
      <span style={{ color: t.inkMute, fontSize: 13 }}>·</span>
      <span style={{ fontFamily: TYPE.serif, fontSize: 17, color: t.ink, whiteSpace: "pre-line" }}>
        {item.title.replace(/\n/g, " ")}
      </span>
    </div>
    <button onClick={onClose} className="x-btn" style={{
      width: 34, height: 34, background: "transparent", border: `1px solid ${t.line}`,
      borderRadius: R.sm, color: t.ink, display: "flex", alignItems: "center", justifyContent: "center",
    }} aria-label="Fermer">
      <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.4">
        <line x1="2" y1="2" x2="12" y2="12"/><line x1="12" y1="2" x2="2" y2="12"/>
      </svg>
    </button>
  </div>

  <div style={{
    flex: 1, padding: 32, background: t.surface2,
    display: "flex", alignItems: "center", justifyContent: "center",
    position: "relative", minHeight: 460,
  }}>
    {hasImg ? (
      <img key={imgs[idx]} src={imgs[idx]} alt="" style={{
        maxWidth: "100%", maxHeight: "70vh", objectFit: "contain",
        boxShadow: t.shadow, border: `1px solid ${t.line}`, borderRadius: R.md,
        animation: "fadeIn 280ms ease both",
      }}/>
    ) : (
      <div style={{ textAlign: "center", color: t.inkMute }}>
        <div style={{ width: 360, height: 240, margin: "0 auto" }}>
          <ArtPattern variant={item.artVariant ?? 0} color={color} opacity={0.6}/>
        </div>
        <div style={{ marginTop: 14, fontSize: 13 }}>
          Carrousel à venir — ajoutez des images dans{" "}
          <code style={{ fontFamily: TYPE.mono, fontSize: 11.5,
                         background: t.surface, padding: "2px 6px", borderRadius: R.sm }}>
            public/content/{item.category}/{[...(item.topicPath||[]), item.slug].join("/")}/
          </code>
        </div>
      </div>
    )}
    {hasImg && total > 1 && (
      <>
        <button onClick={() => setIdx(i => Math.max(0, i-1))} disabled={idx === 0}
          className="carou-arrow" style={carouArrow(t, "left")} aria-label="Précédent">
          <svg width="18" height="14" viewBox="0 0 18 14" fill="none" stroke="currentColor" strokeWidth="1.5">
            <polyline points="8,1 1,7 8,13"/><line x1="1" y1="7" x2="17" y2="7"/>
          </svg>
        </button>
        <button onClick={() => setIdx(i => Math.min(total-1, i+1))} disabled={idx === total-1}
          className="carou-arrow" style={carouArrow(t, "right")} aria-label="Suivant">
          <svg width="18" height="14" viewBox="0 0 18 14" fill="none" stroke="currentColor" strokeWidth="1.5">
            <line x1="1" y1="7" x2="17" y2="7"/><polyline points="10,1 17,7 10,13"/>
          </svg>
        </button>
      </>
    )}
  </div>

  {hasImg && (
    <div style={{
      padding: "12px 24px", borderTop: `1px solid ${t.line}`,
      display: "flex", justifyContent: "space-between", alignItems: "center",
      fontFamily: TYPE.mono, fontSize: 11, color: t.inkMute,
    }}>
      <span>{String(idx+1).padStart(2,"0")} / {String(total).padStart(2,"0")}</span>
      <div style={{ display: "flex", gap: 4 }}>
        {imgs.map((_, i) => (
          <button key={i} onClick={() => setIdx(i)} aria-label={`Slide ${i+1}`} style={{
            width: i === idx ? 22 : 8, height: 2, background: i === idx ? t.ink : t.line,
            border: 0, padding: 0, borderRadius: 1, transition: "width 220ms",
          }}/>
        ))}
      </div>
      <span style={{ letterSpacing: "0.06em", textTransform: "uppercase" }}>← → · ESC</span>
    </div>
  )}
</Modal>
);
}
const carouArrow = (t, side) => ({
position: "absolute", [side]: 22, top: "50%", transform: "translateY(-50%)",
width: 42, height: 42, background: t.surface, border: `1px solid ${t.line}`,
borderRadius: "50%", color: t.ink, display: "flex", alignItems: "center", justifyContent: "center",
});

/* ---------- Finder Explorer (Tree + Columns) ---------- */

const trafficDot = (color, passive=false) => ({
width: 10, height: 10, borderRadius: "50%", background: color, border: 0, padding: 0,
cursor: passive ? "default" : "pointer", flexShrink: 0,
});

function ViewToggle({ active, onClick, t, label, icon }) {
return (
<button onClick={onClick} style={{
  padding: "4px 10px", fontSize: 11, fontWeight: 500,
  background: active ? t.surface : "transparent",
  color: active ? t.ink : t.inkMute,
  border: active ? `1px solid ${t.line}` : "1px solid transparent",
  borderRadius: R.sm, display: "inline-flex", alignItems: "center", gap: 5,
  transition: "all 160ms", boxShadow: active ? t.shadow : "none",
}}>{icon}<span>{label}</span></button>
);
}

function FinderExplorer({ open, onClose, onOpenItem, t }) {
const { tree, items, ready } = useContent();
const [view, setView] = useState("tree");
const [selectedId, setSelectedId] = useState(null);
const [path, setPath] = useState([]);
useEffect(() => { if (open) { setSelectedId(null); setPath([]); } }, [open]);

const selectedConcept = useMemo(() => {
  if (!selectedId) return null;
  return items.find(it => it.id === selectedId) || null;
}, [selectedId, items]);

const breadcrumb = path.length
  ? path.map(p => p.charAt(0).toUpperCase() + p.slice(1).replace(/-/g, " ")).join(" / ")
  : null;

return (
<Modal open={open} onClose={onClose} t={t} maxWidth={1200}>
  {/* Barre titre style macOS — trafic lights uniquement ici */}
  <div style={{
    height: 48, padding: "0 16px",
    display: "flex", alignItems: "center", justifyContent: "space-between",
    background: t.isDark ? t.surface2 : "#f9f9f8",
    borderBottom: `1px solid ${t.line}`,
    flexShrink: 0,
  }}>
    {/* Traffic lights */}
    <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
      <button onClick={onClose} aria-label="Fermer" style={trafficDot("#ff5f57")}/>
      <span style={trafficDot("#febc2e", true)}/>
      <span style={trafficDot("#28c840", true)}/>
    </div>
    {/* Breadcrumb centré */}
    <div style={{ position: "absolute", left: "50%", transform: "translateX(-50%)",
                  display: "flex", alignItems: "center", gap: 6 }}>
      <FolderIcon size={12} color={t.accent}/>
      <span style={{ fontFamily: TYPE.mono, fontSize: 11, color: t.inkSoft }}>
        lawandeconomics{breadcrumb ? " / " + breadcrumb : ""}
      </span>
    </div>
    {/* Toggle vue */}
    <div style={{ display: "flex", gap: 2, padding: 3,
                  background: t.isDark ? t.gutter : "#eeeeec",
                  borderRadius: R.md, border: `1px solid ${t.line}` }}>
      <ViewToggle active={view === "tree"} onClick={() => setView("tree")} t={t} label="Arbre"
        icon={<svg width="12" height="12" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.4">
          <path d="M3 2 h2 v2 h2 v2 h2 v2 h2" strokeLinecap="round"/><line x1="3" y1="2" x2="3" y2="12" strokeLinecap="round"/>
        </svg>}/>
      <ViewToggle active={view === "columns"} onClick={() => setView("columns")} t={t} label="Colonnes"
        icon={<svg width="12" height="12" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.4">
          <rect x="1" y="2" width="3" height="10" rx="0.5"/><rect x="5.5" y="2" width="3" height="10" rx="0.5"/>
          <rect x="10" y="2" width="3" height="10" rx="0.5"/>
        </svg>}/>
    </div>
  </div>

  {/* Corps */}
  <div style={{ display: "grid",
                gridTemplateColumns: view === "tree" ? "300px 1fr" : "auto 1fr",
                height: 560, overflow: "hidden", flex: 1 }}>
    {view === "tree" ? (
      <TreeView t={t} tree={tree} selectedId={selectedId} setSelectedId={setSelectedId}/>
    ) : (
      <ColumnsView t={t} tree={tree} items={items} selectedId={selectedId}
        setSelectedId={setSelectedId} path={path} setPath={setPath}/>
    )}
    <FinderPreview t={t} concept={selectedConcept}
      onOpen={(c) => { onOpenItem(c); onClose(); }}/>
  </div>
</Modal>
);
}

/* Tree view */
function TreeView({ t, tree, selectedId, setSelectedId }) {
return (
<div style={{
  borderRight: `1px solid ${t.line}`,
  background: t.isDark ? t.surface : "#fcfcfb",
  display: "flex", flexDirection: "column", overflow: "hidden",
}}>
  <div style={{
    padding: "8px 12px 6px", borderBottom: `1px solid ${t.lineSoft}`,
    display: "flex", alignItems: "center", gap: 6, flexShrink: 0,
  }}>
    <span style={{ fontSize: 9, letterSpacing: "0.2em", textTransform: "uppercase",
                   fontWeight: 600, color: t.inkMute, fontFamily: TYPE.mono }}>explorer</span>
  </div>
  <div style={{ overflowY: "auto", padding: "4px 0 12px", userSelect: "none" }}>
    {CATEGORIES.map((c, idx) => (
      <TreeNode key={c.key} name={c.label} node={tree[c.key]} category={c.key}
        depth={0} t={t} selectedId={selectedId} setSelectedId={setSelectedId}
        defaultOpen={false} isLast={idx === CATEGORIES.length - 1} parentConnectors={[]}/>
    ))}
  </div>
</div>
);
}

function TreeNode({ name, node, category, depth, t, selectedId, setSelectedId, defaultOpen, isConcept = false, concept = null, isLast = false, parentConnectors = [] }) {
const [open, setOpen] = useState(!!defaultOpen);
const [hover, setHover] = useState(false);
const color = colorOf(category, t.isDark);
const children = node?._children || {};
const childKeys = Object.keys(children).sort((a, b) => {
  const ac = children[a]._concept ? 1 : 0, bc = children[b]._concept ? 1 : 0;
  return ac !== bc ? ac - bc : a.localeCompare(b);
});
const hasChildren = childKeys.length > 0;
const active = isConcept && selectedId === concept?.id;
const INDENT = 14, BASE = 8;

return (
<div>
  <button
    onClick={() => {
      if (isConcept) { setSelectedId(concept.id); }
      else if (hasChildren) { setOpen(o => !o); }
    }}
    onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
    style={{
      width: "100%", textAlign: "left", border: 0, cursor: "pointer",
      background: active ? `${color}15` : hover ? (t.isDark ? t.lineSoft : "#f0f0ee") : "transparent",
      paddingTop: 3, paddingBottom: 3, paddingRight: 10,
      paddingLeft: BASE + depth * INDENT,
      display: "flex", alignItems: "center", gap: 4,
      transition: "background 100ms", position: "relative",
    }}
  >
    {/* Lignes verticales connectrices */}
    {parentConnectors.map((show, i) => show && (
      <div key={i} style={{
        position: "absolute", left: BASE + i * INDENT + 7,
        top: 0, bottom: 0, width: 1,
        background: t.isDark ? t.line : "#dddddA",
      }}/>
    ))}

    {/* Chevron */}
    <div style={{ width: 12, height: 12, flexShrink: 0, display: "flex", alignItems: "center", justifyContent: "center" }}>
      {!isConcept && hasChildren ? (
        <svg width="5" height="8" viewBox="0 0 5 8" fill="none"
             stroke={hover || open ? color : t.inkMute} strokeWidth="1.5"
             strokeLinecap="round" strokeLinejoin="round"
             style={{ transform: open ? "rotate(90deg)" : "none", transition: "transform 180ms, stroke 120ms" }}>
          <path d="M1 1L4 4L1 7"/>
        </svg>
      ) : <span style={{ width: 5, display: "inline-block" }}/>}
    </div>

    {/* Icône */}
    {isConcept
      ? <FileIcon size={12} color={active ? color : (hover ? color : `${color}88`)}/>
      : <FolderIcon size={13} color={open || hover ? color : `${color}99`}/>
    }

    {/* Nom */}
    <span style={{
      flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
      fontWeight: depth === 0 ? 500 : 400,
      color: active ? color : (hover ? t.ink : depth === 0 ? t.ink : t.inkSoft),
      fontSize: depth === 0 ? 12 : 11.5,
      fontFamily: TYPE.sans,
      transition: "color 100ms",
    }}>
      {name}
    </span>

    {/* Point coloré au survol */}
    {!active && (
      <div style={{
        width: 4, height: 4, borderRadius: "50%", background: color, marginRight: 2, flexShrink: 0,
        opacity: hover ? 0.8 : 0, transform: hover ? "scale(1)" : "scale(0)",
        transition: "opacity 150ms, transform 150ms",
      }}/>
    )}
  </button>

  {/* Enfants */}
  {!isConcept && hasChildren && (
    <div style={{
      overflow: "hidden",
      maxHeight: open ? "9999px" : 0,
      transition: "max-height 300ms cubic-bezier(.4,0,.2,1)",
    }}>
      {childKeys.map((k, idx) => {
        const child = children[k];
        const childIsConcept = !!child._concept;
        const childIsLast = idx === childKeys.length - 1;
        const childName = childIsConcept
          ? child._concept.title.replace(/\n/g, " ")
          : k.charAt(0).toUpperCase() + k.slice(1).replace(/-/g, " ");
        return (
          <TreeNode key={k} name={childName} node={child} category={category}
            depth={depth + 1} t={t} selectedId={selectedId} setSelectedId={setSelectedId}
            isConcept={childIsConcept} concept={child._concept || null}
            isLast={childIsLast} parentConnectors={[...parentConnectors, !isLast]}/>
        );
      })}
    </div>
  )}
</div>
);
}

/* Columns view */
function ColumnsView({ t, tree, items, selectedId, setSelectedId, path, setPath }) {
const buildEntries = useCallback((node, catKey) => {
  const children = node?._children || {};
  return Object.values(children).map(child => {
    const isConcept = !!child._concept;
    const hasSubTopics = !isConcept && Object.values(child._children || {}).some(c => !c._concept);
    return {
      key: child._key,
      label: isConcept
        ? child._concept.title.replace(/\n/g, " ")
        : child._key.charAt(0).toUpperCase() + child._key.slice(1).replace(/-/g, " "),
      type: isConcept ? "concept" : "topic",
      category: catKey,
      concept: child._concept || null,
      hasSubTopics,
      childCount: isConcept ? 0 : Object.keys(child._children || {}).length,
    };
  }).sort((a, b) => {
    if (a.type !== b.type) return a.type === "topic" ? -1 : 1;
    return a.label.localeCompare(b.label);
  });
}, []);

const columns = useMemo(() => {
  const cols = [];
  cols.push({
    title: "Catégories",
    entries: CATEGORIES.map(c => ({
      key: c.key, label: c.label, type: "category", category: c.key,
      count: items.filter(it => it.category === c.key).length,
      hasSubTopics: true, concept: null,
    })),
  });
  let node = tree;
  for (let depth = 0; depth < path.length; depth++) {
    const seg = path[depth];
    if (!node[seg]) break;
    const catKey = path[0];
    const entries = buildEntries(node[seg], catKey);
    if (entries.length > 0) {
      const hasFolders = entries.some(e => e.type === "topic");
      cols.push({ title: depth === 0 ? "Sujets" : (hasFolders ? "Sous-thèmes" : "Concepts"), entries });
    }
    node = node[seg]._children || {};
  }
  return cols;
}, [tree, path, items, buildEntries]);

const onClickEntry = (depth, entry) => {
  if (entry.type === "concept") {
    setSelectedId(entry.concept.id);
    return; // Ne pas étendre le chemin pour les concepts
  }
  const newPath = [...path.slice(0, depth), entry.key];
  setSelectedId(null);
  setPath(newPath);
};

return (
<div style={{ display: "flex", overflow: "auto", borderRight: `1px solid ${t.line}` }}>
  {columns.map((col, depth) => (
    <FinderColumn key={depth} col={col} t={t} depth={depth}
      selectedKey={path[depth] || null}
      onClick={(entry) => onClickEntry(depth, entry)}/>
  ))}
</div>
);
}

function FinderColumn({ col, t, depth, selectedKey, onClick }) {
return (
<div style={{
  width: 210, flexShrink: 0,
  borderRight: `1px solid ${t.line}`,
  background: t.isDark ? (depth === 0 ? t.surface2 : t.surface) : (depth === 0 ? "#f5f5f3" : "#fcfcfb"),
  display: "flex", flexDirection: "column", overflow: "hidden",
}}>
  <div style={{
    padding: "8px 12px", fontSize: 9, letterSpacing: "0.2em",
    textTransform: "uppercase", fontWeight: 700, color: t.inkMute,
    fontFamily: TYPE.mono, borderBottom: `1px solid ${t.lineSoft}`,
    flexShrink: 0,
  }}>{col.title}</div>
  <div style={{ flex: 1, overflowY: "auto" }}>
    {col.entries.length === 0 ? (
      <div style={{ padding: "20px 12px", fontSize: 12, color: t.inkMute }}>Aucun élément.</div>
    ) : col.entries.map(e => {
      const active = e.key === selectedKey;
      const color = colorOf(e.category, t.isDark);
      return (
        <button key={e.key} onClick={() => onClick(e)} className="finder-row" style={{
          width: "100%", textAlign: "left", border: 0,
          padding: "7px 12px", display: "flex", alignItems: "center", gap: 8,
          background: active ? (t.isDark ? `${color}22` : `${color}12`) : "transparent",
          color: active ? color : t.ink,
          borderBottom: `1px solid ${t.isDark ? t.lineSoft : "#efefec"}`,
          transition: "background 120ms",
        }}>
          {e.type === "concept"
            ? <FileIcon size={12} color={active ? color : `${color}99`}/>
            : <FolderIcon size={13} color={active ? color : `${color}aa`}/>
          }
          <span style={{
            flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
            fontSize: 12.5, fontFamily: TYPE.sans,
            fontWeight: active ? 500 : 400,
          }}>
            {e.label}
          </span>
          {e.type !== "concept" && e.childCount > 0 && (
            <span style={{ fontSize: 10, color: active ? `${color}99` : t.inkMute,
                           fontFamily: TYPE.mono, flexShrink: 0 }}>{e.childCount}</span>
          )}
          {e.type !== "concept" && (
            <svg width="5" height="8" viewBox="0 0 5 8" fill="none"
                 stroke={active ? color : t.inkMute} strokeWidth="1.3" strokeLinecap="round">
              <polyline points="1,1 4,4 1,7"/>
            </svg>
          )}
        </button>
      );
    })}
  </div>
</div>
);
}

function FinderPreview({ t, concept, onOpen }) {
if (!concept) return (
<div style={{
  padding: 36,
  background: t.isDark ? t.surface : "#fcfcfb",
  color: t.inkMute,
  display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center",
  gap: 10, textAlign: "center",
}}>
  <FolderIcon size={32} color={t.inkMute}/>
  <div style={{ fontSize: 12.5 }}>Sélectionnez un concept</div>
</div>
);
const color = colorOf(concept.category, t.isDark);
const imgs = concept.images || [];
return (
<div style={{
  padding: "20px 24px",
  background: t.isDark ? t.surface : "#fcfcfb",
  overflow: "auto", display: "flex", flexDirection: "column", gap: 12,
}}>
  {/* Catégorie */}
  <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
    <div style={{ width: 7, height: 7, borderRadius: "50%", background: color, flexShrink: 0 }}/>
    <span style={{ color, fontSize: 9.5, letterSpacing: "0.22em",
                   textTransform: "uppercase", fontWeight: 700, fontFamily: TYPE.mono }}>
      {CAT_BY_KEY[concept.category]?.label}
    </span>
  </div>

  {/* Titre */}
  <h3 style={{ fontFamily: TYPE.serif, fontWeight: 400, fontSize: 22, lineHeight: 1.18,
               letterSpacing: "-0.012em", margin: 0, whiteSpace: "pre-line", color: t.ink }}>
    {concept.title}
  </h3>

  {/* Résumé */}
  {concept.summary && (
    <p style={{ margin: 0, color: t.inkSoft, fontSize: 12.5, lineHeight: 1.6 }}>{concept.summary}</p>
  )}

  {/* Aperçu image */}
  <div style={{
    aspectRatio: "1.6/1", background: t.isDark ? t.surface2 : "#f4f4f0",
    border: `1px solid ${t.line}`, borderRadius: R.md,
    display: "flex", alignItems: "center", justifyContent: "center", overflow: "hidden",
  }}>
    {imgs.length > 0 ? (
      <img src={imgs[0]} alt="" style={{ width: "100%", height: "100%", objectFit: "contain" }}/>
    ) : (
      <div style={{ width: "65%", height: "65%" }}>
        <ArtPattern variant={concept.artVariant ?? 0} color={color} opacity={0.5}/>
      </div>
    )}
  </div>

  {/* Miniatures si plusieurs slides */}
  {imgs.length > 1 && (
    <div>
      <div style={{ fontSize: 9, color: t.inkMute, letterSpacing: "0.16em",
                    textTransform: "uppercase", fontWeight: 600, marginBottom: 8, fontFamily: TYPE.mono }}>
        {imgs.length} slides
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 5 }}>
        {imgs.slice(0, 8).map((src, i) => (
          <div key={i} style={{
            aspectRatio: "1/1",
            background: t.isDark ? t.surface2 : "#f4f4f0",
            border: `1px solid ${t.line}`, borderRadius: R.sm, overflow: "hidden",
          }}>
            <img src={src} alt="" style={{ width: "100%", height: "100%", objectFit: "cover" }}/>
          </div>
        ))}
      </div>
    </div>
  )}

  {/* CTA */}
  <div style={{ marginTop: "auto", paddingTop: 14, borderTop: `1px solid ${t.line}` }}>
    <button onClick={() => onOpen(concept)} style={{
      background: t.ink, color: t.surface, border: 0, padding: "10px 16px",
      fontSize: 10, letterSpacing: "0.2em", textTransform: "uppercase", fontWeight: 600,
      borderRadius: R.md, display: "inline-flex", alignItems: "center", gap: 10,
      cursor: "pointer",
    }}>
      <span>Ouvrir le carrousel</span>
      <svg width="14" height="8" viewBox="0 0 16 8" fill="none" stroke="currentColor" strokeWidth="1.3">
        <line x1="0" y1="4" x2="12" y2="4"/><polyline points="9,1 12,4 9,7"/>
      </svg>
    </button>
  </div>
</div>
);
}

/* ============================================================
13. APP
============================================================ */

function App() {
const [isDark, setIsDark] = useState(() =>
typeof window !== "undefined" && window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
);
const t = isDark ? DARK : LIGHT;
const [openItem, setOpenItem] = useState(null);
const [finderOpen, setFinderOpen] = useState(false);

const css = `
@keyframes fadeIn { from {opacity:0} to {opacity:1} }
@keyframes fadeUp { from {opacity:0; transform: translateY(8px)} to {opacity:1; transform:translateY(0)} }
@keyframes lineUp { from {opacity:0; transform: translateY(14px)} to {opacity:1; transform:translateY(0)} }
@keyframes barIn  { from {transform:scaleX(0)} to {transform:scaleX(1)} }
@keyframes cardIn { from {opacity:0; transform: translateY(14px)} to {opacity:1; transform:translateY(0)} }
@keyframes fadeInUp { from {opacity:0; transform: translateY(8px) scale(.99)} to {opacity:1; transform: translateY(0) scale(1)} }
@keyframes modalIn { from {opacity:0; transform: translateY(20px) scale(.98)} to {opacity:1; transform: translateY(0) scale(1)} }
@keyframes pulse { 0%,100% {opacity:1; transform: scale(1)} 50% {opacity:.5; transform: scale(1.15)} }
@keyframes auroraFloat1 { 0%,100% {transform: translate(0,0) scale(1)} 50% {transform: translate(8vw, 6vh) scale(1.1)} }
@keyframes auroraFloat2 { 0%,100% {transform: translate(0,0) scale(1)} 50% {transform: translate(-6vw, -4vh) scale(1.08)} }
@keyframes auroraFloat3 { 0%,100% {transform: translate(0,0) scale(1)} 50% {transform: translate(-5vw, 5vh) scale(1.05)} }
@keyframes hBlob1 { 0%,100% {transform: translate(0,0) scale(1)} 50% {transform: translate(-4%, 7%) scale(1.07)} }
@keyframes hBlob2 { 0%,100% {transform: translate(0,0) scale(1)} 50% {transform: translate(5%, -5%) scale(1.05)} }
@keyframes orbPulse { 0%,100% {opacity:0.75; transform:scale(1)} 50% {opacity:1; transform:scale(1.14)} }
@keyframes orbRing  { 0% {transform:scale(1)} 100% {transform:scale(1.07)} }

body, section, header, footer, nav, div, a, p, h1, h2, h3, span, button {
  transition: background-color 380ms, border-color 380ms, color 380ms;
}
.nav-link::after {
  content:""; position:absolute; left:0; bottom:-2px; height:1px; width:0;
  background:${t.accent}; transition: width 280ms cubic-bezier(.4,0,.2,1);
}
.nav-link:hover { color:${t.accent}; }
.nav-link:hover::after { width:100%; }
a:hover .logo-line { transform: scaleX(1) !important; }
.hdr-icon { transition: background 200ms, color 200ms; }
.hdr-icon:hover { background:${t.lineSoft}; color:${t.accent}; }
.nav-btn:hover { background:${t.lineSoft}; }
.nav-btn[disabled] { opacity:.3; cursor:not-allowed; }
.cta-primary .cta-arrow { transition: transform 240ms cubic-bezier(.4,0,.2,1); }
.cta-primary:hover .cta-arrow { transform: translateX(6px); }
.cta-secondary { transition: background 200ms; }
.cta-secondary:hover { background:${t.lineSoft}; }
.filter-chip:hover { border-color:${t.ink} !important; }
.carou-arrow:hover:not([disabled]) { background:${t.ink}; color:${t.surface}; border-color:${t.ink}; }
.carou-arrow[disabled] { opacity:.25; cursor:not-allowed; }
.x-btn:hover { background:${t.ink}; color:${t.surface}; border-color:${t.ink}; }
.soc:hover { opacity:1 !important; color:${t.accent}; border-color:${t.accent}; }
.footer-link:hover { opacity:1 !important; color:${t.accent}; }
.finder-row:hover:not([data-active="true"]) { background:${t.lineSoft} !important; }

@media (max-width: 980px) {
  .pub-card { font-size: inherit; }
}
`;

return (
<ContentProvider>
  <style>{css}</style>
  <AuroraBackground t={t}/>
  <div id="top" style={{
    display: "grid", gridTemplateColumns: "44px 1fr 44px",
    minHeight: "100vh", color: t.ink, position: "relative", zIndex: 1,
  }}>
    <div style={{ position: "sticky", top: 0, height: "100vh", overflow: "hidden",
                  background: isDark ? t.gutter : "#ffffff", borderRight: `1px solid ${t.line}` }}>
      <PlusPattern density={11} opacity={isDark ? 0.05 : 0.08} color={t.ink}/>
    </div>

    <main style={{
      background: t.surface,
    }}>
      <Header t={t} isDark={isDark}
        onToggleTheme={() => setIsDark(d => !d)}
        onOpenFinder={() => setFinderOpen(true)}/>
      <Hero t={t} isDark={isDark}/>
      <Categories t={t}/>
      <Publications t={t} onOpenItem={setOpenItem}/>
      <Manifeste t={t}/>
      <Footer t={t}/>
    </main>

    <div style={{ position: "sticky", top: 0, height: "100vh", overflow: "hidden",
                  background: isDark ? t.gutter : "#ffffff", borderLeft: `1px solid ${t.line}` }}>
      <PlusPattern density={11} opacity={isDark ? 0.05 : 0.08} color={t.ink}/>
    </div>

    <CarouselViewer open={!!openItem} item={openItem} onClose={() => setOpenItem(null)} t={t}/>
    <FinderExplorer open={finderOpen} onClose={() => setFinderOpen(false)}
      onOpenItem={setOpenItem} t={t}/>
  </div>
</ContentProvider>
);
}

ReactDOM.createRoot(document.getElementById("root")).render(<App/>);