{
  "$schema": "https://da.zlef.fr/design.json",
  "name": "ZLEF Direction Artistique",
  "version": "3.2.0",
  "updated": "2026-07-14",
  "summary": "Dark, atmospheric, minimal design system for all zlef.fr properties. Two deep, desaturated accents — olive leaf (#3E4618) and vintage grape (#4C3B4D) — over a near-black canvas. Lightly rounded corners (3–8px), flat crisp components (no gradients, no glow), large legible type. No light theme, no web fonts, no blue.",
  "canonical_stylesheet": "https://da.zlef.fr/tokens.css",
  "usage": "Import https://da.zlef.fr/tokens.css and use the CSS variables (--zl-*) and component classes (.zl-btn, .zl-card, .zl-badge, .zl-input, …). Components reference palette-agnostic role variables (--zl-accent* = primary, --zl-accent2* = secondary); raw ramps are exposed as --zl-olive* / --zl-grape*. Editing tokens.css changes the language everywhere.",

  "principles": [
    "Dark only — the background is near-black #06060a; there is no light theme.",
    "Minimal & atmospheric — restraint over decoration; depth comes from a subtle particle field, haze and vignette (assets.zlef.fr/particles.js), not from heavy chrome.",
    "Two accents only — olive leaf is primary, vintage grape is secondary. Both are deep and desaturated. Everything else is neutral.",
    "Light rounding — corners are subtle: 3px chips/badges, 5px buttons/inputs, 8px cards. Never pill-shaped UI, never heavily rounded.",
    "Flat, crisp components — solid fills, 1px borders, no gradients, no coloured glow. Hover shifts the fill one shade; it never jumps or blooms.",
    "Legible by default — body text sits at 17px and never drops below 16px. Generous line-height (1.6). Controls are ≥44px tall.",
    "Subtle motion — eases are smooth (cubic-bezier(.22,.61,.36,1)), durations 110–360ms, no bounce. Respect prefers-reduced-motion."
  ],

  "colors": {
    "surfaces": {
      "bg":        { "value": "#06060a", "use": "page background — the canonical near-black" },
      "surface-1": { "value": "#0e0e13", "use": "raised panels and cards" },
      "surface-2": { "value": "#15151c", "use": "inputs, nested panels" },
      "surface-3": { "value": "#1d1d25", "use": "hover / active raised state" }
    },
    "text": {
      "text":       { "value": "#e9eae2", "use": "primary copy (faint warm-neutral)" },
      "text-soft":  { "value": "#b6b7ad", "use": "secondary copy" },
      "text-muted": { "value": "#7d7e76", "use": "captions, labels, placeholders" },
      "text-faint": { "value": "#54554f", "use": "disabled / decorative" }
    },
    "accent_primary_olive_leaf": {
      "role": "PRIMARY accent — main CTAs, links, focus, active nav. Base #3E4618 fills solid surfaces; the soft tint is used for text/links on dark.",
      "deep":   { "value": "#2c3212", "var": "--zl-olive-deep" },
      "base":   { "value": "#3e4618", "var": "--zl-olive / --zl-accent" },
      "mid":    { "value": "#59642a", "var": "--zl-olive-mid" },
      "soft":   { "value": "#9dae50", "var": "--zl-olive-soft / --zl-accent-soft" },
      "bright": { "value": "#bdce74", "var": "--zl-olive-bright" }
    },
    "accent_secondary_vintage_grape": {
      "role": "SECONDARY accent — alternate CTA, secondary highlights, info. Base #4C3B4D fills solid surfaces; the soft tint is used for text/links on dark.",
      "deep":   { "value": "#382b39", "var": "--zl-grape-deep" },
      "base":   { "value": "#4c3b4d", "var": "--zl-grape / --zl-accent2" },
      "mid":    { "value": "#6b5470", "var": "--zl-grape-mid" },
      "soft":   { "value": "#b095b3", "var": "--zl-grape-soft / --zl-accent2-soft" },
      "bright": { "value": "#cdb6cf", "var": "--zl-grape-bright" }
    },
    "semantic": {
      "success": { "value": "#6f9a3a", "note": "leafy green — sibling of olive" },
      "warning": { "value": "#c79a3e", "note": "muted amber" },
      "danger":  { "value": "#c2566a", "note": "dusty rose-red — the single error/destructive hue" },
      "info":    { "value": "#b095b3", "note": "info reuses grape-soft — never blue" }
    },
    "lines": {
      "line":        { "value": "rgba(255,255,255,.07)", "use": "default hairline" },
      "line-strong": { "value": "rgba(255,255,255,.13)", "use": "emphasised border / control outline" },
      "line-focus":  { "value": "rgba(157,174,80,.55)", "use": "focused control border (olive)" }
    },
    "atmospheric_only": {
      "haze-grape": { "value": "rgba(76,59,77,.10)", "use": "ONLY inside background haze gradients — never as a UI fill" }
    }
  },

  "banned": [
    "Light / white backgrounds or a light theme of any kind.",
    "Blue as an accent or link colour in UI chrome — use olive instead. (Data-series colours from zl-charts --zlc-cat-* are exempt.)",
    "Pure black #000000 fills for surfaces — use #06060a and the surface ramp.",
    "Full pills and systematically heavy rounding — keep rounding light (3–8px); circles stay circles.",
    "The 'AI-generated' aesthetic: gradients on buttons, coloured glows, scale/zoom on hover. Flat, crisp, one-shade hover shift.",
    "Body text below 16px; low-contrast grey-on-grey for primary copy; thin hairline text.",
    "Web-font dependencies — use the system sans stack and a monospace for code/data.",
    "Glossy gradients, skeuomorphism, bouncy/elastic motion.",
    "Generic framework defaults (unstyled Bootstrap/Material) that ignore these tokens."
  ],

  "typography": {
    "font_sans": "system-ui, -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif",
    "font_mono": "ui-monospace, \"SF Mono\", \"JetBrains Mono\", Menlo, Consolas, monospace",
    "scale": {
      "xs": "13px", "sm": "14.5px", "base": "17px", "lg": "19px",
      "xl": "22px", "2xl": "28px", "3xl": "clamp(30px,4vw,38px)", "4xl": "clamp(40px,6vw,56px)"
    },
    "body_size": "17px (minimum 16px anywhere)",
    "leading": { "body": 1.6, "tight": 1.15 },
    "weights": { "normal": 400, "medium": 550, "bold": 700 },
    "tracking": { "tight": "-0.02em (headings)", "wide": "0.14em (uppercase eyebrows/labels)" }
  },

  "spacing": { "base": "4px", "scale_px": [4, 8, 12, 16, 24, 32, 48, 64] },
  "radius":  { "note": "light rounding only", "sm": "3px (badges, chips, code)", "md": "5px (buttons, inputs)", "lg": "8px (cards)", "xl": "12px", "pill": "999px (reserve for genuinely circular elements)" },
  "elevation": {
    "shadow_sm": "0 1px 2px rgba(0,0,0,.4)",
    "shadow_md": "0 6px 22px rgba(0,0,0,.45)",
    "shadow_lg": "0 18px 50px rgba(0,0,0,.55)",
    "note": "Plain dark shadows only. No coloured glow on accents — flatness is intentional."
  },
  "motion": { "ease": "cubic-bezier(.22,.61,.36,1)", "durations_ms": { "fast": 110, "base": 170, "slow": 360 }, "rule": "subtle, no bounce; honour prefers-reduced-motion" },

  "components": {
    "button":  { "class": "zl-btn", "variants": ["zl-btn--primary (solid olive)", "zl-btn--secondary (solid grape)", "zl-btn--ghost (outline)", "zl-btn--destructive (solid rose-red — destructive MAIN action; fill --zl-danger, ink --zl-on-danger, hover --zl-danger-mid, active --zl-danger-deep)", "zl-btn--danger (outline rose-red — the QUIET destructive variant: table rows, toolbars)", "zl-btn--loading (+ zl-spinner--btn, disabled)"], "destructive_pairing": "Canonical destructive dialog: the solid zl-btn--destructive action + « Annuler » as zl-btn--secondary (or zl-btn--ghost on dense UIs). Never make a destructive action the olive primary.", "sizes": ["zl-btn--sm", "(default)", "zl-btn--lg"], "min_height": "44px", "style": "flat, lightly rounded (5px), 1px border; hover shifts fill one shade — no gradient, no glow, no scale; works identically as <button> or <a> (text-decoration reset)" },
    "input":   { "class": "zl-input", "siblings": ["zl-textarea", "zl-select", "zl-check", "zl-toggle"], "field_wrapper": "zl-field + zl-label + zl-hint", "min_height": "46px", "radius": "5px", "focus": "olive border + soft olive focus ring", "note": "toggle is a lightly-rounded rectangle, not a pill" },
    "select":  { "class": "zl-pick", "style": "generic custom select, customized end-to-end — shares every rule with zl-langpick in tokens.css (the flag span is simply optional markup): zl-pick__btn (current value + zl-pick__chev chevron) over a token-styled zl-pick__menu listbox of zl-pick__opt rows with a selected ✓ (zl-pick__check); aria-expanded on the root drives open state; full keyboard (Up/Down/Home/End/Enter/Esc), click-outside to close; sync aria-selected into a hidden input or a real <select> (live markup + JS on da.zlef.fr#champs)", "ios_exception": "On iOS keep the NATIVE <select class=\"zl-select\"> — the Apple HIG system picker is better touch UX than any custom menu. Detect: const isIOS = /iP(hone|ad|od)/.test(navigator.userAgent) || (navigator.maxTouchPoints > 1 && /Mac/.test(navigator.userAgent)); everywhere else use zl-pick." },
    "badge":   { "class": "zl-badge", "variants": ["zl-badge--accent (olive)", "zl-badge--accent2 (grape)", "zl-badge--success", "zl-badge--warning", "zl-badge--danger", "zl-badge--new (olive wash)", "zl-badge--beta (grape wash)", "zl-badge--wip (warning wash)", "zl-badge--draft (neutral, dashed 1px border)", "zl-badge--experimental (grape, dashed)", "zl-badge--live (success + built-in dot)", "zl-badge--deprecated (danger wash, muted)", "zl-badge--archived (faint text, transparent bg)"], "modifiers": ["zl-badge--dot", "zl-badge--sm (11px / 3px 7px)", "zl-badge--lg (14px / 7px 13px, radius 4px max)", "zl-badge--solid (solid fill + dark ink, ≥4.5:1 — high-signal chips only)"], "moods": "lifecycle gamme of a product/feature, strictly on-palette: new → beta → wip → draft → experimental → live → deprecated → archived", "radius": "3px (not a pill)" },
    "card":    { "class": "zl-card", "modifiers": ["zl-card--hover", "zl-card--accent (olive hairline)"], "parts": ["zl-card__title", "zl-card__body"], "radius": "8px" },
    "header":  { "class": "zl-header", "parts": ["zl-header__brand", "zl-header__nav", "zl-navlink", "zl-navlink--active (olive-soft)"], "style": "translucent, blurred, hairline bottom border; sticky top by default (z --zl-z-nav), flex-wraps on narrow screens; zl-header--static opts out", "variants": ["zl-header--hero (marketing/landing: transparent, no blur, no border, larger brand, non-sticky — sits over a hero/particle field)", "zl-header--compact (utility/tool: slim padding, smaller brand, solid surface-1, no blur, hairline; stays sticky)", "zl-header--editorial (magazine masthead: centered brand on its own line over a centered hairline-framed nav, generous vertical padding, non-sticky)"], "mobile": "all moods are intrinsically responsive (flex-wrap) — they adapt to their container and never overflow horizontally at 360px" },
    "footer":  { "class": "zl-footer", "style": "muted text, hairline top border, links hover to olive-soft", "variants": ["zl-footer--columns (sitemap: auto-fit grid of zl-footer__col link columns with zl-footer__col-title, collapses 4→2→1 with container width; zl-footer__bottom bar for © + legal)", "zl-footer--slim (one thin centered xs line, minimal padding)", "zl-footer--cta (conversion: zl-footer__cta headline + zl-footer__cta-sub + primary button on a flat olive wash — no gradient — above a classic zl-footer__bottom line)"], "mobile": "all moods are intrinsically responsive (grid auto-fit / flex-wrap / min()) — nothing overflows horizontally at 360px" },
    "link":    { "class": "zl-link", "modifiers": ["zl-link--quiet"], "style": "olive-soft text, faint olive underline that fills on hover; quiet variant stays neutral until hover" },
    "kbd":     { "class": "zl-kbd", "style": "mono 13px chip on surface-2, 2px bottom border for key relief" },
    "table":   { "class": "zl-table", "wrapper": "zl-table-wrap (rounded border + horizontal overflow + sticky header)", "parts": ["zl-num (mono tabular numerals, right-aligned)"], "style": "hairline rows, uppercase label headers, quiet row hover" },
    "tabs":    { "class": "zl-tabs (role=tablist)", "parts": ["zl-tab", "zl-tab--active or [aria-selected=true]"], "style": "underline indicator in olive-soft — never a filled pill" },
    "alert":   { "class": "zl-alert", "variants": ["zl-alert--info (grape, never blue)", "zl-alert--success", "zl-alert--warning", "zl-alert--danger"], "parts": ["zl-alert__title"], "layout": "title stacked on its own line ABOVE the body (like zl-card__title over __body) — never inline; direct-child <span>s display block so legacy title+body span markup stacks", "style": "semantic wash background + 3px semantic left border; copy says what to do next" },
    "tooltip": { "attr": "data-zl-tip", "style": "CSS-only tooltip on surface-3 with hairline; shows on hover AND :focus-visible; sits on --zl-z-tip" },
    "spinner": { "class": "zl-spinner", "modifiers": ["zl-spinner--lg", "zl-spinner--btn (inside .zl-btn--loading + disabled)"], "page_loader": "zl-loader-page — centered column (zl-spinner--lg over a short muted label) for route transitions / full-page loads; give it the height of the region it replaces (min-height 240px default)", "style": "2px ring, olive-soft head; keeps spinning under prefers-reduced-motion (it IS the status signal, just slower)", "use_cases": "form submit = inputs disabled + zl-btn--loading; full page / route change = zl-loader-page" },
    "skeleton": { "class": "zl-skeleton", "modifiers": ["zl-skeleton--title", "zl-skeleton--text", "zl-skeleton--block", "zl-skeleton--circle", "zl-skeleton--cell (one line inside a <td> — loading tables keep their real columns)", "zl-skeleton--chart (ghost bars, olive-tinted low opacity, hairline baseline — chart placeholder)"], "rule": "compose skeletons to mirror the final layout (no lone gray box); shimmer stops under prefers-reduced-motion (--chart included)", "use_cases": "table = zl-table rows of --cell; chart = card with --title + --chart; media gallery = cards of --block + --text lines" },
    "progress": { "class": "zl-progress > zl-progress__bar", "modifiers": ["zl-progress--indeterminate"], "style": "6px track on surface-3, olive-soft bar; set the bar width inline/JS for determinate", "use_cases": "upload = filename + percent label above the bar, ghost --sm cancel button below" },
    "empty":   { "class": "zl-empty", "parts": ["zl-empty__title"], "rule": "an empty screen is an invitation to act — say what to create and give the button" },
    "divider": { "class": "zl-divider" }
  },

  "loading_states_rule": "MANDATORY sitewide: nothing may feel frozen. Every async action (fetch, submit, navigation, long render) shows a visible state — spinner, skeleton mirroring the final layout, or progress bar — and buttons enter .zl-btn--loading + disabled while pending.",

  "background_field": {
    "script": "https://assets.zlef.fr/particles.js",
    "note": "Drop-in animated particle + constellation canvas with haze and vignette. Auto-loads analytics (track.js). Use on landing/marketing surfaces; keep content panels solid enough to stay legible above it. (Particle accent colours are managed in the shared asset, independent of these tokens.)"
  },

  "i18n": {
    "summary": "Every project ships keyed string dictionaries with English (default fallback) + French at minimum. The language selector is conditional on how many locales exist, and the chosen language is remembered sitewide across all *.zlef.fr properties.",
    "selector_visibility": {
      "two_locales_en_fr": "Show NO selector. Resolve the language silently from the browser (navigator.language / Accept-Language), English as fallback.",
      "three_or_more_locales": "Show ONE curated global selector: a dropdown whose options are '<flag emoji> <native language name>' (endonym), e.g. '🇬🇧 English', '🇫🇷 Français', '🇪🇸 Español'. Flag emoji THEN the language's own name — never flag-only, never English exonyms."
    },
    "persistence": {
      "scope": "sitewide — the choice follows the visitor across every *.zlef.fr property",
      "store": "cookie 'zl-lang' on domain '.zlef.fr', path '/', ~1 year. NOT localStorage (per-origin; will not share across subdomains).",
      "resolution_order": ["zl-lang cookie", "navigator.language", "en"],
      "note": "Logged-in users may also persist to their account, but the .zlef.fr cookie stays the cross-site source of truth."
    },
    "component": "zl-langpick",
    "styling": "Use the canonical .zl-langpick custom dropdown (CSS ships in tokens.css; markup + accessible JS shown live on da.zlef.fr), NOT a native <select> — native selects can't render flag emoji reliably (blank on Windows) nor be styled to the tokens. It is a button (current flag + native name + chevron) over a token-styled menu with a selected ✓, full keyboard support (Up/Down/Home/End/Enter/Esc) and click-outside-to-close. Place it in the header (or footer on minimal pages)."
  },

  "charts": {
    "summary": "Shared dependency-free SVG chart engine for every project that shows data. Import zl-charts.css + zl-charts.js AFTER tokens.css. Do NOT hand-roll chart SVG or add a charting library.",
    "stylesheet": "https://da.zlef.fr/zl-charts.css",
    "script": "https://da.zlef.fr/zl-charts.js",
    "usage_declarative": "Emit a container + inline JSON; the engine hydrates every [data-zlc] on load: <figure class=\"zlc\" data-zlc><script type=\"application/json\" class=\"zlc-spec\">{\"type\":\"bar\",\"x\":{\"values\":[…]},\"series\":[…]}</script></figure>",
    "usage_imperative": "ZLC.mount(el, spec)",
    "forms": ["bar (horizontal, stacked, emphasis)", "line", "area", "scatter", "heatmap", "timeline", "stat (tile: value+delta+sparkline)", "meter", "sparkline"],
    "spec_keys": {
      "type": "one of the forms",
      "title/subtitle": "chart chrome",
      "x": "{ values, title, type:'time' }",
      "series": "[{ name, data, color? }]  (scatter data = [{x,y,label}])",
      "valueFmt": "'number' | 'compact' | 'euro' | 'percent' | 'integer' | function",
      "valueDecimals/unit/height": "optional",
      "table": "false to drop the table-view toggle (default true)",
      "zoom": "true/false to force zoom+pan on/off (auto-on for x.type:'time' or dense series); maxZoom / zoomStart tune it",
      "locale": "'en'|'fr' — else auto from <html lang> / zl-lang cookie"
    },
    "tokens": {
      "categorical": ["--zlc-cat-1 #8a9a3c", "--zlc-cat-2 #a86fb2", "--zlc-cat-3 #b8842a", "--zlc-cat-4 #cf5064", "--zlc-cat-5 #159a8b", "--zlc-cat-6 #6f7ad0", "--zlc-cat-7 #c65f37", "--zlc-cat-8 #3fa877"],
      "sequential": "--zlc-seq-1..7 (olive ramp, near-surface → bright = low → high)",
      "diverging": "rose (--zlc-div-neg-2) ↔ neutral gray (--zlc-div-mid) ↔ olive (--zlc-div-pos-2)",
      "status": "--zlc-good / --zlc-warning / --zlc-serious / --zlc-critical (reserved for state; icon+label; never a series)",
      "chrome": "--zlc-surface / --zlc-grid / --zlc-axis / --zlc-ink / --zlc-ink-2 / --zlc-ink-muted"
    },
    "rules": [
      "ONE axis — never two scales. Two measures of different scale → two charts.",
      "Categorical hues assigned in fixed order 1..N, never cycled; a 9th series folds into 'Other'. Palette CVD-validated on the ZLEF surface (worst adjacent ΔE 14.7). Color follows the entity, not its rank.",
      "Legend auto-shown for ≥2 series; direct labels stay sparing; grid is hairline; 2px surface gaps between touching marks.",
      "Tooltips ON by default (hover on desktop, tap on mobile) via one shared floating node; a table-view twin toggles under every cartesian chart.",
      "Dense temporal series (x.type:'time' or many points) auto-get ZOOM + internal horizontal scroll: plot re-lays-out at containerWidth×zoom px in its own scroll box, Y-axis pinned while panning the full range. Controls: −/⤡/+ buttons, ⌘/Ctrl+wheel & pinch to zoom, drag/swipe to pan.",
      "EVERY chart renders pixel-accurate at the real container width and redraws on resize — axis text and marks keep their true size at any width; the engine never stretches a fixed viewBox.",
      "Numbers and dates format through Intl in the resolved locale (i18n). Dark-only — one mode.",
      "Override a series hue per project by redefining --zlc-cat-N in your own CSS."
    ]
  },

  "logo": {
    "wordmark_svg": "https://assets.zlef.fr/logo.svg",
    "note": "Custom geometric ZLEF wordmark with angular notch cuts, white fill, faint glow filter. No web font. viewBox 0 0 322 100."
  },

  "mascot": {
    "name": "Sylve",
    "svg": "https://da.zlef.fr/mascot.svg",
    "svg_mini": "https://da.zlef.fr/mascot-mini.svg",
    "viewBox": "0 0 240 200",
    "note": "Faceted low-poly origami sentinel folded from olive-leaf planes, crown notched like the ZLEF wordmark, sparing vintage-grape gaze. Self-contained SVG, transparent bg, class-grouped for restyling. Use for empty-state media, 404s, and illustrations — mascot-mini.svg at icon scale.",
    "usage": "Empty state: place inside .zl-empty above the title. Keep it sparing — one per empty screen, never as decoration on data-filled views. When possible (owner-validated), derive a product-specific variant (same low-poly origami construction, restyled via the SVG classes) instead of stock Sylve."
  },

  "links": {
    "live_guide": "https://da.zlef.fr",
    "tokens_css": "https://da.zlef.fr/tokens.css",
    "charts_css": "https://da.zlef.fr/zl-charts.css",
    "charts_js": "https://da.zlef.fr/zl-charts.js",
    "design_json": "https://da.zlef.fr/design.json",
    "llms_txt": "https://da.zlef.fr/llms.txt"
  }
}
