// Klyvio — Shared UI pieces (dark theme Klyvio) const { useState, useEffect, useRef, useMemo, useCallback } = React; /* ———————————————————— LOGO ———————————————————— */ function Logo({ size = 22 }) { const h = Math.round(size * 2); return ( Klyvio ); } /* ———————————————————— PAGE CHROME ———————————————————— */ function AppChrome({ current, onNav, children, hideNav = false }) { const isMobile = typeof window !== "undefined" && window.innerWidth <= 960; const items = [ { id: "landing", label: "Accueil", icon: "home" }, { id: "onboarding", label: "Démarrer l\'audit", icon: "sparkles" }, { id: "audit", label: "Questionnaire", icon: "compass" }, { id: "loading", label: "Analyse", icon: "bolt" }, { id: "report", label: "Rapport", icon: "fileText" }, ]; return (
{!hideNav && (
{!isMobile && ( Beta )}
)}
{children}
); } /* ———————————————————— CIRCULAR SCORE ———————————————————— */ function ScoreDial({ value = 0, size = 220, stroke = 14, label = "Maturité IA", sub }) { const r = (size - stroke) / 2; const c = 2 * Math.PI * r; const offset = c - (value / 100) * c; const bandColor = value < 30 ? "var(--danger-500)" : value < 55 ? "var(--warn-500)" : value < 75 ? "var(--accent)" : "var(--success-500)"; const gradId = `dial-grad-${size}-${Math.round(value)}`; return (
{Math.round(value)}
{label}
{sub &&
{sub}
}
); } /* ———————————————————— RADAR ———————————————————— */ function RadarChart({ data, size = 320, max = 100 }) { const cx = size / 2; const cy = size / 2; const r = size / 2 - 44; const n = data.length; const angle = (i) => (Math.PI * 2 * i) / n - Math.PI / 2; const point = (i, v) => { const a = angle(i); const rr = (v / max) * r; return [cx + Math.cos(a) * rr, cy + Math.sin(a) * rr]; }; const polyPoints = data.map((d, i) => point(i, d.value).join(",")).join(" "); const gridLevels = [0.25, 0.5, 0.75, 1]; return ( {gridLevels.map((lvl, k) => ( { const a = angle(i); return [cx + Math.cos(a) * r * lvl, cy + Math.sin(a) * r * lvl].join(","); }).join(" ")} fill="none" stroke="rgba(0,174,239,0.15)" strokeWidth={k === gridLevels.length - 1 ? 1.2 : 0.8} strokeDasharray={k === gridLevels.length - 1 ? "" : "3 3"} /> ))} {data.map((_, i) => { const [x, y] = point(i, max); return ; })} {data.map((d, i) => { const [x, y] = point(i, d.value); return ; })} {data.map((d, i) => { const a = angle(i); const lx = cx + Math.cos(a) * (r + 26); const ly = cy + Math.sin(a) * (r + 26); const anchor = Math.abs(Math.cos(a)) < 0.3 ? "middle" : Math.cos(a) > 0 ? "start" : "end"; return ( {d.label} {Math.round(d.value)}/100 ); })} ); } /* ———————————————————— STAT CARD ———————————————————— */ function StatCard({ eyebrow, value, unit, sub, icon, tone = "default", trend, className = "" }) { const tones = { default: { bg: "var(--bg2)", accent: "var(--accent)", text: "var(--white)" }, accent: { bg: "linear-gradient(135deg, rgba(0,174,239,0.2), rgba(0,136,204,0.15))", accent: "var(--accent)", text: "var(--white)" }, dark: { bg: "var(--bg)", accent: "var(--accent)", text: "var(--white)" }, soft: { bg: "var(--accent-dim)", accent: "var(--accent)", text: "var(--white)" }, }; const t = tones[tone] || tones.default; return (
{eyebrow}
{icon && (
)}
{value}
{unit &&
{unit}
}
{(sub || trend) && (
{trend && ( 0 ? "var(--success-500)" : "var(--danger-500)" }}> 0 ? "trendUp" : "trendDown"} size={12} /> {trend > 0 ? "+" : ""}{trend}% )} {sub}
)}
); } /* ———————————————————— UTILS ———————————————————— */ function formatEur(n) { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n > 10_000_000 ? 0 : 1)}\u202fM€`; if (n >= 1_000) return `${Math.round(n / 100) / 10}\u202fk€`; return `${Math.round(n)}\u202f€`; } function formatHours(n) { if (n >= 1000) return `${Math.round(n / 100) / 10}\u202fk\u202fh`; return `${Math.round(n)}\u202fh`; } Object.assign(window, { Logo, AppChrome, ScoreDial, RadarChart, StatCard, formatEur, formatHours });