/* =====================================================================
   JOG · V3b Design System · Components (layout shells minimum)
   Version 2.0.0 · 2026-04-24 · Rewrite from zero
   Mobile-first · WCAG AA · Keyboard-first
   Consomme exclusivement les tokens de tokens.css — aucun hex hardcoded
   ===================================================================== */

/* =====================================================================
   1. RESET (moderne, minimal)
   ===================================================================== */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  text-rendering: optimizeLegibility;
}

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: var(--fw-regular);
  line-height: var(--lh-normal);
  color: var(--color-fg);
  background: var(--color-bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "ss01", "ss02", "cv11";
  transition: background var(--duration-base) var(--ease-out), color var(--duration-base) var(--ease-out);
}

h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd {
  margin: 0;
}

h1, h2, h3, h4, h5, h6 {
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  letter-spacing: var(--tracking-tight);
}

a {
  color: var(--color-accent);
  text-decoration: none;
  transition: color var(--duration-fast) var(--ease-out);
}
a:not(.jog-btn):hover { color: var(--color-accent-hover); }
a:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
  border-radius: var(--r-sm);
}

button, input, textarea, select {
  font: inherit;
  color: inherit;
}

button {
  background: none;
  border: 0;
  cursor: pointer;
  padding: 0;
}

input, textarea, select {
  font-size: var(--text-input);  /* 16px — évite zoom iOS au focus */
}

img, svg, video { display: block; max-width: 100%; }

code, kbd, samp, pre { font-family: var(--font-mono); font-size: 0.95em; }

/* Scrollbar minimal (WebKit) */
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--color-border-strong);
  border-radius: var(--r-pill);
  border: 2px solid var(--color-bg);
}
*::-webkit-scrollbar-thumb:hover { background: var(--color-fg-faint); }

/* =====================================================================
   2. ACCESSIBILITY — skip link, focus ring, sr-only
   ===================================================================== */
.jog-skip-link {
  position: absolute;
  top: -100px;
  left: var(--space-4);
  padding: var(--space-2) var(--space-4);
  background: var(--color-accent);
  color: var(--color-fg-on-accent);
  font-weight: var(--fw-semibold);
  border-radius: var(--r-md);
  z-index: var(--z-toast);
  transition: top var(--duration-base) var(--ease-out);
}
.jog-skip-link:focus-visible {
  top: var(--space-4);
  outline: none;
  box-shadow: var(--shadow-focus);
}

.jog-sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* =====================================================================
   3. APP SHELL — grid header/main/footer full height responsive
   ===================================================================== */
.jog-app-shell {
  display: grid;
  grid-template-rows: auto 1fr auto;
  min-height: 100vh;
  min-height: 100dvh;
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
}

/* =====================================================================
   4. (was APP HEADER — removed in v1.1.4 / v1.1.5)
   The hand-rolled .jog-app-header / .jog-app-brand* / .jog-app-titlegroup
   block was replaced by the canonical lib/navigation/top_nav.html macro.
   See §13.2 for current top_nav styling.
   ===================================================================== */

/* =====================================================================
   5. THEME TOGGLE — sun/moon visibility driven by [data-theme]
   ===================================================================== */
.jog-theme-toggle {
  width: var(--tap-min);
  height: var(--tap-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-md);
  color: var(--color-fg-muted);
  transition: background var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.jog-theme-toggle:hover {
  background: var(--color-bg-soft);
  color: var(--color-fg);
}
.jog-theme-toggle:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}
.jog-theme-toggle__sun { display: inline; }
.jog-theme-toggle__moon { display: none; }
[data-theme="dark"] .jog-theme-toggle__sun { display: none; }
[data-theme="dark"] .jog-theme-toggle__moon { display: inline; }

/* =====================================================================
   6. APP MAIN — full-viewport-width container with light breathing margin
   (pas de max-width — l'app prend toute la largeur, pattern app shell
   pas marketing webpage. Petit padding interne pour respirer ; les panes
   internes gèrent leur propre padding via .jog-pane-l/r.)
   ===================================================================== */
.jog-app-main {
  width: 100%;
  padding: var(--space-3);
  outline: none;
  /* Flex column so direct children can stretch to fill viewport-available
     height. Grid-row 1fr from .jog-app-shell already gives main the
     vertical space ; flex propagates that down so consumers can request
     fill via .jog-stretch / .jog-card--fill-height. */
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.jog-app-main > * {
  flex: 1 1 auto;
  min-height: 0;
}

/* Opt-in stretch utility — wrapper element fills its flex parent and
   becomes a flex column itself so its child can also stretch. Used by
   app1 _processing.html on <div id="work-pane"> to chain fill down to
   the processing card. */
.jog-stretch {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* =====================================================================
   7. APP FOOTER — 3 sections stack mobile, row desktop
   ===================================================================== */
.jog-app-footer {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-4);
  background: var(--color-bg-soft);
  border-top: 1px solid var(--color-border);
  color: var(--color-fg-muted);
  font-size: var(--text-caption);
}

.jog-app-footer__section {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.jog-app-footer__section a {
  color: var(--color-fg-muted);
  transition: color var(--duration-fast) var(--ease-out);
}
.jog-app-footer__section a:hover { color: var(--color-accent); }

.jog-app-footer__sep { color: var(--color-fg-faint); }
.jog-app-footer__version { font-family: var(--font-mono); }

.jog-app-footer__right {
  display: flex;
  gap: var(--space-4);
}
.jog-app-footer__right a {
  width: var(--tap-min);
  height: var(--tap-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-md);
}
.jog-app-footer__right a:hover { background: var(--color-bg-muted); }

/* =====================================================================
   8. RESPONSIVE — tablette + desktop
   ===================================================================== */

/* Tablette+ : footer gagne en espace (main reste full-viewport flat padding) */
@media (min-width: 768px) {
  .jog-app-footer {
    flex-direction: row;
    justify-content: space-between;
    padding: var(--space-6) var(--space-6);
  }

  .jog-app-footer__center {
    flex: 1 1 auto;
    justify-content: center;
  }
}

/* Desktop : footer gagne en espace */
@media (min-width: 1024px) {
  .jog-app-footer { padding: var(--space-6) var(--space-8); }
}

/* Phone (<480px) : footer compact */
@media (max-width: 479px) {
  .jog-app-footer { padding: var(--space-5) var(--space-3); font-size: var(--text-micro); }
}

/* =====================================================================
   9. PRINT (minimal — masque UI chrome)
   ===================================================================== */
@media print {
  .jog-top-nav,
  .jog-app-footer,
  .jog-skip-link { display: none; }
  .jog-app-main { padding: 0; }
  body { background: white; color: black; }
}

/* =====================================================================
   10. ATOMICS — consomment exclusivement les tokens, aucun hex hardcoded
   ===================================================================== */

/* 10.1 BUTTON — .jog-btn
   variants : primary | ghost | secondary | danger | icon-only | close
   sizes    : sm 32px · md 44px (tap-min, default) · lg 48px (tap-comfy)
   states   : hover · active · focus-visible · disabled · aria-busy
   pattern  : .is-active pour filter bars (app4 .obs-btn.active) */
.jog-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  position: relative;
  padding: var(--space-2) var(--space-4);
  min-height: var(--tap-min);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  appearance: none;
  -webkit-tap-highlight-color: transparent;
  transition:
    background var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out);
}
.jog-btn:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
  z-index: var(--z-raised);
}
.jog-btn__icon {
  width: 16px; height: 16px;
  flex: 0 0 auto;
  fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.jog-btn__label { display: inline-block; }

/* Sizes */
.jog-btn--sm {
  padding: var(--space-1) var(--space-3);
  min-height: 32px;
  font-size: var(--text-caption);
  border-radius: var(--r-sm);
  gap: var(--space-1);
}
.jog-btn--sm .jog-btn__icon { width: 14px; height: 14px; }
.jog-btn--lg {
  padding: var(--space-3) var(--space-5);
  min-height: var(--tap-comfy);
  font-size: var(--text-h3);
  border-radius: var(--r-lg);
  gap: var(--space-3);
}
.jog-btn--lg .jog-btn__icon { width: 18px; height: 18px; }

/* Variant primary (accent brown) */
.jog-btn--primary {
  background: var(--color-accent);
  color: var(--color-fg-on-accent);
  border-color: var(--color-accent);
}
.jog-btn--primary:hover:not(:disabled):not([aria-busy="true"]) { background: var(--color-accent-hover); border-color: var(--color-accent-hover); }
.jog-btn--primary:active:not(:disabled):not([aria-busy="true"]) { background: var(--color-accent-active); border-color: var(--color-accent-active); }

/* Variant ghost (transparent bordered) */
.jog-btn--ghost {
  background: transparent;
  color: var(--color-fg);
  border-color: var(--color-border-strong);
}
.jog-btn--ghost:hover:not(:disabled):not([aria-busy="true"]) { background: var(--color-bg-soft); border-color: var(--color-fg-muted); color: var(--color-accent); }
.jog-btn--ghost:active:not(:disabled):not([aria-busy="true"]) { background: var(--color-bg-muted); }

/* Variant secondary (primary teal) */
.jog-btn--secondary {
  background: var(--color-primary);
  color: var(--color-fg-on-primary);
  border-color: var(--color-primary);
}
.jog-btn--secondary:hover:not(:disabled):not([aria-busy="true"]) { background: var(--color-primary-hover); border-color: var(--color-primary-hover); }
.jog-btn--secondary:active:not(:disabled):not([aria-busy="true"]) { background: var(--color-primary-active); border-color: var(--color-primary-active); }

/* Variant danger (error red) */
.jog-btn--danger {
  background: var(--color-error);
  color: var(--color-fg-on-accent);
  border-color: var(--color-error);
}
.jog-btn--danger:hover:not(:disabled):not([aria-busy="true"]) { background: var(--color-error-hover); border-color: var(--color-error-hover); }
.jog-btn--danger:focus-visible { box-shadow: var(--shadow-focus-error); }

/* Variant icon-only (square, no label) */
.jog-btn--icon-only {
  padding: 0;
  width: var(--tap-min);
  background: transparent;
  color: var(--color-fg-muted);
  border-color: transparent;
}
.jog-btn--icon-only:hover:not(:disabled):not([aria-busy="true"]) { background: var(--color-bg-soft); color: var(--color-fg); }
.jog-btn--icon-only .jog-btn__icon { width: 20px; height: 20px; }
.jog-btn--icon-only.jog-btn--sm { width: 32px; }
.jog-btn--icon-only.jog-btn--sm .jog-btn__icon { width: 16px; height: 16px; }
.jog-btn--icon-only.jog-btn--lg { width: var(--tap-comfy); }
.jog-btn--icon-only.jog-btn--lg .jog-btn__icon { width: 22px; height: 22px; }

/* Variant close (dismiss X, circular hover) */
.jog-btn--close {
  padding: 0;
  width: var(--tap-min);
  background: transparent;
  color: var(--color-fg-faint);
  border-color: transparent;
  border-radius: var(--r-pill);
}
.jog-btn--close:hover:not(:disabled) { background: var(--color-error-soft); color: var(--color-error); }
.jog-btn--close .jog-btn__icon { width: 18px; height: 18px; }
.jog-btn--close.jog-btn--sm { width: 32px; }
.jog-btn--close.jog-btn--sm .jog-btn__icon { width: 14px; height: 14px; }
.jog-btn--close.jog-btn--lg { width: var(--tap-comfy); }
.jog-btn--close.jog-btn--lg .jog-btn__icon { width: 20px; height: 20px; }

/* Disabled — opacity + cursor + pointer-events (bloque click même si handler attaché) */
.jog-btn:disabled,
.jog-btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* Loading (aria-busy) — spinner absolu remplace label visuellement, width stable */
.jog-btn[aria-busy="true"] { cursor: progress; pointer-events: none; }
.jog-btn[aria-busy="true"] .jog-btn__label,
.jog-btn[aria-busy="true"] .jog-btn__icon:not(.jog-btn__spinner) { visibility: hidden; }
.jog-btn__spinner {
  position: absolute;
  top: 50%; left: 50%;
  width: 18px; height: 18px;
  margin: -9px 0 0 -9px;
  display: none;
  color: currentColor;
  animation: jog-btn-spin 0.8s linear infinite;
}
.jog-btn[aria-busy="true"] .jog-btn__spinner { display: block; }
.jog-btn--sm .jog-btn__spinner { width: 14px; height: 14px; margin: -7px 0 0 -7px; }
.jog-btn--lg .jog-btn__spinner { width: 20px; height: 20px; margin: -10px 0 0 -10px; }
@keyframes jog-btn-spin { to { transform: rotate(360deg); } }

/* .is-active pattern (filter bars — n'override pas primary/secondary/danger) */
.jog-btn.is-active:not(.jog-btn--primary):not(.jog-btn--secondary):not(.jog-btn--danger) {
  background: var(--color-accent-soft);
  color: var(--color-accent-strong);
  border-color: var(--color-accent-border);
}

/* Block-level (form actions mobile full-width) */
.jog-btn--block { display: flex; width: 100%; }

/* CTA hint — small muted text below a primary action (e.g. estimated
   duration "Extraction ≈ 2 min", prerequisite, format reminder).
   Sits flush under the btn it qualifies. Tokens-only, dark-mode auto. */
.jog-cta-hint {
  margin: var(--space-2) 0 0;
  padding: 0 var(--space-2);
  text-align: center;
  font-size: var(--text-micro);
  color: var(--color-fg-muted);
  line-height: 1.4;
}

/* 10.2 ICON — .jog-icon
   sizes  : xs 14 · sm 16 · md 18 (default) · lg 20 · xl 24
   stroke : currentColor (hérite du parent, dark mode auto via tokens)
   usage  : décoratif par défaut (aria-hidden), interactive via macro param
   sprite : shared/static/lib/icons.svg — 27 symbols viewBox 24×24 */
.jog-icon {
  display: inline-block;
  flex-shrink: 0;
  vertical-align: middle;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  width: 18px;
  height: 18px;
}
.jog-icon--xs { width: 14px; height: 14px; }
.jog-icon--sm { width: 16px; height: 16px; }
.jog-icon--md { width: 18px; height: 18px; }
.jog-icon--lg { width: 20px; height: 20px; }
.jog-icon--xl { width: 24px; height: 24px; }

/* 10.3 BADGE — .jog-badge
   variants sémantiques : neutral (def) | success | warning | error | info | accent | primary
   variants BTP (mono auto) : decret | arrete | faq | label | loi | prio-1 | prio-2 | prio-3
   shapes  : default (r-sm 4px) | pill (r-pill)
   pattern : décoratif par défaut ; tokens-only, zéro hex hardcoded */
.jog-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  font-family: var(--font-sans);
  font-size: var(--text-micro);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  white-space: nowrap;
  vertical-align: middle;
}
.jog-badge__icon {
  width: 12px; height: 12px;
  flex: 0 0 auto;
  fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.jog-badge__label { display: inline-block; }

/* Shape pill — padding horizontal élargi pour équilibre visuel */
.jog-badge--pill {
  border-radius: var(--r-pill);
  padding-left: var(--space-3);
  padding-right: var(--space-3);
}

/* Mono modifier — signature BTP (font mono + uppercase + tracking wide) */
.jog-badge--mono {
  font-family: var(--font-mono);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

/* Variants sémantiques */
.jog-badge--neutral { background: var(--color-bg-muted);       color: var(--color-fg-muted);       border-color: var(--color-border); }
.jog-badge--success { background: var(--color-success-soft);   color: var(--color-success-fg);     border-color: var(--color-success-border); }
.jog-badge--warning { background: var(--color-warning-soft);   color: var(--color-warning-fg);     border-color: var(--color-warning-border); }
.jog-badge--error   { background: var(--color-error-soft);     color: var(--color-error-fg);       border-color: var(--color-error-border); }
.jog-badge--info    { background: var(--color-info-soft);      color: var(--color-info-fg);        border-color: var(--color-info-border); }
.jog-badge--accent  { background: var(--color-accent-soft);    color: var(--color-accent-strong);  border-color: var(--color-accent-border); }
.jog-badge--primary { background: var(--color-primary-soft);   color: var(--color-primary);        border-color: var(--color-primary-border); }

/* Variants BTP — mono auto-on via chain selector (pas besoin de mono=true en consumer) */
.jog-badge--decret, .jog-badge--arrete, .jog-badge--faq, .jog-badge--label,
.jog-badge--loi, .jog-badge--prio-1, .jog-badge--prio-2, .jog-badge--prio-3 {
  font-family: var(--font-mono);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}
.jog-badge--decret, .jog-badge--arrete, .jog-badge--loi {
  background: var(--color-primary-soft);  color: var(--color-primary);        border-color: var(--color-primary-border);
}
.jog-badge--faq     { background: var(--color-info-soft);      color: var(--color-info-fg);        border-color: var(--color-info-border); }
.jog-badge--label   { background: var(--color-success-soft);   color: var(--color-success-fg);     border-color: var(--color-success-border); }
.jog-badge--prio-1  { background: var(--color-error-soft);     color: var(--color-error-fg);       border-color: var(--color-error-border); }
.jog-badge--prio-2  { background: var(--color-warning-soft);   color: var(--color-warning-fg);     border-color: var(--color-warning-border); }
.jog-badge--prio-3  { background: var(--color-accent-soft);    color: var(--color-accent-strong);  border-color: var(--color-accent-border); }

/* 10.4 DOT — .jog-dot
   Status indicator · 6 variants × 4 sizes (xs 6 · sm 8 · md 10 · lg 12)
   Pulse ring via ::after · désactivée sous prefers-reduced-motion
   Sizes amendement audit : 12px = terrain bp-dot app5 + cluster-swatch app6 */
.jog-dot {
  position: relative;
  display: inline-block;
  flex-shrink: 0;
  vertical-align: middle;
  width: 10px;
  height: 10px;
  border-radius: var(--r-pill);
  background: var(--color-fg-faint);
}
.jog-dot--xs { width: 6px;  height: 6px;  }
.jog-dot--sm { width: 8px;  height: 8px;  }
.jog-dot--md { width: 10px; height: 10px; }
.jog-dot--lg { width: 12px; height: 12px; }

.jog-dot--neutral { background: var(--color-fg-faint); }
.jog-dot--success { background: var(--color-success); }
.jog-dot--warning { background: var(--color-warning); }
.jog-dot--error   { background: var(--color-error);   }
.jog-dot--accent  { background: var(--color-accent);  }
.jog-dot--primary { background: var(--color-primary); }

/* Pulse ring — autour du dot via ::after, n'affecte pas le label voisin */
.jog-dot[data-pulse="true"]::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--r-pill);
  background: inherit;
  opacity: 0.7;
  animation: jog-dot-pulse 1.6s var(--ease-out) infinite;
  pointer-events: none;
}
@keyframes jog-dot-pulse {
  0%   { transform: scale(1);   opacity: 0.7; }
  100% { transform: scale(2.4); opacity: 0;   }
}
@media (prefers-reduced-motion: reduce) {
  .jog-dot[data-pulse="true"]::after { animation: none; }
}

/* 10.5 CHIP — .jog-chip
   variants : neutral (def) | accent | primary | success | warning | error
   pattern  : removable pill interactif ; close = <button> natif
   tap      : visuel 20×20, tap-target 44×44 via ::before hit-slop invisible */
.jog-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  position: relative;
  min-height: 32px;
  padding: var(--space-1) var(--space-1) var(--space-1) var(--space-3);
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: var(--fw-medium);
  line-height: var(--lh-tight);
  white-space: nowrap;
  vertical-align: middle;
  transition:
    background var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out);
}
.jog-chip__icon {
  width: 14px; height: 14px;
  flex: 0 0 auto;
  fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.jog-chip__label { display: inline-block; line-height: 1; }

/* Chip sans close (tag statique) — padding symétrique */
.jog-chip:not(:has(.jog-chip__close)) { padding-right: var(--space-3); }

/* Close button natif — visuel 20×20, hit-slop 44×44 via ::before invisible */
.jog-chip__close {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  padding: 0;
  margin: 0;
  background: transparent;
  color: currentColor;
  opacity: 0.7;
  border: none;
  border-radius: var(--r-pill);
  cursor: pointer;
  appearance: none;
  -webkit-tap-highlight-color: transparent;
  transition:
    background var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out),
    opacity var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out);
}
.jog-chip__close::before {
  content: "";
  position: absolute;
  inset: -12px;
  border-radius: inherit;
}
.jog-chip__close:hover {
  background: var(--color-error-soft);
  color: var(--color-error);
  opacity: 1;
}
.jog-chip__close:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
  opacity: 1;
  z-index: var(--z-raised);
}
.jog-chip__close-icon {
  width: 14px; height: 14px;
  fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
  pointer-events: none;  /* simplifie event.target côté consumer */
}

/* Variants sémantiques — paires soft/border/fg (cohérentes avec badge) */
.jog-chip--neutral { background: var(--color-bg-muted);     color: var(--color-fg-muted);      border-color: var(--color-border); }
.jog-chip--accent  { background: var(--color-accent-soft);  color: var(--color-accent-strong); border-color: var(--color-accent-border); }
.jog-chip--primary { background: var(--color-primary-soft); color: var(--color-primary);       border-color: var(--color-primary-border); }
.jog-chip--success { background: var(--color-success-soft); color: var(--color-success-fg);    border-color: var(--color-success-border); }
.jog-chip--warning { background: var(--color-warning-soft); color: var(--color-warning-fg);    border-color: var(--color-warning-border); }
.jog-chip--error   { background: var(--color-error-soft);   color: var(--color-error-fg);      border-color: var(--color-error-border); }

/* Container helper — filter bars : overflow visible pour que les hit-slops débordent */
.jog-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  overflow: visible;
}


/* =====================================================================
   11. FORM — wrappers + controls (Session 03)
   ===================================================================== */

/* 11.1 FIELD — .jog-field (wrapper universel)
   Structure : label (+ required *) → control (injecté via caller) → helper | error
   ===================================================================== */
.jog-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: 100%;
}

.jog-field__label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-body);
  font-weight: var(--fw-medium);
  color: var(--color-fg);
  line-height: var(--lh-snug);
  cursor: pointer;
}

.jog-field__required {
  color: var(--color-error);
  text-decoration: none;
  font-weight: var(--fw-semibold);
  cursor: help;
}

.jog-field__helper,
.jog-field__error {
  display: inline-flex;
  align-items: flex-start;
  gap: var(--space-2);
  font-size: var(--text-caption);
  line-height: var(--lh-snug);
  margin: 0;
}

.jog-field__helper {
  color: var(--color-fg-subtle);
}

.jog-field__error {
  color: var(--color-error-fg);
  font-weight: var(--fw-medium);
}

.jog-field__error-icon {
  flex: none;
  width: 14px;
  height: 14px;
  margin-top: 1px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Size modifiers propagés au label (le control gère son propre size via .jog-input--<size>) */
.jog-field--sm .jog-field__label { font-size: var(--text-caption); }
.jog-field--lg .jog-field__label { font-size: var(--text-input); }

/* Disabled : opacity sur label + helper. Le control natif applique son propre pattern. */
.jog-field--disabled .jog-field__label,
.jog-field--disabled .jog-field__helper { opacity: 0.5; cursor: not-allowed; }

/* Readonly : label normal (le readonly est un état de lecture, pas désactivé) */
.jog-field--readonly .jog-field__label { cursor: default; }


/* 11.2 INPUT — .jog-input (wrapper) + .jog-input__control (le <input> natif)
   Consommé par input_field macro. Structure : [icon-left] <input> [icon-right | clear | spinner]
   ===================================================================== */
.jog-input {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
}

.jog-input__control {
  flex: 1;
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  font-family: var(--font-sans);
  font-size: var(--text-input);  /* 16px NON-NÉGOCIABLE (iOS zoom prevention) */
  font-weight: var(--fw-regular);
  color: var(--color-fg);
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--r-md);
  transition: border-color var(--duration-fast) var(--ease-out),
              box-shadow var(--duration-fast) var(--ease-out),
              background var(--duration-fast) var(--ease-out);
  appearance: none;
  -webkit-appearance: none;
}

.jog-input__control::placeholder {
  color: var(--color-fg-faint);
  opacity: 1;
}

.jog-input__control:hover:not(:disabled):not(:focus):not([readonly]) {
  border-color: var(--color-border-strong);
}

.jog-input__control:focus-visible {
  outline: none;
  border-color: var(--color-border-focus);
  box-shadow: var(--shadow-focus);
}

/* Sizes — parité scale btn (sm 32 / md 44 / lg 48) */
.jog-input--sm .jog-input__control { min-height: 32px; padding: var(--space-2) var(--space-3); }
.jog-input--lg .jog-input__control { min-height: var(--tap-comfy); padding: var(--space-3) var(--space-5); border-radius: var(--r-lg); }

/* Icons adornments (positionnés absolute dans le wrapper flex) */
.jog-input__icon {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  color: var(--color-fg-subtle);
  pointer-events: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.jog-input__icon--left  { left: var(--space-4); }
.jog-input__icon--right { right: var(--space-4); }

.jog-input--with-icon-left  .jog-input__control { padding-left: calc(var(--space-4) + 18px + var(--space-2)); }
.jog-input--with-icon-right .jog-input__control { padding-right: calc(var(--space-4) + 18px + var(--space-2)); }

/* Size sm shrink icons */
.jog-input--sm .jog-input__icon { width: 14px; height: 14px; }
.jog-input--sm.jog-input--with-icon-left  .jog-input__control { padding-left: calc(var(--space-3) + 14px + var(--space-2)); }
.jog-input--sm.jog-input--with-icon-right .jog-input__control { padding-right: calc(var(--space-3) + 14px + var(--space-2)); }

/* Clear button — hit-slop 44×44 via ::before (pattern chip S2) */
.jog-input__clear {
  position: absolute;
  right: var(--space-3);
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: var(--r-pill);
  color: var(--color-fg-subtle);
  cursor: pointer;
  transition: color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out);
}
.jog-input__clear::before {
  content: "";
  position: absolute;
  inset: -12px;
}
.jog-input__clear:hover  { color: var(--color-fg); background: var(--color-bg-muted); }
.jog-input__clear:focus-visible { outline: none; color: var(--color-fg); box-shadow: var(--shadow-focus); }
.jog-input__clear svg {
  width: 14px;
  height: 14px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.25;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
}

/* Loading spinner — réutilise keyframe jog-btn-spin §10.1
   WHY margin-top au lieu de translateY : jog-btn-spin @keyframes réécrit
   `transform: rotate(...)` donc un `transform: translateY(-50%)` statique
   est écrasé pendant l'animation (spinner décalé + jump visuel). margin
   négatif = décentrage géométrique indépendant de transform. */
.jog-input__spinner {
  position: absolute;
  right: var(--space-4);
  top: 50%;
  margin-top: -9px;              /* moitié de 18px height */
  width: 18px;
  height: 18px;
  color: var(--color-accent);
  transform-origin: center;
  animation: jog-btn-spin 0.8s linear infinite;
}
.jog-input--lg .jog-input__spinner {
  width: 20px;
  height: 20px;
  margin-top: -10px;
}
.jog-input--sm .jog-input__spinner {
  width: 14px;
  height: 14px;
  margin-top: -7px;
}
@media (prefers-reduced-motion: reduce) {
  .jog-input__spinner { animation: none; }
}

/* Error state — propagé depuis .jog-field--error sur le wrapper */
.jog-field--error .jog-input__control {
  border-color: var(--color-error-border);
  background: var(--color-error-soft);
}
.jog-field--error .jog-input__control:focus-visible {
  border-color: var(--color-error);
  box-shadow: var(--shadow-focus-error);
}

/* Readonly — bg soft + border normal + cursor default (lecture text OK) */
.jog-input__control[readonly] {
  background: var(--color-bg-soft);
  cursor: default;
}
.jog-input__control[readonly]:focus-visible {
  border-color: var(--color-border);
  box-shadow: none;
}

/* Disabled — triple-layer (opacity .5 + cursor not-allowed + pointer-events: none) */
.jog-input__control:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
  background: var(--color-bg-soft);
}


/* 11.3 TEXTAREA — .jog-textarea (wrapper) + .jog-textarea__control (le <textarea> natif)
   Consommé par textarea macro. Resize vertical only (layout safe).
   ===================================================================== */
.jog-textarea {
  position: relative;
  display: flex;
  width: 100%;
}

.jog-textarea__control {
  flex: 1;
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  font-family: var(--font-sans);
  font-size: var(--text-input);  /* 16px NON-NÉGOCIABLE (iOS zoom prevention) */
  font-weight: var(--fw-regular);
  line-height: var(--lh-normal);  /* 1.5 — lisibilité multi-ligne */
  color: var(--color-fg);
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--r-md);
  resize: vertical;               /* NON-NÉGOCIABLE — pas horizontal, pas both */
  transition: border-color var(--duration-fast) var(--ease-out),
              box-shadow var(--duration-fast) var(--ease-out),
              background var(--duration-fast) var(--ease-out);
  appearance: none;
  -webkit-appearance: none;
}

.jog-textarea__control::placeholder {
  color: var(--color-fg-faint);
  opacity: 1;
}

.jog-textarea__control:hover:not(:disabled):not(:focus):not([readonly]) {
  border-color: var(--color-border-strong);
}

.jog-textarea__control:focus-visible {
  outline: none;
  border-color: var(--color-border-focus);
  box-shadow: var(--shadow-focus);
}

.jog-textarea--sm .jog-textarea__control {
  min-height: 32px;
  padding: var(--space-2) var(--space-3);
}
.jog-textarea--lg .jog-textarea__control {
  min-height: var(--tap-comfy);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--r-lg);
}

.jog-textarea--autogrow .jog-textarea__control { overflow-y: hidden; }
.jog-textarea.is-loading .jog-textarea__control { opacity: 0.75; }

.jog-field--error .jog-textarea__control {
  border-color: var(--color-error-border);
  background: var(--color-error-soft);
}
.jog-field--error .jog-textarea__control:focus-visible {
  border-color: var(--color-error);
  box-shadow: var(--shadow-focus-error);
}

.jog-textarea__control[readonly] {
  background: var(--color-bg-soft);
  cursor: default;
  resize: none;
}
.jog-textarea__control[readonly]:focus-visible {
  border-color: var(--color-border);
  box-shadow: none;
}

.jog-textarea__control:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
  background: var(--color-bg-soft);
  resize: none;
}


/* 11.4 SELECT — .jog-select (native <select> + custom caret SVG sibling)
   Consommé par select_field macro. appearance: none pour uniformité cross-browser,
   caret SVG theme-aware via currentColor.
   ===================================================================== */
.jog-select {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
}

.jog-select__control {
  flex: 1;
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  padding-right: calc(var(--space-4) + 18px + var(--space-2));
  font-family: var(--font-sans);
  font-size: var(--text-input);
  font-weight: var(--fw-regular);
  color: var(--color-fg);
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out),
              box-shadow var(--duration-fast) var(--ease-out),
              background var(--duration-fast) var(--ease-out);
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image: none;
}

.jog-select__control:invalid { color: var(--color-fg-faint); }
.jog-select__control option { color: var(--color-fg); }

.jog-select__control:hover:not(:disabled):not(:focus) {
  border-color: var(--color-border-strong);
}

.jog-select__control:focus-visible {
  outline: none;
  border-color: var(--color-border-focus);
  box-shadow: var(--shadow-focus);
}

.jog-select--sm .jog-select__control {
  min-height: 32px;
  padding: var(--space-2) var(--space-3);
  padding-right: calc(var(--space-3) + 14px + var(--space-2));
}
.jog-select--lg .jog-select__control {
  min-height: var(--tap-comfy);
  padding: var(--space-3) var(--space-5);
  padding-right: calc(var(--space-5) + 18px + var(--space-2));
  border-radius: var(--r-lg);
}

.jog-select__caret {
  position: absolute;
  right: var(--space-4);
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  color: var(--color-fg-subtle);
  pointer-events: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.jog-select--sm .jog-select__caret { width: 14px; height: 14px; right: var(--space-3); }
.jog-select--lg .jog-select__caret { right: var(--space-5); }

.jog-field--error .jog-select__control {
  border-color: var(--color-error-border);
  background: var(--color-error-soft);
}
.jog-field--error .jog-select__control:focus-visible {
  border-color: var(--color-error);
  box-shadow: var(--shadow-focus-error);
}
.jog-field--error .jog-select__caret { color: var(--color-error-fg); }

.jog-select.is-readonly .jog-select__control {
  background: var(--color-bg-soft);
  cursor: default;
  pointer-events: none;
}
.jog-select.is-readonly .jog-select__caret { opacity: 0.5; }

.jog-select__control:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
  background: var(--color-bg-soft);
}
.jog-select__control:disabled + .jog-select__caret { opacity: 0.5; }


/* 11.5 SEGMENTED — .jog-segmented (radio-group form-friendly + toggle variant)
   Slab background + pills imbriqués (style iOS/macOS), cohérent earth-warm apps 4/5/6.
   Variant radio = <fieldset> + <input type="radio" .jog-sr-only> + <label>.
   Variant toggle = <div role="group"> + <button aria-pressed>.
   Font-size = --text-body (14px) — exception au 16px input (options courtes,
   pas un input texte → pas de zoom iOS).
   ===================================================================== */
.jog-segmented {
  display: block;
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;
}

.jog-segmented__legend {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin-bottom: var(--space-2);
  padding: 0;
  font-size: var(--text-caption);
  font-weight: var(--fw-medium);
  color: var(--color-fg);
  letter-spacing: var(--tracking-wide);
}
.jog-segmented__legend-text { display: inline-block; }
.jog-segmented__required {
  color: var(--color-error);
  text-decoration: none;
  font-weight: var(--fw-semibold);
  cursor: help;
}

.jog-segmented__group,
.jog-segmented--toggle {
  display: inline-flex;
  align-items: stretch;
  gap: 2px;
  padding: 3px;
  background: var(--color-bg-soft);
  border: 1px solid var(--color-border-soft);
  border-radius: var(--r-md);
  vertical-align: top;
  max-width: 100%;
}
.jog-segmented__group { width: 100%; }
.jog-segmented--toggle { width: auto; }

.jog-segmented__option {
  flex: 1 1 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-width: 0;
  min-height: calc(var(--tap-min) - 8px);
  padding: var(--space-2) var(--space-4);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  text-align: center;
  color: var(--color-fg-muted);
  background: transparent;
  border: 0;
  border-radius: calc(var(--r-md) - 3px);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color var(--duration-fast) var(--ease-out),
              background var(--duration-fast) var(--ease-out),
              box-shadow var(--duration-fast) var(--ease-out);
  -webkit-appearance: none;
  appearance: none;
}

.jog-segmented__icon {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.jog-segmented__label {
  display: inline-block;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.jog-segmented__option:hover {
  color: var(--color-fg);
  background: var(--color-bg-muted);
}

.jog-segmented__input:focus-visible + .jog-segmented__option,
.jog-segmented--toggle .jog-segmented__option:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
  position: relative;
  z-index: 1;
}

.jog-segmented__input:checked + .jog-segmented__option {
  color: var(--color-fg);
  background: var(--color-bg-elevated);
  box-shadow: var(--shadow-xs);
}
.jog-segmented__input:checked + .jog-segmented__option:hover {
  background: var(--color-bg-elevated);
}

.jog-segmented--toggle .jog-segmented__option[aria-pressed="true"] {
  color: var(--color-fg);
  background: var(--color-bg-elevated);
  box-shadow: var(--shadow-xs);
}
.jog-segmented--toggle .jog-segmented__option[aria-pressed="true"]:hover {
  background: var(--color-bg-elevated);
}

.jog-segmented--sm .jog-segmented__option {
  min-height: 24px;
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-caption);
}
.jog-segmented--sm .jog-segmented__icon { width: 14px; height: 14px; }

.jog-segmented--lg .jog-segmented__option {
  min-height: calc(var(--tap-comfy) - 8px);
  padding: var(--space-3) var(--space-5);
  font-size: var(--text-h3);
}
.jog-segmented--lg .jog-segmented__icon { width: 18px; height: 18px; }
.jog-segmented--lg .jog-segmented__group,
.jog-segmented--lg.jog-segmented--toggle { border-radius: var(--r-lg); }
.jog-segmented--lg .jog-segmented__option { border-radius: calc(var(--r-lg) - 3px); }

.jog-segmented--disabled .jog-segmented__group,
.jog-segmented--disabled.jog-segmented--toggle {
  opacity: 0.5;
  pointer-events: none;
  cursor: not-allowed;
}

.jog-segmented__input:disabled + .jog-segmented__option,
.jog-segmented__option:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

.jog-segmented--error .jog-segmented__group,
.jog-segmented--error.jog-segmented--toggle {
  border-color: var(--color-error-border);
  background: var(--color-error-soft);
}
.jog-segmented--error .jog-segmented__input:focus-visible + .jog-segmented__option,
.jog-segmented--error.jog-segmented--toggle .jog-segmented__option:focus-visible {
  box-shadow: var(--shadow-focus-error);
}

.jog-segmented__helper,
.jog-segmented__error {
  display: flex;
  align-items: flex-start;
  gap: var(--space-1);
  margin-top: var(--space-2);
  font-size: var(--text-caption);
  line-height: var(--lh-snug);
}
.jog-segmented__helper { color: var(--color-fg-subtle); }
.jog-segmented__error { color: var(--color-error-fg); font-weight: var(--fw-medium); }
.jog-segmented__error-icon {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  margin-top: 1px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.jog-field .jog-segmented--toggle { margin-top: 0; }


/* 11.6 FILTER BAR — .jog-filter-bar (consomme chip §10.5 + btn §10.1)
   Barre horizontale de filtres actifs. 2 variants : chips (plat) | groups
   (labels uppercase par catégorie, pattern app4 .obs-filter-*).
   ===================================================================== */
.jog-filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-bg-soft);
  border-bottom: 1px solid var(--color-border);
  border-radius: var(--r-md);
}

.jog-filter-bar--sm {
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
}

.jog-filter-bar--groups {
  align-items: flex-start;
  gap: var(--space-4);
  row-gap: var(--space-3);
}

.jog-filter-bar__group {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.jog-filter-bar__group-label {
  font-size: var(--text-micro);
  font-weight: var(--fw-semibold);
  color: var(--color-fg-subtle);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  line-height: 1;
  white-space: nowrap;
  margin-right: var(--space-1);
}

.jog-filter-bar--sm .jog-chip-row { gap: var(--space-1); }

.jog-filter-bar__empty {
  font-size: var(--text-caption);
  font-style: italic;
  color: var(--color-fg-faint);
  padding: var(--space-1) var(--space-2);
}
.jog-filter-bar--empty { justify-content: center; }

.jog-filter-bar__actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}

.jog-filter-bar--sticky {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  border-radius: 0;
  background: var(--color-bg-soft);
  box-shadow: var(--shadow-sm);
}

@supports (backdrop-filter: blur(8px)) {
  .jog-filter-bar--sticky {
    background: color-mix(in srgb, var(--color-bg-soft) 88%, transparent);
    backdrop-filter: blur(8px) saturate(1.1);
    -webkit-backdrop-filter: blur(8px) saturate(1.1);
  }
}

@media (max-width: 640px) {
  .jog-filter-bar {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-2);
  }
  .jog-filter-bar__actions {
    margin-left: 0;
    justify-content: flex-end;
  }
  .jog-filter-bar--groups { gap: var(--space-3); }
}

/* =====================================================================
   §12 COMPOSITES — Card, banner, drawer, dropzone, dual_dropzone
   Session 04 · 2026-04-24 — macros structurelles layout
   Consomment tokens S1 + atomiques S2 + form S3
   ===================================================================== */

/* §12.1 .jog-card — conteneur flexible
   ---------------------------------------------------------------------
   Variants : default (border + bg-elevated), elevated (shadow-md, border
              transparent), outlined (transparent, border seule),
              flat (bg-soft, no border, no shadow).
   Sizes    : sm (space-3/4), md (space-4/5), lg (space-5/6) padding.
   Modifier : --interactive (hover lift + focus ring) sur <a> ou <button>.
*/

.jog-card {
  display: flex;
  flex-direction: column;
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--r-xl);
  color: var(--color-fg);
  overflow: hidden;  /* default — clips child content (images, etc.) */
  transition:
    border-color var(--duration-base) var(--ease-out),
    background-color var(--duration-base) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out),
    transform var(--duration-base) var(--ease-out);
}

/* Reset natif <a>/<button> quand la card est interactive */
a.jog-card,
button.jog-card {
  appearance: none;
  -webkit-appearance: none;
  text-decoration: none;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
  width: 100%;
}

/* --- Variants --- */
.jog-card--default {
  /* bordure + bg-elevated défaut ci-dessus */
}
.jog-card--elevated {
  border-color: transparent;
  box-shadow: var(--shadow-md);
}
.jog-card--outlined {
  background: transparent;
  box-shadow: none;
}
.jog-card--flat {
  background: var(--color-bg-soft);
  border-color: transparent;
  box-shadow: none;
}

/* --- Overflow modifier ---
   Default .jog-card has overflow: hidden (clips child content for rounded
   corners). Some consumers need overflow visible : combobox listbox that
   pops below the input, tooltip that overflows the card edge, popover, etc.
   Opt-in via {{ card(overflow_visible=true) }}. */
.jog-card--overflow-visible { overflow: visible; }
.jog-card--overflow-visible .jog-card__body { overflow: visible; }

/* --- Fill-height modifier : card stretches to fill its flex parent
   vertically + body becomes a flex column with content centered. Used
   for "loading state" cards where the stepper should be vertically
   centered in the available work area (e.g. app1 _processing.html).
   Requires parent chain to be flex / .jog-stretch / .jog-pane-r ; the
   lib v1.1.11 chain (app-main → pane-r) provides that. */
.jog-card--fill-height {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.jog-card--fill-height .jog-card__body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 0;
}

/* --- Sizes : padding scale header / body / footer --- */
.jog-card--sm .jog-card__header,
.jog-card--sm .jog-card__body,
.jog-card--sm .jog-card__footer { padding: var(--space-3) var(--space-4); }

.jog-card--md .jog-card__header,
.jog-card--md .jog-card__body,
.jog-card--md .jog-card__footer { padding: var(--space-4) var(--space-5); }

.jog-card--lg .jog-card__header,
.jog-card--lg .jog-card__body,
.jog-card--lg .jog-card__footer { padding: var(--space-5) var(--space-6); }

/* --- Interactive modifier : lift + focus ring --- */
.jog-card--interactive:hover {
  border-color: var(--color-accent-border);
  box-shadow: var(--shadow-lg);
  transform: translateY(-2px);
}
.jog-card--interactive:focus-visible {
  outline: none;
  border-color: var(--color-border-focus);
  box-shadow: var(--shadow-focus);
}
.jog-card--interactive:active {
  transform: translateY(0);
  box-shadow: var(--shadow-sm);
}
.jog-card--elevated.jog-card--interactive:hover {
  box-shadow: var(--shadow-xl);
}
.jog-card--outlined.jog-card--interactive:hover {
  background: var(--color-accent-soft);
  box-shadow: var(--shadow-md);
}

@media (prefers-reduced-motion: reduce) {
  .jog-card--interactive:hover,
  .jog-card--interactive:active { transform: none; }
}

/* --- Parts : header / titles / actions / body / footer --- */
.jog-card__header {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  border-bottom: 1px solid var(--color-border-soft);
}
.jog-card--flat .jog-card__header,
.jog-card--outlined .jog-card__header { border-bottom-color: transparent; }

.jog-card__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  border-radius: var(--r-md);
  background: var(--color-accent-soft);
  color: var(--color-accent-strong);
}
.jog-card__icon svg {
  width: 20px;
  height: 20px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.jog-card__titles {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  flex: 1;
  min-width: 0;
}
.jog-card__title {
  font-size: var(--text-h3);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  color: var(--color-fg);
  margin: 0;
}
.jog-card__subtitle {
  font-size: var(--text-caption);
  color: var(--color-fg-subtle);
  line-height: var(--lh-normal);
  margin: 0;
}

.jog-card__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
  margin-left: auto;
}

.jog-card__body {
  flex: 1;
  font-size: var(--text-body);
  line-height: var(--lh-normal);
  color: var(--color-fg);
}
.jog-card__body > *:first-child { margin-top: 0; }
.jog-card__body > *:last-child  { margin-bottom: 0; }

.jog-card__footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  border-top: 1px solid var(--color-border-soft);
  background: var(--color-bg-soft);
}
.jog-card--flat .jog-card__footer {
  border-top-color: transparent;
  background: transparent;
}
.jog-card--outlined .jog-card__footer {
  border-top-color: var(--color-border-soft);
  background: transparent;
}

/* Mobile : header wraps quand actions + title + icon coexistent */
@media (max-width: 640px) {
  .jog-card__header { flex-wrap: wrap; }
  .jog-card__actions { width: 100%; justify-content: flex-end; }
}

/* §12.2 .jog-banner — notification inline horizontale
   ---------------------------------------------------------------------
   Variants sémantiques : info / success / warning / error.
   Layout : flex row, icon-left, body-center, actions-right (close + CTA).
   role="status|alert" + aria-live polite|assertive selon variant.
   Dismiss via data-banner-dismiss, géré par initBanner (shared-lib.js).
*/

.jog-banner {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  font-size: var(--text-body);
  line-height: var(--lh-normal);
  color: var(--color-fg);
  transition:
    opacity var(--duration-base) var(--ease-out),
    transform var(--duration-base) var(--ease-out);
}

/* --- Variants (paires soft / border / fg) --- */
.jog-banner--info {
  background: var(--color-info-soft);
  border-color: var(--color-info-border);
  color: var(--color-info-fg);
}
.jog-banner--success {
  background: var(--color-success-soft);
  border-color: var(--color-success-border);
  color: var(--color-success-fg);
}
.jog-banner--warning {
  background: var(--color-warning-soft);
  border-color: var(--color-warning-border);
  color: var(--color-warning-fg);
}
.jog-banner--error {
  background: var(--color-error-soft);
  border-color: var(--color-error-border);
  color: var(--color-error-fg);
}

/* --- Parts : icon / body / actions / close --- */
.jog-banner__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  margin-top: 1px; /* Optical alignment avec première ligne texte */
  color: currentColor;
}
.jog-banner__icon svg {
  width: 20px;
  height: 20px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.jog-banner__body {
  flex: 1;
  min-width: 0;
}
.jog-banner__body > *:first-child { margin-top: 0; }
.jog-banner__body > *:last-child  { margin-bottom: 0; }
.jog-banner__body a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  font-weight: var(--fw-semibold);
}
.jog-banner__body a:hover { text-decoration-thickness: 2px; }

.jog-banner__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
  margin-left: auto;
}

.jog-banner__close {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  background: transparent;
  color: currentColor;
  border-radius: var(--r-sm);
  cursor: pointer;
  opacity: .7;
  transition:
    opacity var(--duration-fast) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out);
}
/* Hit-slop 44×44 pseudo (pattern S2 chip.close) */
.jog-banner__close::before {
  content: "";
  position: absolute;
  inset: -10px;
}
.jog-banner__close:hover { opacity: 1; background: color-mix(in srgb, currentColor 10%, transparent); }
.jog-banner__close:focus-visible {
  outline: none;
  opacity: 1;
  box-shadow: 0 0 0 2px currentColor;
}
.jog-banner__close svg {
  width: 14px;
  height: 14px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
}

/* --- Dismiss animation (removed par initBanner) --- */
.jog-banner.is-dismissing {
  opacity: 0;
  transform: translateY(-4px);
}

@media (max-width: 640px) {
  .jog-banner { flex-wrap: wrap; }
  .jog-banner__actions { width: 100%; justify-content: flex-end; }
}

/* §12.3 .jog-drawer — panel slide-in via <dialog> natif
   ---------------------------------------------------------------------
   <dialog> gère nativement : focus trap, Escape close, backdrop.
   Open state : .is-open ajouté par initDrawer après showModal() (pilote
   transform + ::backdrop opacity).
   Sides    : right (default) | left | bottom.
   Sizes    : sm / md / lg — width pour right/left, max-height pour bottom.
*/

.jog-drawer {
  /* Reset <dialog> browser defaults */
  padding: 0;
  border: none;
  margin: 0;
  max-width: none;
  max-height: none;
  color: var(--color-fg);
  background: var(--color-bg-elevated);
  box-shadow: var(--shadow-xl);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  will-change: transform;
  transition: transform var(--duration-slow) var(--ease-out);
  z-index: var(--z-modal);
}

/* <dialog> natif : pas de display quand fermé — évite transitions parasites */
.jog-drawer:not([open]) {
  display: none;
}

/* Backdrop fade — :: backdrop pseudo-element natif */
.jog-drawer::backdrop {
  background: color-mix(in srgb, #000 55%, transparent);
  opacity: 0;
  transition: opacity var(--duration-slow) var(--ease-out);
}
.jog-drawer.is-open::backdrop { opacity: 1; }

/* --- Sides ---
   <dialog> UA stylesheet injecte `inset: 0; margin: auto;` au showModal()
   qui combat notre positionnement latéral. On override explicitement
   l'axe opposé à `auto` pour libérer le positionnement pinned.
*/
.jog-drawer--right {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: auto;          /* libère le left=0 UA → droite respectée */
  height: 100vh;
  height: 100dvh;
  transform: translateX(100%);
  border-radius: 0;
}
.jog-drawer--right.is-open { transform: translateX(0); }

.jog-drawer--left {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: auto;         /* libère le right=0 UA → gauche respectée */
  height: 100vh;
  height: 100dvh;
  transform: translateX(-100%);
  border-radius: 0;
}
.jog-drawer--left.is-open { transform: translateX(0); }

.jog-drawer--bottom {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  top: auto;           /* libère le top=0 UA → bas respecté */
  width: 100%;
  transform: translateY(100%);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
}
.jog-drawer--bottom.is-open { transform: translateY(0); }

/* --- Sizes (right/left : width ; bottom : max-height) --- */
.jog-drawer--right.jog-drawer--sm,
.jog-drawer--left.jog-drawer--sm { width: min(380px, 92vw); }

.jog-drawer--right.jog-drawer--md,
.jog-drawer--left.jog-drawer--md { width: min(480px, 92vw); }

.jog-drawer--right.jog-drawer--lg,
.jog-drawer--left.jog-drawer--lg { width: min(640px, 94vw); }

.jog-drawer--bottom.jog-drawer--sm { max-height: 40vh; max-height: 40dvh; }
.jog-drawer--bottom.jog-drawer--md { max-height: 60vh; max-height: 60dvh; }
.jog-drawer--bottom.jog-drawer--lg { max-height: 85vh; max-height: 85dvh; }

/* --- Parts : header / title / body (scrollable) / footer (sticky bas) --- */
.jog-drawer__header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--color-border-soft);
  flex-shrink: 0;
}

.jog-drawer__title {
  flex: 1;
  margin: 0;
  min-width: 0;
  font-size: var(--text-h2);
  font-weight: var(--fw-semibold);
  color: var(--color-fg);
  line-height: var(--lh-snug);
}

.jog-drawer__body {
  flex: 1 1 auto;
  padding: var(--space-4) var(--space-5);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  font-size: var(--text-body);
  line-height: var(--lh-normal);
  color: var(--color-fg);
}
.jog-drawer__body > *:first-child { margin-top: 0; }
.jog-drawer__body > *:last-child  { margin-bottom: 0; }

.jog-drawer__footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border-top: 1px solid var(--color-border-soft);
  background: var(--color-bg-soft);
  flex-shrink: 0;
}

@media (prefers-reduced-motion: reduce) {
  .jog-drawer,
  .jog-drawer::backdrop { transition: none; }
}

@media (max-width: 640px) {
  .jog-drawer--right.jog-drawer--lg,
  .jog-drawer--left.jog-drawer--lg { width: 100vw; max-width: 100vw; }
}

/* §12.4 .jog-dropzone — zone drag-drop fichier
   ---------------------------------------------------------------------
   <label class="jog-dropzone" data-state="..."> wrappant <input type="file">
   6 états pilotés par data-state : idle, dragover, filled, uploading,
   success, error. Disabled via triple-layer.
   Consumer pilote uploading/success/error (JS applicatif) ; initDropzone
   gère idle/dragover/filled automatiquement.
*/

.jog-dropzone {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 200px;
  padding: var(--space-5) var(--space-4);
  border: 2px dashed var(--color-border-strong);
  border-radius: var(--r-lg);
  background: var(--color-bg);
  color: var(--color-fg);
  cursor: pointer;
  text-align: center;
  transition:
    border-color var(--duration-base) var(--ease-out),
    background-color var(--duration-base) var(--ease-out),
    color var(--duration-base) var(--ease-out);
}
.jog-dropzone:hover {
  border-color: var(--color-accent);
  background: var(--color-accent-soft);
}
.jog-dropzone:focus-within {
  outline: none;
  border-color: var(--color-border-focus);
  box-shadow: var(--shadow-focus);
}

/* Visually-hidden input file (keyboard focusable via label) */
.jog-dropzone__input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* --- Visibility par data-state — seul le sous-élément de l'état est rendu --- */
.jog-dropzone__idle,
.jog-dropzone__entry,
.jog-dropzone__progress,
.jog-dropzone__success,
.jog-dropzone__error {
  display: none;
  width: 100%;
}
.jog-dropzone[data-state="idle"]     .jog-dropzone__idle,
.jog-dropzone[data-state="dragover"] .jog-dropzone__idle     { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); }
.jog-dropzone[data-state="filled"]   .jog-dropzone__entry    { display: flex; align-items: center; gap: var(--space-3); }
.jog-dropzone[data-state="uploading"] .jog-dropzone__progress { display: flex; flex-direction: column; align-items: stretch; gap: var(--space-2); text-align: left; }
.jog-dropzone[data-state="success"]  .jog-dropzone__success  { display: flex; align-items: center; gap: var(--space-3); justify-content: center; }
.jog-dropzone[data-state="error"]    .jog-dropzone__error    { display: flex; align-items: center; gap: var(--space-3); justify-content: center; }

/* --- States visuels sur le container --- */
.jog-dropzone[data-state="dragover"] {
  border-style: solid;
  border-color: var(--color-accent);
  background: var(--color-accent-soft);
}
.jog-dropzone[data-state="filled"],
.jog-dropzone[data-state="uploading"] {
  border-style: solid;
  border-color: var(--color-border);
  background: var(--color-bg-soft);
  min-height: 96px;
  cursor: default;
  text-align: left;
}
.jog-dropzone[data-state="success"] {
  border-style: solid;
  border-color: var(--color-success-border);
  background: var(--color-success-soft);
  color: var(--color-success-fg);
  min-height: 96px;
  cursor: default;
}
.jog-dropzone[data-state="error"] {
  border-style: solid;
  border-color: var(--color-error-border);
  background: var(--color-error-soft);
  color: var(--color-error-fg);
  min-height: 96px;
  cursor: default;
}

/* --- Parts : idle --- */
.jog-dropzone__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: var(--r-pill);
  background: var(--color-accent-soft);
  color: var(--color-accent-strong);
  margin-bottom: var(--space-1);
}
.jog-dropzone__icon svg {
  width: 24px;
  height: 24px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.jog-dropzone__title {
  font-size: var(--text-body);
  font-weight: var(--fw-semibold);
  color: var(--color-fg);
  line-height: var(--lh-snug);
}
.jog-dropzone__hint {
  font-size: var(--text-caption);
  color: var(--color-fg-subtle);
  line-height: var(--lh-normal);
}

/* --- Parts : filled (entry) --- */
.jog-dropzone__file-ext {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--r-md);
  background: var(--color-accent-soft);
  color: var(--color-accent-strong);
  font-size: var(--text-micro);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  flex-shrink: 0;
  font-family: var(--font-mono);
}
.jog-dropzone__file-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
  text-align: left;
}
.jog-dropzone__file-name {
  font-size: var(--text-body);
  font-weight: var(--fw-medium);
  color: var(--color-fg);
  line-height: var(--lh-snug);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.jog-dropzone__file-size {
  font-size: var(--text-caption);
  color: var(--color-fg-subtle);
  font-family: var(--font-mono);
}
.jog-dropzone__clear {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--color-fg-muted);
  border-radius: var(--r-sm);
  cursor: pointer;
  flex-shrink: 0;
  transition:
    color var(--duration-fast) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out);
}
/* Hit-slop 44×44 pseudo */
.jog-dropzone__clear::before {
  content: "";
  position: absolute;
  inset: -6px;
}
.jog-dropzone__clear:hover {
  color: var(--color-error);
  background: var(--color-error-soft);
}
.jog-dropzone__clear:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
  color: var(--color-error);
}
.jog-dropzone__clear svg {
  width: 16px;
  height: 16px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
}

/* --- Parts : uploading --- */
.jog-dropzone__progress-name {
  font-size: var(--text-body);
  font-weight: var(--fw-medium);
  color: var(--color-fg);
  line-height: var(--lh-snug);
}
.jog-dropzone__progress-bar {
  appearance: none;
  -webkit-appearance: none;
  display: block;
  width: 100%;
  height: 6px;
  border: none;
  border-radius: var(--r-pill);
  background: var(--color-bg-muted);
  overflow: hidden;
}
.jog-dropzone__progress-bar::-webkit-progress-bar {
  background: var(--color-bg-muted);
  border-radius: var(--r-pill);
}
.jog-dropzone__progress-bar::-webkit-progress-value {
  background: var(--color-accent);
  border-radius: var(--r-pill);
  transition: width var(--duration-base) var(--ease-out);
}
.jog-dropzone__progress-bar::-moz-progress-bar {
  background: var(--color-accent);
  border-radius: var(--r-pill);
}
.jog-dropzone__progress-percent {
  font-size: var(--text-caption);
  color: var(--color-fg-subtle);
  font-family: var(--font-mono);
  align-self: flex-end;
}

/* --- Parts : success / error status icons --- */
.jog-dropzone__status-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
}
.jog-dropzone__status-icon svg {
  width: 24px;
  height: 24px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.jog-dropzone__status-icon--success { color: var(--color-success); }
.jog-dropzone__status-icon--error   { color: var(--color-error); }

.jog-dropzone__error-message {
  font-size: var(--text-body);
  font-weight: var(--fw-medium);
  color: var(--color-error-fg);
  line-height: var(--lh-snug);
  text-align: left;
}

/* --- Disabled (triple-layer pattern S2) --- */
.jog-dropzone.is-disabled {
  opacity: .5;
  cursor: not-allowed;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .jog-dropzone,
  .jog-dropzone__progress-bar::-webkit-progress-value {
    transition: none;
  }
}

/* §12.5 .jog-dual-dropzone — 2 dropzones côte-à-côte
   ---------------------------------------------------------------------
   Composition pure de 2 instances dropzone.
   Responsive : grid 2 cols ≥768px, stack column <768px.
*/

.jog-dual-dropzone {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
}

.jog-dual-dropzone__slot {
  min-width: 0;
}

.jog-dual-dropzone__slot > .jog-dropzone {
  width: 100%;
}

@media (max-width: 768px) {
  .jog-dual-dropzone {
    grid-template-columns: 1fr;
    gap: var(--space-3);
  }
}


/* §12.6 .jog-export-pair — dual-download bloc (primary + optional secondary)
   ---------------------------------------------------------------------
   Macro : shared/templates/lib/composites/export_pair.html
   Pattern Phase B legacy promu (commit 3fcf4fe). Apps 1 dual CSV+DPGF, apps 2/3 single.
   Grid 2-col ≥768px → 1-col <768px (auto). Override stacked=True force 1-col desktop.
   Primary : accent border + soft bg. Secondary : default. Réuse .jog-btn (S2) sur <a>.
*/

.jog-export-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
}
.jog-export-pair--stacked { grid-template-columns: 1fr; }

@media (max-width: 768px) {
  .jog-export-pair { grid-template-columns: 1fr; }
}

.jog-export-pair__item {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--r-md);
  background: var(--color-bg);
  transition: border-color var(--duration-base) var(--ease-out);
}
.jog-export-pair__item:hover { border-color: var(--color-border-strong); }

.jog-export-pair__item--primary {
  border-color: var(--color-accent);
  background: var(--color-accent-soft);
}
.jog-export-pair__item--primary:hover { border-color: var(--color-accent-hover); }

.jog-export-pair__header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.jog-export-pair__icon {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--color-fg-muted);
}
.jog-export-pair__item--primary .jog-export-pair__icon { color: var(--color-accent); }
.jog-export-pair__icon svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; }

.jog-export-pair__label {
  margin: 0;
  font-size: var(--text-h3);
  font-weight: var(--fw-semibold);
  color: var(--color-fg);
}

.jog-export-pair__desc {
  margin: 0;
  font-size: var(--text-caption);
  color: var(--color-fg-muted);
  line-height: 1.5;
}

.jog-export-pair__action {
  margin-top: auto;
  padding-top: var(--space-2);
}


/* =====================================================================
   §13 NAVIGATION
   ---------------------------------------------------------------------
   Macros dans shared/templates/lib/navigation/.
   4 éléments : app_shell / top_nav / sidebar / breadcrumb.
   Tokens-only, mobile-first, WCAG AA.
   ===================================================================== */

/* §13.1 .jog-app — wrapper shell avec sidebar optionnelle (grid-areas)
   ---------------------------------------------------------------------
   Décision S05 §5.1 A : grid-template-areas lisibles.
   Décision S05 §5.3 C : sidebar = grid track col (pas position fixed).
   Desktop ≥768px avec sidebar : "top top" / "side main" / "foot foot"
   Mobile <768px : "top" / "main" / "foot" — sidebar rendue dans drawer (cf §13.3).
*/

.jog-app {
  display: grid;
  min-height: 100vh;
  min-height: 100dvh;
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "top"
    "main"
    "foot";
  background: var(--color-bg);
}

.jog-app__top    { grid-area: top;  min-width: 0; }
.jog-app__side   { grid-area: side; min-width: 0; display: none; }
.jog-app__main   { grid-area: main; min-width: 0; outline: none; }
.jog-app__foot   { grid-area: foot; min-width: 0; }

/* Main focusable via skip-link, mais focus ring dispensable (body text follows) */
.jog-app__main:focus { outline: none; }

.jog-app__main-container {
  width: 100%;
  max-width: var(--container-xl);
  margin: 0 auto;
  padding: var(--space-6) var(--space-4);
}

/* --- Desktop ≥768px avec sidebar --- */
@media (min-width: 768px) {
  .jog-app--with-sidebar {
    grid-template-columns: 260px 1fr;
    grid-template-areas:
      "top  top"
      "side main"
      "foot foot";
  }
  .jog-app--with-sidebar .jog-app__side { display: block; }
}

/* --- Desktop padding rétabli --- */
@media (min-width: 1024px) {
  .jog-app__main-container {
    padding: var(--space-8) var(--space-6);
  }
}


/* §13.2 .jog-top-nav — barre supérieure sticky avec hamburger mobile
   ---------------------------------------------------------------------
   Décision S05 §5.2 A : flat par défaut, shadow-sm ajoutée par JS (class --shadow)
     au scroll > 8px. Pure visuelle en top, feedback visuel une fois décalé.
   Décision S05 §5.8 A : hamburger toujours dans HTML, CSS caché ≥ 768px.
*/

.jog-top-nav {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  background: var(--color-bg-elevated);
  border-bottom: 1px solid var(--color-border);
  transition: box-shadow var(--duration-base) var(--ease-out);
}

.jog-top-nav--shadow {
  box-shadow: var(--shadow-sm);
}

.jog-top-nav__inner {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  max-width: var(--container-2xl);
  margin: 0 auto;
  padding: var(--space-3) var(--space-4);
  min-height: 56px;
}

/* Mobile hamburger */
.jog-top-nav__mobile-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-min);
  height: var(--tap-min);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--color-fg-muted);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
  flex-shrink: 0;
}
.jog-top-nav__mobile-toggle:hover {
  background: var(--color-bg-muted);
  color: var(--color-fg);
}
.jog-top-nav__mobile-toggle:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
  border-color: var(--color-border-focus);
}

/* Brand (logo + wordmark + sub) */
.jog-top-nav__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-fg);
  text-decoration: none;
  font-weight: var(--fw-semibold);
  flex-shrink: 0;
}
.jog-top-nav__brand:hover { color: var(--color-accent-strong); }
.jog-top-nav__brand:focus-visible {
  outline: none;
  border-radius: var(--r-md);
  box-shadow: var(--shadow-focus);
}

.jog-top-nav__brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--r-md);
  background: var(--color-accent);
  color: var(--color-fg-on-accent);
  font-size: var(--text-body);
  font-weight: var(--fw-bold);
  line-height: 1;
}

.jog-top-nav__brand-text {
  display: inline-flex;
  flex-direction: column;
  line-height: var(--lh-tight);
}
.jog-top-nav__brand-wordmark {
  font-size: var(--text-h3);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-tight);
}
.jog-top-nav__brand-sub {
  font-size: var(--text-caption);
  font-weight: var(--fw-regular);
  color: var(--color-fg-subtle);
}

/* Nav links — horizontal row (caché mobile par défaut, visible desktop) */
.jog-top-nav__nav {
  display: none;
  margin-left: var(--space-4);
  min-width: 0;
  flex: 1 1 auto;
}

.jog-top-nav__links {
  display: flex;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.jog-top-nav__link {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--r-md);
  color: var(--color-fg-muted);
  font-size: var(--text-body);
  font-weight: var(--fw-medium);
  text-decoration: none;
  transition: color var(--duration-fast) var(--ease-out),
              background var(--duration-fast) var(--ease-out);
}
.jog-top-nav__link:hover {
  color: var(--color-fg);
  background: var(--color-bg-muted);
}
.jog-top-nav__link:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}
.jog-top-nav__link[aria-current="page"] {
  color: var(--color-accent-strong);
}
/* Underline indicator active — 2px sous le link, animé */
.jog-top-nav__link[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: var(--space-3);
  right: var(--space-3);
  bottom: -2px;
  height: 2px;
  background: var(--color-accent);
  border-radius: 2px;
}

/* Actions slot (theme toggle, user menu, CTAs) */
.jog-top-nav__actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-left: auto;
  flex-shrink: 0;
}

/* --- Desktop ≥ 768px : hamburger caché, nav links visible --- */
@media (min-width: 768px) {
  .jog-top-nav__mobile-toggle { display: none; }
  .jog-top-nav__nav { display: block; }
  .jog-top-nav__inner {
    padding: var(--space-3) var(--space-6);
    gap: var(--space-4);
  }
}

/* §13.2.1 .jog-top-nav--app — variant pour app shells (apps demo, dashboards).
   Drop max-width sur __inner pour aller pleine largeur viewport. Webpages /
   marketing utilisent la default constrained ; apps utilisent --app. */
.jog-top-nav--app .jog-top-nav__inner {
  max-width: none;
}

/* §13.2.2 .jog-top-nav__divider + __page-title — divider + page-name inline
   dans le topbar (entre brand et nav). Évite un second navbar visuel sous le
   topbar, le titre de la page reste partie du chrome principal. */
.jog-top-nav__divider {
  width: 1px;
  align-self: stretch;
  background: var(--color-border);
  margin: var(--space-2) var(--space-1);
  flex-shrink: 0;
}
.jog-top-nav__page-title {
  font-size: var(--text-body);
  font-weight: var(--fw-semibold);
  color: var(--color-fg);
  letter-spacing: var(--tracking-tight);
  line-height: var(--lh-tight);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.jog-top-nav__page-title small {
  display: block;
  font-size: var(--text-caption);
  font-weight: var(--fw-regular);
  color: var(--color-fg-muted);
  letter-spacing: 0;
}


/* §13.3 .jog-sidebar — nav latérale (aside desktop + drawer duplicata mobile)
   ---------------------------------------------------------------------
   Décision S05 §5.6 A : active state = bar gauche 4px accent + bg-accent-soft.
   Décision S05 §5.9 A : group title uppercase tracking-wide, pas de séparateur.
   La version --in-drawer reset le sticky/overflow pour laisser le .jog-drawer__body
   gérer le scroll.
*/

.jog-sidebar {
  position: sticky;
  top: 56px;                                /* cleared le top_nav sticky */
  align-self: start;                        /* évite grid-stretch qui casse sticky */
  max-height: calc(100vh - 56px);
  max-height: calc(100dvh - 56px);
  overflow-y: auto;
  padding: var(--space-4) var(--space-3);
  background: var(--color-bg-soft);
  border-right: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  min-width: 0;
}

/* Dans le drawer : on hérite du scroll/padding du .jog-drawer__body */
.jog-sidebar--in-drawer {
  position: static;
  max-height: none;
  overflow: visible;
  padding: 0;
  background: transparent;
  border-right: none;
}

/* Brand (optional — if top_nav already handles, laisser none) */
.jog-sidebar__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--color-border);
  color: var(--color-fg);
  font-weight: var(--fw-semibold);
}
.jog-sidebar__brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--r-md);
  background: var(--color-accent);
  color: var(--color-fg-on-accent);
  font-size: var(--text-body);
  font-weight: var(--fw-bold);
}
.jog-sidebar__brand-text {
  display: inline-flex;
  flex-direction: column;
  line-height: var(--lh-tight);
}
.jog-sidebar__brand-wordmark {
  font-size: var(--text-body);
  font-weight: var(--fw-bold);
}
.jog-sidebar__brand-sub {
  font-size: var(--text-caption);
  color: var(--color-fg-subtle);
  font-weight: var(--fw-regular);
}

/* Nav + groups */
.jog-sidebar__nav {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  flex: 1 1 auto;
  min-width: 0;
}

.jog-sidebar__group {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.jog-sidebar__group-title {
  margin: 0;
  padding: var(--space-1) var(--space-2) var(--space-1);
  font-size: var(--text-micro);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--color-fg-subtle);
}

.jog-sidebar__items {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.jog-sidebar__item { min-width: 0; }

/* Link — grid : icon | label | badge */
.jog-sidebar__link {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--r-md);
  color: var(--color-fg-muted);
  font-size: var(--text-body);
  font-weight: var(--fw-medium);
  text-decoration: none;
  min-height: 36px;
  transition: color var(--duration-fast) var(--ease-out),
              background var(--duration-fast) var(--ease-out);
}
.jog-sidebar__link:hover {
  color: var(--color-fg);
  background: var(--color-bg-muted);
}
.jog-sidebar__link:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

/* Active state : bar gauche 4px + bg-accent-soft + fg-accent-strong */
.jog-sidebar__link[aria-current="page"] {
  color: var(--color-accent-strong);
  background: var(--color-accent-soft);
  font-weight: var(--fw-semibold);
}
.jog-sidebar__link[aria-current="page"]::before {
  content: "";
  position: absolute;
  left: 0;
  top: 6px;
  bottom: 6px;
  width: 3px;
  background: var(--color-accent);
  border-radius: 0 2px 2px 0;
}

.jog-sidebar__link-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  color: currentColor;
  flex-shrink: 0;
}
.jog-sidebar__link-icon svg { stroke: currentColor; }

.jog-sidebar__link-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.jog-sidebar__link-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 18px;
  padding: 0 var(--space-2);
  border-radius: var(--r-pill);
  background: var(--color-bg-muted);
  color: var(--color-fg-muted);
  font-size: var(--text-micro);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
}
.jog-sidebar__link[aria-current="page"] .jog-sidebar__link-badge {
  background: var(--color-accent);
  color: var(--color-fg-on-accent);
}

.jog-sidebar__footer {
  margin-top: auto;
  padding: var(--space-3) var(--space-2) var(--space-1);
  border-top: 1px solid var(--color-border);
  font-size: var(--text-caption);
  color: var(--color-fg-subtle);
}


/* §13.4 .jog-breadcrumb — chemin hiérarchique <nav> <ol> <li>
   ---------------------------------------------------------------------
   Décision S05 §5.11 A : separator = chevron-right icon sprite (theme-aware).
   Décision S05 §5.5 A : truncation mobile <640px simple — si count > 4,
     affiche premier + … + avant-dernier + dernier.
*/

.jog-breadcrumb {
  min-width: 0;
  font-size: var(--text-caption);
  color: var(--color-fg-muted);
}

.jog-breadcrumb__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.jog-breadcrumb__item {
  display: inline-flex;
  align-items: center;
  min-width: 0;
  gap: var(--space-1);
}

.jog-breadcrumb__link {
  display: inline-block;
  color: var(--color-fg-muted);
  text-decoration: none;
  padding: 2px 4px;
  border-radius: var(--r-sm);
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: color var(--duration-fast) var(--ease-out),
              background var(--duration-fast) var(--ease-out);
}
.jog-breadcrumb__link:hover {
  color: var(--color-accent-strong);
  background: var(--color-accent-soft);
}
.jog-breadcrumb__link:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

.jog-breadcrumb__current {
  display: inline-block;
  padding: 2px 4px;
  color: var(--color-fg);
  font-weight: var(--fw-semibold);
  max-width: 30ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.jog-breadcrumb__sep {
  color: var(--color-fg-faint);
  flex-shrink: 0;
  margin: 0 var(--space-1);
}

/* Ellipsis : caché par défaut (desktop), révélé sur mobile via truncation rule */
.jog-breadcrumb__ellipsis {
  display: none;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-fg-subtle);
}
.jog-breadcrumb__ellipsis-dots {
  padding: 0 var(--space-1);
  letter-spacing: 0.1em;
}

/* --- Mobile <640px : truncation si data-truncatable --- */
@media (max-width: 639px) {
  .jog-breadcrumb[data-truncatable="true"] .jog-breadcrumb__ellipsis {
    display: inline-flex;
  }
  /* Cache tous les items middle (pos ≥ 3, sauf 2 derniers : avant-dernier + current).
     Pos 1 = premier item, pos 2 = ellipsis, pos 3+ = items intermédiaires. */
  .jog-breadcrumb[data-truncatable="true"] .jog-breadcrumb__item:nth-child(n+3):not(:nth-last-child(-n+2)) {
    display: none;
  }
}

/* ============================================================================
   §14 · DATA DISPLAY — tables, KPI cards, stat groups, chart cards
   ============================================================================
   Macros consommatrices : shared/templates/lib/data/{table, kpi_card,
   stat_group, chart_card}.html

   Conventions :
   - Tokens S1 uniquement, zéro hex.
   - Cellules/valeurs numériques : font-variant-numeric: tabular-nums +
     --font-mono pour alignement en colonne et lisibilité chiffrée.
   - Dark mode auto via tokens (pas de bloc [data-theme="dark"] dédié).
   - Classes base consommées : .jog-card (§12.1), .jog-icon (atomic S2),
     .jog-btn (atomic S2).
============================================================================ */

/* §14.1 .jog-table — table sémantique sortable/sticky/dense/zebra
   ---------------------------------------------------------------------
   Structure : .jog-table-wrap > <table class="jog-table"> > <thead>/<tbody>
   Variants   : default (borders) · --dense · --zebra · --sticky (via wrap)
   Sortable   : data-component="sortable-table" sur <table>, aria-sort sur <th>.
*/

.jog-table-wrap {
  width: 100%;
  overflow-x: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--r-lg);
  background: var(--color-bg-elevated);
  /* iOS momentum scroll */
  -webkit-overflow-scrolling: touch;
}

.jog-table-wrap--sticky {
  overflow-y: auto;
  /* max-height via inline style de la macro */
}

.jog-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-body);
  color: var(--color-fg);
  line-height: var(--lh-normal);
}

.jog-table__caption {
  caption-side: top;
  padding: var(--space-3) var(--space-4);
  text-align: left;
  font-size: var(--text-caption);
  font-weight: var(--fw-medium);
  color: var(--color-fg-muted);
  background: var(--color-bg-soft);
  border-bottom: 1px solid var(--color-border);
}

.jog-table thead th {
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-body);
  font-weight: var(--fw-semibold);
  color: var(--color-fg-muted);
  background: var(--color-bg-soft);
  border-bottom: 1px solid var(--color-border);
  text-align: left;
  white-space: nowrap;
  vertical-align: middle;
  line-height: var(--lh-snug);
}

.jog-table tbody td {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-border-soft);
  vertical-align: middle;
}

.jog-table tbody tr:last-child td {
  border-bottom: none;
}

.jog-table tbody tr {
  transition: background-color var(--duration-fast) var(--ease-out);
}

.jog-table tbody tr:hover {
  background: var(--color-bg-soft);
}

/* --- Alignement cellules : utilitaires nestés sous .jog-table pour
       gagner en spécificité (0,2,0) vs les règles de base thead/tbody
       (0,1,2). Sans ce nesting, text-align: left sur thead th prévalait
       sur .jog-table__num/center → colonnes décalées entre th et td. --- */
.jog-table .jog-table__num {
  text-align: right;
}
.jog-table .jog-table__center {
  text-align: center;
}

/* Font mono + tabular-nums uniquement sur cellules de données (tbody td).
   Les labels th restent en sans-serif (prose : "Surface (m²)", "Qté", etc.),
   le mono serait bizarre sur du texte. */
.jog-table tbody td.jog-table__num,
.jog-table tbody td.jog-table__mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}

.jog-table__th-label {
  display: inline-block;
  vertical-align: middle;
}

/* --- Sortable indicator : chevron rotatif via aria-sort --- */
.jog-table th[aria-sort] {
  cursor: pointer;
  user-select: none;
  transition: color var(--duration-fast) var(--ease-out);
}
.jog-table th[aria-sort]:hover {
  color: var(--color-fg);
}
.jog-table th[aria-sort]:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--color-border-focus);
  border-radius: var(--r-sm);
}

.jog-table__sort-icon {
  display: inline-block;
  margin-left: var(--space-1);
  vertical-align: middle;
  color: var(--color-fg-faint);
  opacity: 0.6;
  transition:
    transform var(--duration-base) var(--ease-out),
    color     var(--duration-fast) var(--ease-out),
    opacity   var(--duration-fast) var(--ease-out);
}

.jog-table th[aria-sort="ascending"] .jog-table__sort-icon,
.jog-table th[aria-sort="descending"] .jog-table__sort-icon {
  color: var(--color-accent);
  opacity: 1;
}
.jog-table th[aria-sort="descending"] .jog-table__sort-icon {
  transform: rotate(180deg);
}

/* --- Variant : --dense (padding réduit pour dashboards data-heavy) --- */
.jog-table--dense thead th,
.jog-table--dense tbody td {
  padding: var(--space-2) var(--space-3);
}
.jog-table--dense,
.jog-table--dense thead th {
  font-size: var(--text-caption);
}

/* --- Variant : --zebra (rows alternées, pas de borders inter-cells) --- */
.jog-table--zebra tbody tr:nth-child(even) {
  background: var(--color-bg-soft);
}
.jog-table--zebra tbody td {
  border-bottom: none;
}
.jog-table--zebra tbody tr:hover {
  background: var(--color-accent-soft);
}

/* --- Variant : --sticky (thead sticky dans wrapper scrollable) --- */
.jog-table--sticky thead th {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  /* shadow subtile sous le header au scroll (border-bottom visible + shadow) */
  box-shadow: 0 1px 0 var(--color-border), var(--shadow-xs);
}

/* --- Empty state --- */
.jog-table__empty-row:hover {
  background: transparent;
}
.jog-table__empty {
  text-align: center;
  padding: var(--space-6) var(--space-4) !important;
  color: var(--color-fg-subtle);
  font-size: var(--text-caption);
  font-style: italic;
}

/* --- Mobile : le wrapper permet overflow-x, pas de transformation card --- */
@media (max-width: 639px) {
  .jog-table thead th,
  .jog-table tbody td {
    padding: var(--space-2) var(--space-3);
  }
}

/* §14.2 .jog-kpi-card — indicateur chiffré (variant data de .jog-card)
   ---------------------------------------------------------------------
   Visuel DNA app4 : text-align center, value-first dominant, label muted
   below, delta/sparkline pushed to bottom (margin-top: auto) pour hauteurs
   uniformes dans une grille stat_group.
*/

.jog-kpi-card.jog-card {
  min-width: 0;
}

.jog-kpi-card__body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-5);
  text-align: center;
  /* min-height raisonnable pour cards sans bottom cluster, évite
     l'aplatissement visuel quand card a juste label+value */
  min-height: 128px;
}

.jog-kpi-card__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--r-md);
  background: var(--color-bg-soft);
  color: var(--color-fg-muted);
  flex-shrink: 0;
  margin-bottom: var(--space-1);
}

.jog-kpi-card__figure {
  display: inline-flex;
  align-items: baseline;
  justify-content: center;
  gap: var(--space-1);
  flex-wrap: wrap;
  min-width: 0;
  max-width: 100%;
}

.jog-kpi-card__value {
  font-family: var(--font-mono);
  font-size: var(--text-display);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  line-height: var(--lh-tight);
  color: var(--color-fg);
  letter-spacing: var(--tracking-tight);
}

.jog-kpi-card__unit {
  font-size: var(--text-caption);
  font-weight: var(--fw-medium);
  color: var(--color-fg-subtle);
  line-height: var(--lh-tight);
}

/* Row label + info icon inline : garde le label centré même si info icon
   présent (l'icon se place à droite du label sans shift du centre). */
.jog-kpi-card__label-row {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  max-width: 100%;
  min-width: 0;
}

.jog-kpi-card__label {
  font-size: var(--text-caption);
  font-weight: var(--fw-medium);
  color: var(--color-fg-muted);
  line-height: var(--lh-snug);
  /* Label peut wrap sur 2 lignes pour libellés BTP longs (« Émission
     moyenne trajectoire RE2020 »). Limite à 2 lignes puis ellipsis. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  max-width: 100%;
  word-break: break-word;
}

/* Info icon inline (pattern app4 obs-info-icon-inline) — bulle italic "i" */
.jog-kpi-card__info {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: var(--r-pill);
  border: none;
  background: var(--color-fg-faint);
  color: var(--color-bg-elevated);
  font-size: 0.7rem;
  font-weight: var(--fw-bold);
  font-style: italic;
  font-family: Georgia, "Times New Roman", serif;
  cursor: help;
  flex-shrink: 0;
  padding: 0;
  line-height: 1;
  transition: background var(--duration-fast) var(--ease-out);
}
.jog-kpi-card__info:hover,
.jog-kpi-card__info:focus-visible {
  background: var(--color-accent);
  outline: none;
  box-shadow: var(--shadow-focus);
}

/* Sub meta : context tiny sous le label (app4 obs-indicator-sub) */
.jog-kpi-card__sub {
  font-size: var(--text-micro);
  color: var(--color-fg-faint);
  line-height: var(--lh-snug);
  margin-top: calc(-1 * var(--space-1));
  max-width: 100%;
}

/* Bottom cluster (delta + sparkline) — margin-top:auto pour equal heights */
.jog-kpi-card__bottom {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  margin-top: auto;
  padding-top: var(--space-3);
}

.jog-kpi-card__delta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-1);
  font-size: var(--text-caption);
  font-weight: var(--fw-semibold);
  color: var(--color-fg-muted);
  line-height: var(--lh-tight);
  max-width: 100%;
}

.jog-kpi-card__delta-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.jog-kpi-card__delta-label {
  color: var(--color-fg-subtle);
  font-family: var(--font-sans);
  font-weight: var(--fw-regular);
  margin-left: var(--space-1);
}

.jog-kpi-card__delta--neutral { color: var(--color-fg-muted); }
.jog-kpi-card__delta--better  { color: var(--color-success); }
.jog-kpi-card__delta--worse   { color: var(--color-error); }

/* Sparkline container — hauteur fixe, largeur max pour rester dans la card */
.jog-kpi-card__spark {
  width: 100%;
  max-width: 140px;
  height: 28px;
  color: var(--color-fg-muted);
  display: block;
  overflow: hidden;
}
.jog-kpi-card__spark[data-sparkline-semantic="better"] { color: var(--color-success); }
.jog-kpi-card__spark[data-sparkline-semantic="worse"]  { color: var(--color-error); }

/* Interactive : quand le KPI est un lien */
a.jog-kpi-card.jog-card {
  text-decoration: none;
}

/* --- Variant --indicator : liseré gauche coloré 4 px direction (app4 DNA)
       Pattern app4 obs-indicator-card up/down/na. Liseré communique la
       direction visuellement, indépendant du delta texte/sparkline. Utile
       quand on veut montrer une tendance sémantique sans forcément avoir
       un delta chiffré (ex: "Palier atteint" avec indicateur up). --- */
.jog-kpi-card--indicator {
  position: relative;
  /* Shift le body pour laisser la place du liseré sans coller le contenu */
  padding-left: 0;
}
.jog-kpi-card--indicator .jog-kpi-card__body {
  padding-left: calc(var(--space-5) + 4px);
}
.jog-kpi-card--indicator::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  background: var(--color-fg-faint);
  border-radius: var(--r-xl) 0 0 var(--r-xl);
  /* Le border-radius du conteneur étant xl (12px), le liseré matche
     parfaitement le coin gauche. Si card variant outlined/flat, overflow
     hidden de .jog-card garde le bord propre. */
}
.jog-kpi-card--indicator-up::before    { background: var(--color-success); }
.jog-kpi-card--indicator-down::before  { background: var(--color-info); }
.jog-kpi-card--indicator-na::before    { background: var(--color-fg-faint); }
.jog-kpi-card--indicator-na           { opacity: 0.75; }

/* §14.3 .jog-stat-group — flex-wrap responsive pour collection de KPI
   ---------------------------------------------------------------------
   flex-wrap + justify-content: center résout deux problèmes que grid auto-fit
   ne gère pas :
   1) Rangées partielles centrées (ex: 7 items dans 3 cols → 4ème rangée à 1
      item, centré au lieu de collé à gauche).
   2) Viewport < min_width : flex-basis min(var(--min), 100%) empêche l'overflow
      horizontal sur phones étroits (iPhone SE 320px face à min_width 300px).
   Full rows : flex-grow:1 fait remplir la largeur uniformément comme grid 1fr.
*/

.jog-stat-group {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--jog-stat-gap, var(--space-4));
  width: 100%;
}

.jog-stat-group > * {
  /* basis = min(min_width, 100%) — jamais plus large que le container.
     grow 1 = remplit l'espace restant sur full rows.
     shrink 1 = protège contre overflow sur viewport < basis. */
  flex: 1 1 min(var(--jog-stat-min, 220px), 100%);
  max-width: 100%;
  min-width: 0;
}

/* §14.3b .jog-hero-stats-bar — barre horizontale stats airy (app5 DNA)
   ---------------------------------------------------------------------
   Pattern app5 .ex-stats-bar : flex-wrap horizontal, groupes séparés par
   borders verticales, stats compactes (num text-h3 bold + label caption
   muted + optional <em> sub). Variant --inline = sans card bg/border.
   Utilisé en haut de page browse/search pour contextualiser un dataset.
*/

.jog-hero-stats-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-6);
  align-items: stretch;
  padding: var(--space-3) var(--space-5);
  margin-bottom: var(--space-4);
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-xs);
}

.jog-hero-stats-bar--inline {
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 0;
  box-shadow: none;
  margin-bottom: var(--space-3);
  gap: var(--space-3) var(--space-5);
}

.jog-hero-stats-group {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--space-2) var(--space-5);
  padding-right: var(--space-5);
  border-right: 1px solid var(--color-border-soft);
}
.jog-hero-stats-group:last-child {
  border-right: none;
  padding-right: 0;
}

.jog-hero-stats-bar--inline .jog-hero-stats-group {
  border-right: none;
  padding-right: 0;
}

.jog-hero-stats-group__label {
  font-size: var(--text-micro);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-fg-faint);
  align-self: center;
}

.jog-hero-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  min-width: 0;
}

.jog-hero-stat__num {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-h3);
  font-weight: var(--fw-bold);
  color: var(--color-fg);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}

.jog-hero-stat__lbl {
  margin: 0;
  font-size: var(--text-caption);
  font-weight: var(--fw-regular);
  color: var(--color-fg-subtle);
  line-height: var(--lh-snug);
  white-space: nowrap;
}

.jog-hero-stat__sub {
  font-style: normal;
  font-size: var(--text-micro);
  color: var(--color-fg-faint);
  margin-left: 2px;
}

/* Semantic coloring : teinte le num selon sens métier
   (CO2 = accent orange, stock biosourcé = success, origine FR = info, etc.) */
.jog-hero-stat--accent  .jog-hero-stat__num { color: var(--color-accent); }
.jog-hero-stat--success .jog-hero-stat__num { color: var(--color-success); }
.jog-hero-stat--info    .jog-hero-stat__num { color: var(--color-info); }
.jog-hero-stat--warning .jog-hero-stat__num { color: var(--color-warning); }
.jog-hero-stat--error   .jog-hero-stat__num { color: var(--color-error); }

/* Mobile : groups stackés verticalement, border passe en bottom */
@media (max-width: 640px) {
  .jog-hero-stats-bar { padding: var(--space-3); }
  .jog-hero-stats-group {
    padding-right: 0;
    border-right: none;
    padding-bottom: var(--space-3);
    border-bottom: 1px solid var(--color-border-soft);
    width: 100%;
  }
  .jog-hero-stats-group:last-child {
    border-bottom: none;
    padding-bottom: 0;
  }
  .jog-hero-stats-bar--inline .jog-hero-stats-group {
    border-bottom: none;
    padding-bottom: 0;
  }
}

/* §14.4 .jog-chart-card — wrapper ApexCharts standardisé
   ---------------------------------------------------------------------
   Base : .jog-card variant --data (ci-dessus). Header title/subtitle +
   actions slot header droite. Body avec min-height configurable. Le rendu
   du chart est délégué à apex-theme.js (lib.renderChart) via
   data-component="chart" sur .jog-chart-body.
*/

.jog-chart-card {
  /* hérite de .jog-card */
  overflow: hidden;
}

.jog-chart-card__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--color-border-soft);
  min-width: 0;
}

.jog-chart-card--flat .jog-chart-card__header,
.jog-card--flat.jog-chart-card .jog-chart-card__header,
.jog-card--outlined.jog-chart-card .jog-chart-card__header {
  border-bottom-color: transparent;
}

.jog-chart-card__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  flex: 1;
  min-width: 0;
}

.jog-chart-card__title {
  font-size: var(--text-h3);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  color: var(--color-fg);
  margin: 0;
  /* truncate long titles */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.jog-chart-card__subtitle {
  font-size: var(--text-caption);
  color: var(--color-fg-subtle);
  line-height: var(--lh-normal);
  margin: 0;
}

.jog-chart-card__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
  margin-left: auto;
}

.jog-chart-card__body {
  position: relative;
  padding: var(--space-4) var(--space-5);
  /* min-height set inline from macro */
}

.jog-chart-body {
  width: 100%;
  /* ApexCharts writes height via its own options ; this element is
     purely a target container. */
}

.jog-chart-card__fallback {
  margin: 0;
  padding: var(--space-4);
  color: var(--color-fg-subtle);
  font-size: var(--text-caption);
  font-style: italic;
  text-align: center;
  background: var(--color-bg-soft);
  border-radius: var(--r-md);
}

/* Mobile : header wrap quand actions + title coexistent */
@media (max-width: 640px) {
  .jog-chart-card__header { flex-wrap: wrap; }
  .jog-chart-card__actions { width: 100%; justify-content: flex-end; }
}


/* ============================================================================
   §15 · INPUTS AVANCÉS — combobox, date_picker, range, search
   ============================================================================
   Macros consommatrices : shared/templates/lib/form/{search, range,
   date_picker, combobox}.html

   Conventions :
   - Tokens S1 uniquement, zéro hex.
   - Étend foundation form §11 (.jog-field, .jog-input, __control, __icon,
     __clear, __spinner).
   - Specificity nesting (décision S06) : utilities d'override nestées sous
     .jog-combobox / .jog-date-picker / .jog-range pour dépasser §11.2.
   - ARIA APG 1.2 patterns : combobox, date picker dialog, range.
   - Terrain absorbé : app5 .ex-search-input (signature airy 2px border +
     radius 10px + glow accent 3px).
============================================================================ */

/* §15.1 .jog-input--search — variant airy pour hero search
   ---------------------------------------------------------------------
   Absorbe terrain app5 `.ex-search-input` (validé prod 2026-04-20) :
   border-width 2px vs 1px standard (signature airy). Le reste (radius,
   focus glow, icon left) utilise les tokens et modifiers §11.2 existants.
   Clear button dynamique masqué par défaut (attribut [hidden]), révélé
   par initSearch quand input.value non-vide.
*/
.jog-input--search .jog-input__control {
  border-width: 2px;
  border-radius: var(--r-lg); /* 10px — dominante app5 */
  transition: border-color var(--duration-fast) var(--ease-out),
              box-shadow var(--duration-fast) var(--ease-out);
}

/* Size lg propagé au control search (cohérence app5 hero 48px) */
.jog-input--search.jog-input--lg .jog-input__control {
  min-height: var(--tap-comfy);
}

/* Clear button dynamique : hidden par défaut, révélé par initSearch
   via removeAttribute("hidden"). Ne pas styler [hidden] — c'est le
   défaut browser (display: none). */
.jog-input--search .jog-input__clear[hidden] {
  display: none;
}

/* Landmark role="search" a un outline natif sur certains screen readers :
   pas de changement visuel additionnel nécessaire. */


/* §15.2 .jog-range — slider single OU dual thumb
   ---------------------------------------------------------------------
   Terrain JOG vide. Design SOTA depuis <input type="range"> natif +
   gradient track highlight entre thumbs (décision 5.5 A : linear-gradient
   sur background vs div overlay).

   Structure :
     .jog-range > .jog-range__values > .jog-range__value
                > .jog-range__track-wrap > .jog-range__track (visuel)
                                        > .jog-range__input (natif, z top)
                                        > .jog-range__input (natif, dual)

   Gradient track : deux CSS vars --jog-range-min-pct + --jog-range-max-pct
   (0→100) drivent le background linear-gradient. Mises à jour par initRange
   au `input` event. Permet fill accent entre thumbs (dual) ou de 0 → thumb
   (single).
*/
.jog-range {
  --jog-range-min-pct: 0%;
  --jog-range-max-pct: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: 100%;
  position: relative;
}

.jog-range__values {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  font-variant-numeric: tabular-nums;
  color: var(--color-fg);
  font-weight: var(--fw-medium);
}
.jog-range__value--max { margin-left: auto; }
.jog-range__value-sep { color: var(--color-fg-faint); }

/* Track wrap relative pour empiler les 2 inputs (dual) au même endroit */
.jog-range__track-wrap {
  position: relative;
  width: 100%;
  height: var(--tap-min);            /* touch target */
  display: flex;
  align-items: center;
}

/* Track visuel derrière les inputs natifs (z=0), rempli via gradient */
.jog-range__track {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  height: 6px;
  border-radius: var(--r-pill);
  background: linear-gradient(
    to right,
    var(--color-border) 0%,
    var(--color-border) var(--jog-range-min-pct),
    var(--color-accent) var(--jog-range-min-pct),
    var(--color-accent) var(--jog-range-max-pct),
    var(--color-border) var(--jog-range-max-pct),
    var(--color-border) 100%
  );
  pointer-events: none;
}

/* Inputs natifs : transparents, z=1 (min) / z=2 (max) pour empilement.
   Thumbs stylés via pseudo-élément WebKit + Firefox.
   appearance none pour override bg browser. */
.jog-range__input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: var(--tap-min);
  background: transparent;
  margin: 0;
  padding: 0;
  appearance: none;
  -webkit-appearance: none;
  pointer-events: none;  /* override par thumbs (cross-browser) */
  z-index: 1;
}
.jog-range--dual .jog-range__input--max { z-index: 2; }

/* Track natif : transparent (on utilise notre .jog-range__track visuel) */
.jog-range__input::-webkit-slider-runnable-track {
  background: transparent;
  height: 6px;
  border: none;
}
.jog-range__input::-moz-range-track {
  background: transparent;
  height: 6px;
  border: none;
}

/* Thumbs : circles accent bordés bg-elevated 3px + shadow-sm.
   Touch target 20px visuel, hitbox natif ~44px via input height. */
.jog-range__input::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  pointer-events: auto;
  width: 20px;
  height: 20px;
  border-radius: var(--r-pill);
  background: var(--color-accent);
  border: 3px solid var(--color-bg-elevated);
  box-shadow: var(--shadow-sm);
  cursor: grab;
  transition: transform var(--duration-fast) var(--ease-out),
              box-shadow var(--duration-fast) var(--ease-out);
  margin-top: -7px;  /* center thumb on 6px track */
}
.jog-range__input::-moz-range-thumb {
  pointer-events: auto;
  width: 20px;
  height: 20px;
  border-radius: var(--r-pill);
  background: var(--color-accent);
  border: 3px solid var(--color-bg-elevated);
  box-shadow: var(--shadow-sm);
  cursor: grab;
  transition: transform var(--duration-fast) var(--ease-out),
              box-shadow var(--duration-fast) var(--ease-out);
}
.jog-range__input:hover::-webkit-slider-thumb { transform: scale(1.1); }
.jog-range__input:hover::-moz-range-thumb { transform: scale(1.1); }
.jog-range__input:active::-webkit-slider-thumb { cursor: grabbing; }
.jog-range__input:active::-moz-range-thumb { cursor: grabbing; }

.jog-range__input:focus-visible { outline: none; }
.jog-range__input:focus-visible::-webkit-slider-thumb {
  box-shadow: var(--shadow-focus);
  transform: scale(1.1);
}
.jog-range__input:focus-visible::-moz-range-thumb {
  box-shadow: var(--shadow-focus);
  transform: scale(1.1);
}

/* Disabled */
.jog-range--disabled .jog-range__track {
  filter: saturate(0.3);
  opacity: 0.5;
}
.jog-range--disabled .jog-range__input { cursor: not-allowed; }
.jog-range--disabled .jog-range__input::-webkit-slider-thumb { cursor: not-allowed; }
.jog-range--disabled .jog-range__input::-moz-range-thumb { cursor: not-allowed; }

/* Error state via .jog-field--error (cohérence §11) */
.jog-field--error .jog-range__track {
  background: linear-gradient(
    to right,
    var(--color-error-soft) 0%,
    var(--color-error-soft) var(--jog-range-min-pct),
    var(--color-error) var(--jog-range-min-pct),
    var(--color-error) var(--jog-range-max-pct),
    var(--color-error-soft) var(--jog-range-max-pct),
    var(--color-error-soft) 100%
  );
}

/* Size modifiers */
.jog-range--sm .jog-range__track-wrap { height: 32px; }
.jog-range--sm .jog-range__input { height: 32px; }
.jog-range--sm .jog-range__values { font-size: var(--text-micro); }
.jog-range--lg .jog-range__track-wrap { height: var(--tap-comfy); }
.jog-range--lg .jog-range__input { height: var(--tap-comfy); }
.jog-range--lg .jog-range__track { height: 8px; }
.jog-range--lg .jog-range__input::-webkit-slider-thumb { width: 24px; height: 24px; margin-top: -8px; }
.jog-range--lg .jog-range__input::-moz-range-thumb { width: 24px; height: 24px; }


/* §15.3 .jog-date-picker — <input type="date"> natif stylisé
   ---------------------------------------------------------------------
   Native first : picker mobile natif (iOS/Android) + picker desktop
   (Chrome/Firefox/Edge/Safari) gèrent l'a11y + format locale utilisateur.
   Calendar indicator recoloré via filter hue/sat-safe (pattern SOTA).

   Variant range : 2 <input type="date"> linked via initDatePicker
   (first.max = second.value + second.min = first.value).

   Fallback popover calendar : NON livré S7 (browser support 99%+ en 2026).
*/
.jog-date-picker {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
}

.jog-date-picker__input {
  flex: 1 1 0;
  min-width: 0;
}

/* Range separator — chevron right discret */
.jog-date-picker__sep {
  flex: 0 0 auto;
  font-size: var(--text-caption);
  color: var(--color-fg-subtle);
  font-weight: var(--fw-medium);
  user-select: none;
}

/* Mobile range : stack vertical + separator horizontal */
@media (max-width: 480px) {
  .jog-date-picker--range {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-2);
  }
  .jog-date-picker--range .jog-date-picker__sep {
    text-align: center;
    transform: rotate(90deg);
    color: var(--color-fg-faint);
  }
}

/* Calendar indicator WebKit (chrome/edge/safari) — recolor accent
   via filter. Firefox n'expose pas le pseudo-element, on garde natif. */
.jog-date-picker .jog-input__control::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: 0.6;
  transition: opacity var(--duration-fast) var(--ease-out);
  /* filter : pas de recolor systématique car Chrome/Edge 2026 ont thème
     clair/sombre auto cohérent. On règle seulement l'opacity hover. */
}
.jog-date-picker .jog-input__control::-webkit-calendar-picker-indicator:hover {
  opacity: 1;
}

/* Firefox : input date ne supporte pas ::-webkit-* mais stocke l'icon
   natif dans la barre — on laisse faire */

/* Placeholder empty state (Chrome/Edge affichent "jj/mm/aaaa")
   Couleur muted pour cohérence avec autres inputs */
.jog-date-picker .jog-input__control::-webkit-datetime-edit {
  color: var(--color-fg);
}
.jog-date-picker .jog-input__control::-webkit-datetime-edit-fields-wrapper {
  color: inherit;
}
.jog-date-picker .jog-input__control:not(:focus):invalid::-webkit-datetime-edit {
  color: var(--color-fg-faint);
}

/* Dark mode : calendar-picker-indicator icon couleur (chrome override) */
[data-theme="dark"] .jog-date-picker .jog-input__control::-webkit-calendar-picker-indicator {
  filter: invert(0.9);
}


/* §15.4 .jog-combobox — searchable select (WAI-ARIA APG 1.2)
   ---------------------------------------------------------------------
   Pattern "Editable Combobox with Both List and Inline Autocomplete".
   Keyboard : ArrowDown/Up, Enter, Escape, Home/End, type filter.

   Multi-select : chips inside input wrapper, aria-multiselectable sur
   listbox, Enter toggle sans close, Backspace remove last chip si
   filter empty.

   Specificity nesting (décision S06) : utilities option-state nestées
   sous .jog-combobox pour override sûrement les règles scope-nested
   du wrapper .jog-input foundation §11.2.
*/
.jog-combobox {
  position: relative;
  width: 100%;
}

/* Hidden inputs wrapper — zéro layout impact */
.jog-combobox__hidden {
  display: none;
}

/* Control wrap : réutilise .jog-input foundation + chevron droite */
.jog-combobox__control {
  cursor: text;
  padding-right: calc(var(--space-3) + 20px);  /* room for chevron */
}

/* Filter input atomique inside combobox — styling minimal, inherits
   de .jog-input__control via __input alternate. */
.jog-combobox .jog-combobox__input {
  flex: 1 1 0;
  min-width: 120px;
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  font-family: var(--font-sans);
  font-size: var(--text-input);
  font-weight: var(--fw-regular);
  color: var(--color-fg);
  background: transparent;
  border: none;
  outline: none;
  appearance: none;
  -webkit-appearance: none;
}
.jog-combobox--sm .jog-combobox__input {
  min-height: 32px;
  padding: var(--space-2) var(--space-3);
}
.jog-combobox--lg .jog-combobox__input {
  min-height: var(--tap-comfy);
  padding: var(--space-3) var(--space-5);
}
.jog-combobox .jog-combobox__input::placeholder {
  color: var(--color-fg-faint);
  opacity: 1;
}

/* Multi : chips list à gauche du filter input inside wrapper
   WHY pas display:contents sur <ul> : les <li> enfants gardent leur
   display:list-item par défaut → block-level dans le flex parent → chaque
   chip wrap full width sur sa propre ligne au lieu de flow inline.
   Fix : <ul> devient flex container qui wrap ses chips inline. */
.jog-combobox__control--multi {
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding-top: var(--space-2);
  padding-bottom: var(--space-2);
}
.jog-combobox__chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  list-style: none;
  margin: 0;
  padding: 0;
}
.jog-combobox__chips li {
  display: flex;
  align-items: center;
}
.jog-combobox__control--multi .jog-combobox__input {
  flex: 1 1 120px;
  min-height: auto;
  padding-top: 0;
  padding-bottom: 0;
}

/* Chevron toggle button (droite) — rotates when expanded */
.jog-combobox__toggle {
  position: absolute;
  right: var(--space-3);
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  background: transparent;
  border: none;
  color: var(--color-fg-subtle);
  cursor: pointer;
  transition: color var(--duration-fast) var(--ease-out);
}
.jog-combobox__toggle:hover { color: var(--color-fg); }
.jog-combobox__chevron {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform var(--duration-fast) var(--ease-out);
}
.jog-combobox[aria-expanded="true"] .jog-combobox__chevron,
.jog-combobox__control[aria-expanded="true"] .jog-combobox__chevron {
  transform: rotate(180deg);
}
/* aria-expanded vit sur le .jog-combobox__input (role combobox), propagé */
.jog-combobox__input[aria-expanded="true"] ~ .jog-combobox__toggle .jog-combobox__chevron {
  transform: rotate(180deg);
}

/* Listbox popover — position absolute sous le control */
.jog-combobox__listbox {
  position: absolute;
  top: calc(100% + var(--space-1));
  left: 0;
  right: 0;
  z-index: var(--z-raised);
  max-height: 280px;
  overflow-y: auto;
  margin: 0;
  padding: var(--space-1);
  list-style: none;
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
}
.jog-combobox__listbox[hidden] { display: none; }

/* Options — spécificity nesting pour override éventuelles <li> rules */
.jog-combobox .jog-combobox__option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  border-radius: var(--r-sm);
  color: var(--color-fg);
  font-size: var(--text-body);
  user-select: none;
  transition: background var(--duration-fast) var(--ease-out);
}
.jog-combobox .jog-combobox__option[hidden] { display: none; }

/* Option hover (pointer) = bg soft */
.jog-combobox .jog-combobox__option:hover {
  background: var(--color-bg-soft);
}

/* Option active (keyboard via aria-activedescendant) = bg accent-soft + strong fg */
.jog-combobox .jog-combobox__option.is-active {
  background: var(--color-accent-soft);
  color: var(--color-accent-strong);
}

/* Option selected (value choisie) = aria-selected="true" */
.jog-combobox .jog-combobox__option[aria-selected="true"] {
  font-weight: var(--fw-medium);
  color: var(--color-accent-strong);
}
.jog-combobox .jog-combobox__option[aria-selected="true"] .jog-combobox__option-check {
  opacity: 1;
}

/* Option check icon (multi) — opacity 0 par défaut, 1 si selected */
.jog-combobox__option-check {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin-left: auto;
  opacity: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: opacity var(--duration-fast) var(--ease-out);
}

/* Option label takes remaining space */
.jog-combobox__option-label {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Empty state row — only visible if listbox .is-empty */
.jog-combobox__listbox.is-empty .jog-combobox__empty { display: block; }
.jog-combobox__empty {
  padding: var(--space-3);
  text-align: center;
  color: var(--color-fg-subtle);
  font-size: var(--text-caption);
  font-style: italic;
}

/* Virtual list spacer (initCombobox ajoute padding-top/bottom pour window) */
.jog-combobox__listbox--virtual {
  position: relative;
}

/* Disabled */
.jog-combobox--disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.jog-combobox--disabled .jog-combobox__input { cursor: not-allowed; pointer-events: none; }
.jog-combobox--disabled .jog-combobox__toggle { cursor: not-allowed; pointer-events: none; }

/* Error state propagé depuis .jog-field--error */
.jog-field--error .jog-combobox__control {
  border-color: var(--color-error-border);
  background: var(--color-error-soft);
}
.jog-field--error .jog-combobox__control .jog-combobox__input:focus-visible {
  box-shadow: none;  /* focus shadow vient du wrapper via foundation §11 */
}

/* Focus visible sur le control wrap — quand __input focused, wrap prend le glow */
.jog-combobox__control:focus-within {
  border-color: var(--color-border-focus);
  box-shadow: var(--shadow-focus);
}
.jog-field--error .jog-combobox__control:focus-within {
  border-color: var(--color-error);
  box-shadow: var(--shadow-focus-error);
}

/* Scrollbar listbox discrète */
.jog-combobox__listbox::-webkit-scrollbar { width: 8px; }
.jog-combobox__listbox::-webkit-scrollbar-thumb {
  background: var(--color-border);
  border-radius: var(--r-pill);
}
.jog-combobox__listbox::-webkit-scrollbar-thumb:hover {
  background: var(--color-border-strong);
}


/* ============================================================================
   §16 · INTERACTION TABULAIRE — Tabs, row selection, pagination
   ============================================================================
   Macros consommatrices :
     shared/templates/lib/navigation/tabs.html
     shared/templates/lib/data/table.html (extension selectable)
     shared/templates/lib/data/pagination.html

   Conventions :
   - Tokens S1 uniquement, zéro hex.
   - Dark mode auto via tokens (pas de bloc [data-theme="dark"] dédié).
   - A11y WAI-ARIA APG 1.2 patterns : Tabs, Pagination (buttons).
   - Self-pattern S07 appliqué : pas de display:contents sur <ul>, utiliser
     margin/padding pour positionnement statique (pas transform conflict avec
     animations).
============================================================================ */

/* §16.1 .jog-tabs — tablist horizontal avec panels (WAI-ARIA APG)
   ---------------------------------------------------------------------
   Décision S08 §5.1 C : activation auto (défaut) OU manual via param.
   Décision S08 §5.2 A : style underline bottom (DNA JOG subtle, cohérent
   breadcrumb link underline).
   Structure : <div role="tablist"> <button role="tab"> + <section role="tabpanel">
   Keyboard : Arrows wrap, Home/End, Enter/Space (manual), Tab sort vers panel.
   Mobile : overflow-x auto + scroll-snap sur tablist si plus que 3 tabs.
*/
.jog-tabs {
  width: 100%;
}

.jog-tabs__list {
  display: flex;
  align-items: stretch;
  gap: 0;
  margin: 0;
  padding: 0;
  border-bottom: 1px solid var(--color-border);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}

/* Mobile scroll-snap pour UX smooth quand > 3 tabs */
@media (max-width: 639px) {
  .jog-tabs__list {
    scroll-snap-type: x proximity;
  }
  .jog-tabs__tab { scroll-snap-align: start; }
}

.jog-tabs__tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  flex: 0 0 auto;
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  margin: 0;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;  /* réservé pour éviter jump à activation */
  border-radius: 0;
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  color: var(--color-fg-muted);
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  appearance: none;
  -webkit-tap-highlight-color: transparent;
  transition:
    color var(--duration-fast) var(--ease-out),
    background var(--duration-fast) var(--ease-out),
    border-color var(--duration-base) var(--ease-out);
}

.jog-tabs__icon {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.jog-tabs__label {
  display: inline-block;
}

/* Hover = subtle bg + fg darker (pas d'underline anticipé) */
.jog-tabs__tab:hover:not([disabled]):not([aria-selected="true"]) {
  color: var(--color-fg);
  background: var(--color-bg-soft);
}

/* Active = underline 2px accent + color accent-strong + font-weight semibold */
.jog-tabs__tab[aria-selected="true"] {
  color: var(--color-accent-strong);
  border-bottom-color: var(--color-accent);
  font-weight: var(--fw-semibold);
}

/* Focus-visible : glow brown (shadow-focus) + inset offset pour pas couper
   l'underline. box-shadow inset pour rester dans le flow du tablist. */
.jog-tabs__tab:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--color-border-focus);
  border-radius: var(--r-sm);
}

/* Disabled */
.jog-tabs__tab[disabled],
.jog-tabs__tab[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* Sizes — parité avec .jog-btn scale */
.jog-tabs--sm .jog-tabs__tab {
  min-height: 32px;
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-caption);
  gap: var(--space-1);
}
.jog-tabs--sm .jog-tabs__icon { width: 14px; height: 14px; }

.jog-tabs--lg .jog-tabs__tab {
  min-height: var(--tap-comfy);
  padding: var(--space-3) var(--space-5);
  font-size: var(--text-h3);
  gap: var(--space-3);
}
.jog-tabs--lg .jog-tabs__icon { width: 18px; height: 18px; }

/* Panels — padding vertical, tabindex=0 pour focusable */
.jog-tabs__panel {
  padding: var(--space-5) 0;
  outline: none;
}
.jog-tabs__panel:focus-visible {
  box-shadow: var(--shadow-focus);
  border-radius: var(--r-sm);
}
.jog-tabs__panel[hidden] {
  display: none;
}


/* §16.2 ROW SELECTION — extension additive .jog-table (S06) + bulk bar
   ---------------------------------------------------------------------
   Décision S08 §5.3 A : param selectable=true sur macro table (non-breaking).
   Décision S08 §5.4 A : bar fixed bottom, absorption app5 .ex-sel-bar,
                          center-aligned (pas left-right Linear, drop stratégique).
   Décision S08 §5.5 A : max_selection optionnel (param), pattern app5 MAX=5.

   Column checkbox = première colonne, visually compact (36px width).
   accent-color brown natif (100 % cross-browser 2026). Row selected = bg
   accent-soft via aria-selected="true" (pas de class, sémantique ARIA first).
   Optional sticky-left (param sticky_left_checkbox=true) pour tables scrollables.
*/
.jog-table__checkbox-col {
  width: 36px;
  padding-left: var(--space-3);
  padding-right: var(--space-2);
  text-align: center;
  vertical-align: middle;
}
.jog-table--dense .jog-table__checkbox-col {
  padding-left: var(--space-2);
  padding-right: var(--space-1);
}

.jog-table__checkbox {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--color-accent);
  cursor: pointer;
}
.jog-table__checkbox:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
  border-radius: var(--r-sm);
}
.jog-table__checkbox:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Hit-slop via <th>/<td> padding natif (44×44 atteint avec padding row std).
   Sur dense tables, width column inchangé mais checkbox reste visuellement 16px. */

/* Row selected — aria-selected true → bg accent-soft highlight */
.jog-table tbody tr[aria-selected="true"] {
  background: var(--color-accent-soft);
}
.jog-table tbody tr[aria-selected="true"]:hover {
  background: var(--color-accent-soft);  /* pas de nouveau hover bg — garder contraste */
}
.jog-table--zebra tbody tr[aria-selected="true"]:nth-child(even),
.jog-table--zebra tbody tr[aria-selected="true"] {
  background: var(--color-accent-soft);
}

/* Sticky-left checkbox column (opt-in) — cohérent pattern .ex-cmp-label-col app5 */
.jog-table--sticky-checkbox .jog-table__checkbox-col {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--color-bg-elevated);
  border-right: 1px solid var(--color-border-soft);
}
.jog-table--sticky-checkbox thead th.jog-table__checkbox-col {
  z-index: 2;  /* au-dessus du tbody sticky-left */
  background: var(--color-bg-soft);
}
.jog-table--sticky-checkbox tbody tr[aria-selected="true"] .jog-table__checkbox-col {
  background: var(--color-accent-soft);
}

/* Bulk selection bar — fixed bottom, center-aligned (app5 .ex-sel-bar DNA) */
.jog-selection-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-5);
  background: var(--color-bg-inverted);
  color: var(--color-fg-on-inverted);
  border-top: 1px solid var(--color-border);
  box-shadow: 0 -4px 12px rgba(0, 0, 0, .15);
  z-index: var(--z-sticky);
  padding-bottom: calc(var(--space-3) + env(safe-area-inset-bottom));
}
.jog-selection-bar[hidden] {
  display: none;
}

.jog-selection-bar__count {
  font-size: var(--text-body);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
  color: var(--color-fg-on-inverted);
}

.jog-selection-bar__actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

/* Buttons inside the bar inherit ghost/primary variants from §10.1 — override
   color on ghost to stay readable on dark inverted bg */
.jog-selection-bar .jog-btn--ghost {
  color: var(--color-fg-on-inverted);
  border-color: rgba(255, 255, 255, 0.24);
}
.jog-selection-bar .jog-btn--ghost:hover:not(:disabled):not([aria-busy="true"]) {
  background: rgba(255, 255, 255, 0.08);
  color: var(--color-fg-on-inverted);
  border-color: rgba(255, 255, 255, 0.4);
}

@media (max-width: 479px) {
  .jog-selection-bar {
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
  }
}


/* §16.3 PAGINATION — <nav> + boutons numérotés + ellipsis + items-per-page
   ---------------------------------------------------------------------
   Décision S08 §5.6 B : monolithique avec info (left) / nav (center) /
   per-page (right). Flex space-between.
   Décision S08 §5.7 A : numbered pattern GitHub [1] …? [cur±1] …? [N].
   Décision S08 §5.8 B : per-page réutilise native <select> + .jog-select
   foundation S3.

   Les boutons numérotés réutilisent .jog-btn --ghost --sm + .is-active
   (pattern filter bar §10.1) — aucun CSS nouveau pour le style bouton.
   Seuls le layout nav + ellipsis + per-page select sont propres à §16.3.

   Mobile < 480 : collapse numbered à prev | current/N | next, per-page
   stack en bas. Préserve les hit targets 44×44.
*/
.jog-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-3) 0;
}

.jog-pagination__info {
  flex: 0 0 auto;
  min-width: 0;
  font-size: var(--text-caption);
  color: var(--color-fg-muted);
}

.jog-pagination__info-text {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-1);
}
.jog-pagination__info-range {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-medium);
  color: var(--color-fg);
}
.jog-pagination__info-sep {
  color: var(--color-fg-faint);
}
.jog-pagination__info-total {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-medium);
  color: var(--color-fg);
}

.jog-pagination__nav {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  flex: 1 1 auto;
  justify-content: center;
  min-width: 0;
}

/* Numbered page buttons — override .jog-btn--sm pour width min cohérente
   (chiffres 1-3 digits doivent avoir largeur stable) */
.jog-pagination__page {
  min-width: 32px;
  padding-left: var(--space-2);
  padding-right: var(--space-2);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
}
.jog-pagination__page:hover { text-decoration: none; }

/* Current page — .is-active hérite déjà de §10.1, on renforce le poids */
.jog-pagination__page.is-active {
  font-weight: var(--fw-semibold);
}

/* Ellipsis non-interactive */
.jog-pagination__ellipsis {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  height: 32px;
  padding: 0 var(--space-1);
  color: var(--color-fg-faint);
  font-weight: var(--fw-medium);
  user-select: none;
  letter-spacing: 0.1em;
}

/* Prev / Next — hériterit .jog-btn--icon-only --sm, juste précision gap */
.jog-pagination__prev,
.jog-pagination__next {
  flex: 0 0 auto;
}

/* Per-page selector — label + select inline */
.jog-pagination__per-page {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex: 0 0 auto;
}

.jog-pagination__per-page-label {
  font-size: var(--text-caption);
  color: var(--color-fg-muted);
  font-weight: var(--fw-medium);
  white-space: nowrap;
}

.jog-pagination__per-page-select {
  width: auto;
}
.jog-pagination__per-page-select .jog-select__control {
  min-width: 72px;
  padding-right: calc(var(--space-3) + 14px + var(--space-1));  /* room caret sm */
}

/* Mobile < 480 : collapse numbered, stack per-page */
@media (max-width: 479px) {
  .jog-pagination {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
  }
  .jog-pagination__info { text-align: center; }
  .jog-pagination__nav { justify-content: center; }

  /* Hide all numbered + ellipsis, keep only current page visible
     (prev | [current] | next pattern) */
  .jog-pagination__page:not(.is-active) {
    display: none;
  }
  .jog-pagination__ellipsis {
    display: none;
  }
  .jog-pagination__page.is-active {
    min-width: 80px;  /* info "X / N" visible */
  }
  .jog-pagination__per-page {
    justify-content: center;
  }
}
/* Mobile: afficher "current / total" via content injection */
@media (max-width: 479px) {
  .jog-pagination__page.is-active::after {
    content: " / " attr(data-total);
  }
}


/* ============================================================================
   §17 · FEEDBACK + STATUS — Empty state, loading, toast, confirm
   ============================================================================
   Macros consommatrices :
     shared/templates/lib/feedback/empty_state.html
     shared/templates/lib/feedback/loading_skeleton.html
     shared/templates/lib/feedback/loading_overlay.html
     shared/templates/lib/feedback/toast.html
     shared/templates/lib/feedback/confirm.html

   Conventions :
   - Tokens S1 uniquement, zéro hex.
   - Dark mode auto via tokens.
   - A11y WAI-ARIA APG : Status Messages (empty, loading), Live Region (toast),
     Alert Dialog (confirm).
   - Self-pattern S08 : preview demos fidèles à l'output macro.
   - Foundation réutilisée : keyframe jog-btn-spin §10.1, tokens --shadow-lg/xl
     pour elevation, --z-toast/--z-modal pour stacking.

   Note : l'"Alert" ARIA APG est réalisé par .jog-banner §12.2 (avec
   force_role='alert' possible) — pas de macro alert.html séparée.
============================================================================ */


/* §17.1 .jog-empty — empty state (collection vide)
   ---------------------------------------------------------------------
   2 sizes terrain validés (audit apps 4/5/6 : .obs-empty-inline /
   .ex-empty / .re-empty convergé) :
     --inline : text center discret, pas d'icon, pattern .obs-empty-inline
                / .jog-table__empty. Utiliser dans cells/containers serrés.
     --block  : icon + title + hint + actions center, padding généreux,
                bg-soft, radius-lg. Pattern .obs-empty / .ex-empty / .re-empty.
*/

.jog-empty {
  text-align: center;
  color: var(--color-fg-muted);
}

/* --- inline : compact, discret, pas d'icon. Même pattern que --block
     pour centrer le bloc <p> (flex column align-items center). --- */
.jog-empty--inline {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--space-4) var(--space-3);
  font-size: var(--text-caption);
  font-style: italic;
  color: var(--color-fg-subtle);
}

/* --- block : flex col center avec icon + title + hint + actions --- */
.jog-empty--block {
  padding: var(--space-8) var(--space-4);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  background: var(--color-bg-soft);
  border-radius: var(--r-lg);
}

.jog-empty__icon {
  width: 40px;
  height: 40px;
  color: var(--color-fg-faint);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.jog-empty__icon svg {
  width: 100%;
  height: 100%;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.jog-empty__title {
  margin: 0;
  font-size: var(--text-h3);
  font-weight: var(--fw-semibold);
  color: var(--color-fg);
  line-height: var(--lh-snug);
}

.jog-empty__hint {
  margin: 0;
  font-size: var(--text-caption);
  color: var(--color-fg-subtle);
  line-height: var(--lh-normal);
  max-width: 48ch; /* readable line length, centered via parent align-items */
}

.jog-empty__actions {
  margin-top: var(--space-2);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  justify-content: center;
}


/* §17.2 .jog-skeleton-* — loading skeletons (async content placeholder)
   ---------------------------------------------------------------------
   4 types : cards (grid) / table (rows) / facets (sidebar filter) /
             chart (single container avec bars animated).
   Animation pure CSS (gradient shift horizontal) via @keyframes
   jog-skeleton-pulse. Reduced-motion → animation OFF, placeholder static
   0.6 opacity (WCAG 2.3.3). aria-busy="true" sur container.
*/

@keyframes jog-skeleton-pulse {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Shimmer base : gradient brown-tinted sur placeholder bg-soft */
.jog-skeleton-line,
.jog-skeleton-media,
.jog-skeleton-cell,
.jog-skeleton-checkbox-box,
.jog-skeleton-bar {
  background: linear-gradient(
    90deg,
    var(--color-bg-soft) 0%,
    var(--color-bg-muted) 50%,
    var(--color-bg-soft) 100%
  );
  background-size: 200% 100%;
  animation: jog-skeleton-pulse 1.5s ease-in-out infinite;
  border-radius: var(--r-sm);
}

@media (prefers-reduced-motion: reduce) {
  .jog-skeleton-line,
  .jog-skeleton-media,
  .jog-skeleton-cell,
  .jog-skeleton-checkbox-box,
  .jog-skeleton-bar {
    animation: none;
    background: var(--color-bg-soft);
    opacity: 0.6;
  }
}

/* --- Type : cards — grid auto-fill responsive --- */
.jog-skeleton-grid--cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-4);
}
.jog-skeleton-item--card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--r-lg);
}
.jog-skeleton-media {
  height: 140px;
  border-radius: var(--r-md);
}
.jog-skeleton-lines {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.jog-skeleton-line {
  height: 10px;
  /* width inline-styled pour varied widths per-line */
}

/* --- Type : table — rows empilées --- */
.jog-skeleton-grid--table {
  border: 1px solid var(--color-border);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--color-bg-elevated);
}
.jog-skeleton-table {
  display: flex;
  flex-direction: column;
}
.jog-skeleton-row {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-border-soft);
}
.jog-skeleton-row:last-child { border-bottom: none; }
.jog-skeleton-cell {
  height: 12px;
  border-radius: var(--r-sm);
}

/* --- Type : facets — sidebar filter panel --- */
.jog-skeleton-grid--facets {
  padding: 0;
  border: none;
  background: transparent;
}
.jog-skeleton-facets {
  margin: 0;
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--r-lg);
  background: var(--color-bg-elevated);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.jog-skeleton-legend {
  height: 14px;
  margin-bottom: var(--space-2);
}
.jog-skeleton-checkbox {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.jog-skeleton-checkbox-box {
  width: 16px;
  height: 16px;
  border-radius: var(--r-sm);
  flex-shrink: 0;
}

/* --- Type : chart — 5 bars animated bottom-up --- */
.jog-skeleton-grid--chart {
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--r-lg);
  background: var(--color-bg-elevated);
  height: var(--jog-skeleton-height, 240px);
}
.jog-skeleton-chart {
  display: flex;
  align-items: flex-end;
  justify-content: space-around;
  gap: var(--space-3);
  height: 100%;
}
.jog-skeleton-bar {
  flex: 1;
  /* height variée pseudo-random via --bar-idx 0-4, stable across reloads
     pour éviter layout shift (pattern SOTA charts skeleton) */
  height: calc(40% + var(--bar-idx, 0) * 12%);
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  min-height: 20px;
  max-height: 100%;
}
.jog-skeleton-bar[style*="--bar-idx: 0"] { height: 60%; }
.jog-skeleton-bar[style*="--bar-idx: 1"] { height: 85%; }
.jog-skeleton-bar[style*="--bar-idx: 2"] { height: 45%; }
.jog-skeleton-bar[style*="--bar-idx: 3"] { height: 75%; }
.jog-skeleton-bar[style*="--bar-idx: 4"] { height: 92%; }

/* Responsive : cards stack 1 col sous 640px si responsive=true */
@media (max-width: 640px) {
  .jog-skeleton-grid--cards[data-responsive="true"] {
    grid-template-columns: 1fr;
  }
}


/* §17.3 .jog-loading-overlay — spinner overlay sur container existant
   ---------------------------------------------------------------------
   Absolute positioned (parent doit avoir position: relative). Spinner
   center, backdrop semi-transparent optionnel pour masquer le contenu
   stale pendant action async. Réutilise keyframe jog-btn-spin §10.1
   (0.8 s linear) — pas de CSS animation nouvelle.

   Pour placeholder complet du contenu (pas juste overlay), utiliser
   loading_skeleton §17.2.
*/

.jog-loading-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: var(--z-raised);
  border-radius: inherit;
  pointer-events: auto; /* capture clicks pendant loading = bloque interaction */
}

/* --- Backdrop semi-transparent (masque contenu stale) --- */
.jog-loading-overlay--backdrop {
  background: color-mix(in srgb, var(--color-bg) 65%, transparent);
  backdrop-filter: blur(1px);
  -webkit-backdrop-filter: blur(1px);
}

/* --- Spinner (réutilise keyframe jog-btn-spin §10.1) --- */
.jog-loading-overlay__spinner {
  display: inline-block;
  width: 18px;
  height: 18px;
  border: 2px solid var(--color-border);
  border-top-color: var(--color-accent);
  border-radius: 50%;
  animation: jog-btn-spin 0.8s linear infinite;
}

/* --- Sizes parité .jog-btn scale --- */
.jog-loading-overlay--sm .jog-loading-overlay__spinner {
  width: 14px;
  height: 14px;
  border-width: 2px;
}
.jog-loading-overlay--lg .jog-loading-overlay__spinner {
  width: 24px;
  height: 24px;
  border-width: 2.5px;
}

/* --- Variant fullscreen (rare, pour page-level blocking) --- */
.jog-loading-overlay--fullscreen {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  border-radius: 0;
}

/* Reduced-motion : spinner tourne plus lentement (WCAG 2.3.3) */
@media (prefers-reduced-motion: reduce) {
  .jog-loading-overlay__spinner {
    animation-duration: 2.4s;
  }
}


/* §17.4 .jog-toast-container + .jog-toast — notifications overlay
   ---------------------------------------------------------------------
   Container global <ol> auto-injecté par JS helper au premier appel
   `JOG.lib.toast(...)`. Sémantique <ol> = ordre d'arrivée préservé,
   accessible SR via role="region" aria-label.

   Position SOTA :
     Desktop ≥ 640 px : fixed top-right (visibility sans overlap content)
     Mobile  < 640 px : fixed bottom (thumb reach iOS/Android)

   Toast card earth-paper DNA : bg-elevated + border-left accent stripe
   3px variant-colored + shadow-lg + icon colored. Subtle vs banner full
   bg tint (banner = persistent page-level, toast = transient feedback).

   Auto-dismiss default 6000 ms (WCAG 2.2.1 floor 5000 + 1 s buffer).
   Hover pause (WCAG 2.2.2). 0 = persistent manuel dismiss only.
   Pas de max stack hardcoded — container overflow gracieux.
*/

.jog-toast-container {
  position: fixed;
  top: var(--space-4);
  right: var(--space-4);
  z-index: var(--z-toast);
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  max-width: min(400px, calc(100vw - var(--space-8)));
  pointer-events: none; /* container lui-même non-cliquable, toasts oui */
}

.jog-toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-left-width: 3px;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  color: var(--color-fg);
  font-size: var(--text-body);
  line-height: var(--lh-normal);
  /* Initial state : hidden off-screen right, fades in via .is-visible */
  transform: translateX(calc(100% + var(--space-4)));
  opacity: 0;
  transition:
    transform var(--duration-base) var(--ease-out),
    opacity var(--duration-base) var(--ease-out);
  will-change: transform, opacity;
}
.jog-toast.is-visible {
  transform: translateX(0);
  opacity: 1;
}
.jog-toast.is-dismissing {
  transform: translateX(calc(100% + var(--space-4)));
  opacity: 0;
}

/* --- Variants (left stripe + icon colored) --- */
.jog-toast--info    { border-left-color: var(--color-info); }
.jog-toast--success { border-left-color: var(--color-success); }
.jog-toast--warning { border-left-color: var(--color-warning); }
.jog-toast--error   { border-left-color: var(--color-error); }

.jog-toast__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  margin-top: 1px; /* optical alignment avec première ligne texte */
}
.jog-toast__icon svg {
  width: 20px;
  height: 20px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.jog-toast--info    .jog-toast__icon { color: var(--color-info); }
.jog-toast--success .jog-toast__icon { color: var(--color-success); }
.jog-toast--warning .jog-toast__icon { color: var(--color-warning); }
.jog-toast--error   .jog-toast__icon { color: var(--color-error); }

.jog-toast__body {
  flex: 1;
  min-width: 0;
  color: var(--color-fg);
}

.jog-toast__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}

.jog-toast__close {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--color-fg-subtle);
  border-radius: var(--r-sm);
  cursor: pointer;
  flex-shrink: 0;
  opacity: 0.7;
  transition:
    opacity var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out);
}
/* Hit-slop 44×44 (pattern S2 chip.close / banner.close) */
.jog-toast__close::before {
  content: "";
  position: absolute;
  inset: -10px;
}
.jog-toast__close:hover {
  opacity: 1;
  color: var(--color-fg);
  background: var(--color-bg-soft);
}
.jog-toast__close:focus-visible {
  outline: none;
  opacity: 1;
  box-shadow: var(--shadow-focus);
}
.jog-toast__close svg {
  width: 14px;
  height: 14px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
}

/* --- Mobile < 640px : bottom-center, slide-from-bottom --- */
@media (max-width: 640px) {
  .jog-toast-container {
    top: auto;
    left: var(--space-4);
    right: var(--space-4);
    bottom: var(--space-4);
    max-width: none;
  }
  .jog-toast {
    transform: translateY(calc(100% + var(--space-4)));
  }
  .jog-toast.is-visible {
    transform: translateY(0);
  }
  .jog-toast.is-dismissing {
    transform: translateY(calc(100% + var(--space-4)));
  }
}

/* --- Reduced motion : no slide, fade only (WCAG 2.3.3) --- */
@media (prefers-reduced-motion: reduce) {
  .jog-toast,
  .jog-toast.is-visible,
  .jog-toast.is-dismissing {
    transform: none;
  }
}


/* §17.5 .jog-confirm — alertdialog modal de confirmation
   ---------------------------------------------------------------------
   <dialog> natif HTML5 : focus trap + Escape close gérés nativement.
   Pattern WAI-ARIA APG "Alert Dialog" — role="alertdialog",
   aria-labelledby (title) + aria-describedby (message).

   Center positioning via UA stylesheet (inset + margin auto au
   showModal()) — on override uniquement le radius/shadow/max-width
   pour earth paper DNA. Animation scale + fade (pas slide, modal
   center).

   Pattern factor : le markup et le wire JS sont quasi-identiques au
   drawer §12.3 — même keyframe d'animation .is-open, même gestion
   close via transitionend, même cancel event intercept pour l'Escape.
   Différences : role="alertdialog" (vs "dialog"), backdrop click ne
   ferme PAS (APG safe pour action critique).
*/

.jog-confirm {
  /* Reset <dialog> browser defaults */
  padding: 0;
  border: none;
  margin: auto; /* center au showModal() */
  max-width: min(440px, 92vw);
  width: 100%;
  color: var(--color-fg);
  background: var(--color-bg-elevated);
  box-shadow: var(--shadow-xl);
  border-radius: var(--r-xl);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  will-change: transform, opacity;
  transform: scale(0.96);
  opacity: 0;
  transition:
    transform var(--duration-base) var(--ease-out),
    opacity var(--duration-base) var(--ease-out);
  z-index: var(--z-modal);
}
.jog-confirm:not([open]) {
  display: none;
}
.jog-confirm.is-open {
  transform: scale(1);
  opacity: 1;
}

/* Backdrop darker que drawer (modal est "au-dessus" sémantiquement) */
.jog-confirm::backdrop {
  background: color-mix(in srgb, #000 60%, transparent);
  opacity: 0;
  transition: opacity var(--duration-base) var(--ease-out);
}
.jog-confirm.is-open::backdrop { opacity: 1; }

/* Parts */
.jog-confirm__body {
  padding: var(--space-5) var(--space-5) var(--space-4);
}

.jog-confirm__title {
  margin: 0 0 var(--space-2) 0;
  font-size: var(--text-h2);
  font-weight: var(--fw-semibold);
  color: var(--color-fg);
  line-height: var(--lh-snug);
}

.jog-confirm__message {
  margin: 0;
  font-size: var(--text-body);
  color: var(--color-fg-muted);
  line-height: var(--lh-normal);
}

.jog-confirm__footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border-top: 1px solid var(--color-border-soft);
  background: var(--color-bg-soft);
}

/* Mobile : footer buttons full-width stacked pour thumb-reach */
@media (max-width: 480px) {
  .jog-confirm__footer {
    flex-direction: column-reverse;
    align-items: stretch;
  }
  .jog-confirm__footer .jog-btn {
    width: 100%;
    justify-content: center;
  }
}

/* Reduced motion : pas de scale, fade only */
@media (prefers-reduced-motion: reduce) {
  .jog-confirm,
  .jog-confirm.is-open {
    transform: none;
  }
  .jog-confirm,
  .jog-confirm::backdrop {
    transition-duration: 0ms;
  }
}


/* §17.6 .jog-log-stream — log journal pipeline / SSE / HTMX append-friendly
   ---------------------------------------------------------------------
   Macro : shared/templates/lib/feedback/log_stream.html
   JS    : shared-lib.js lib.initLogStream + lib.logStream API
   Pattern Phase B legacy promu (commit bae48a7). Apps 1/2/3 ingestion logs
   (HTMX polling) + App6 SSE assistant. Dark inverted bg + mono + grid layout
   (ts/level/msg). Severity color-coded badges via tokens. Auto-scroll bottom
   via MutationObserver. Cursor blink respect prefers-reduced-motion.
*/

.jog-log-stream {
  background: var(--color-bg-inverted);
  color: var(--color-fg-on-inverted);
  border: 1px solid var(--color-border);
  border-radius: var(--r-md);
  padding: var(--space-3) var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  line-height: 1.7;
  overflow-y: auto;
  overflow-x: hidden;
  scroll-behavior: smooth;
}

.jog-log-stream__list {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.jog-log-stream__line {
  display: grid;
  grid-template-columns: 70px 64px 1fr;
  gap: var(--space-3);
  align-items: baseline;
  padding: var(--space-1) 0;
  border-bottom: 1px solid color-mix(in oklab, var(--color-fg-on-inverted) 8%, transparent);
}
.jog-log-stream__line:last-child { border-bottom: 0; }

.jog-log-stream__ts {
  color: var(--color-fg-subtle);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

.jog-log-stream__level {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 2px var(--space-2);
  border-radius: var(--r-sm);
  font-weight: var(--fw-semibold);
  font-size: 10px;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  flex-shrink: 0;
}

.jog-log-stream__line[data-level="info"] .jog-log-stream__level {
  background: color-mix(in oklab, var(--color-info) 22%, transparent);
  color: var(--color-info);
}
.jog-log-stream__line[data-level="success"] .jog-log-stream__level {
  background: color-mix(in oklab, var(--color-success) 22%, transparent);
  color: var(--color-success);
}
.jog-log-stream__line[data-level="warning"] .jog-log-stream__level {
  background: color-mix(in oklab, var(--color-warning) 22%, transparent);
  color: var(--color-warning);
}
.jog-log-stream__line[data-level="error"] .jog-log-stream__level {
  background: color-mix(in oklab, var(--color-error) 25%, transparent);
  color: var(--color-error);
}

.jog-log-stream__msg {
  word-break: break-word;
  min-width: 0;
}

.jog-log-stream__detail {
  grid-column: 3 / -1;
  margin-top: var(--space-2);
  font-size: var(--text-micro);
}
.jog-log-stream__detail summary {
  cursor: pointer;
  color: var(--color-fg-subtle);
  user-select: none;
}
.jog-log-stream__detail summary:hover { color: var(--color-fg-on-inverted); }
.jog-log-stream__detail[open] summary { margin-bottom: var(--space-2); }
.jog-log-stream__detail pre {
  margin: 0;
  padding: var(--space-2);
  background: color-mix(in oklab, var(--color-fg-on-inverted) 6%, transparent);
  border-radius: var(--r-sm);
  font-size: var(--text-micro);
  white-space: pre-wrap;
  word-break: break-all;
}

.jog-log-stream__cursor {
  width: 7px;
  height: 13px;
  margin-top: var(--space-2);
  background: var(--color-fg-on-inverted);
  animation: jog-log-stream-blink 1s step-end infinite;
}

@keyframes jog-log-stream-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .jog-log-stream { scroll-behavior: auto; }
  .jog-log-stream__cursor { animation: none; opacity: 1; }
}


/* §17.7 .jog-stepper — vertical processing stepper with pulse-ring active state
   ---------------------------------------------------------------------
   Macro: shared/templates/lib/macros/stepper.html
   States via [data-state]: done (check + filled accent) / active (pulse ring) /
   pending (greyed). Connector line between dots colored by done state.
   Ported from V3b Linear preview (S18b gap caught — HTML existed without CSS).
*/
.jog-stepper {
  display: flex;
  flex-direction: column;
  gap: 0;
  width: 100%;
  /* Fluid max-width via clamp : scales with viewport.
       - mobile (vw < 1029px): floor at 360px min ; width:100% caps to
         container so no overflow on iPhone SE 320px.
       - desktop ~1440px: 35vw = ~504px (gallery-like comfortable feel).
       - 4K 2560px: capped at 640px to avoid label/time runaway gap. */
  max-width: clamp(360px, 35vw, 640px);
  margin: 0 auto;
  list-style: none;
  padding: 0;
}
.jog-step {
  display: grid;
  grid-template-columns: 28px 1fr auto;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  position: relative;
}
.jog-step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 13px; top: 36px; bottom: -8px;
  width: 2px;
  background: var(--color-border);
  border-radius: 999px;
}
.jog-step[data-state="done"]:not(:last-child)::before {
  background: var(--color-accent);
}

.jog-step-dot {
  width: 28px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  border: 2px solid var(--color-border);
  border-radius: 50%;
  background: var(--color-bg);
  font-size: var(--text-micro);
  font-weight: var(--fw-medium);
  font-family: var(--font-mono);
  position: relative;
}
.jog-step[data-state="done"] .jog-step-dot {
  background: var(--color-accent);
  color: var(--color-accent-fg);
  border-color: var(--color-accent);
}
.jog-step[data-state="active"] .jog-step-dot {
  border-color: var(--color-accent);
  color: var(--color-accent);
  /* Dot itself : breathing scale + radio-wave glow box-shadow expanding outward.
     Combined with ::after ring below, gives a robust sonar effect (3 concentric
     pulses : dot scale, glow shadow, outer ring) all synced at 1.6s. */
  animation: jog-step-dot-breathe 1.6s var(--ease-in-out, ease-in-out) infinite,
             jog-step-dot-glow 1.6s var(--ease-out, ease-out) infinite;
}
.jog-step[data-state="active"] .jog-step-dot::after {
  content: "";
  position: absolute;
  inset: -5px;
  border: 2px solid var(--color-accent);
  border-radius: 50%;
  opacity: 0.35;
  animation: jog-step-pulse-ring 1.6s var(--ease-out, ease-out) infinite;
  pointer-events: none;
}
.jog-step[data-state="pending"] .jog-step-dot {
  color: var(--color-fg-subtle);
}

/* Outer ring expanding outward (border ring grows + fades). */
@keyframes jog-step-pulse-ring {
  0%   { transform: scale(0.85); opacity: 0.55; }
  70%  { transform: scale(1.30); opacity: 0;    }
  100% { transform: scale(1.30); opacity: 0;    }
}

/* Dot itself : subtle breathing scale 1 → 1.06 → 1, in-out for natural feel. */
@keyframes jog-step-dot-breathe {
  0%, 100% { transform: scale(1);    }
  50%      { transform: scale(1.06); }
}

/* Radio-wave glow : box-shadow expanding from edge outward, fading.
   color-mix gives semi-transparent accent that doesn't override flat bg. */
@keyframes jog-step-dot-glow {
  0%   { box-shadow: 0 0 0 0   color-mix(in oklab, var(--color-accent) 55%, transparent); }
  70%  { box-shadow: 0 0 0 14px color-mix(in oklab, var(--color-accent)  0%, transparent); }
  100% { box-shadow: 0 0 0 0   color-mix(in oklab, var(--color-accent)  0%, transparent); }
}

/* Screen-reader-only utility — visually hidden but accessible to AT.
   Used by stepper to announce state (done/active/pending) to screen readers
   without leaking the text "— done" / "— active" visibly. */
.t-hidden-visually,
.jog-sr-only {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.jog-step-label {
  font-size: var(--text-body);
  font-weight: var(--fw-medium);
}
.jog-step[data-state="pending"] .jog-step-label {
  color: var(--color-fg-subtle);
  font-weight: var(--fw-regular);
}
.jog-step-time {
  font-size: var(--text-micro);
  color: var(--color-fg-muted);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

@media (prefers-reduced-motion: reduce) {
  .jog-step[data-state="active"] .jog-step-dot {
    animation: none;
    /* Static visible state for reduced-motion : keep accent-tinted glow without movement. */
    box-shadow: 0 0 0 4px color-mix(in oklab, var(--color-accent) 18%, transparent);
  }
  .jog-step[data-state="active"] .jog-step-dot::after {
    animation: none;
    opacity: 0.45;
    transform: scale(1.1);
  }
}


/* =====================================================================
   §18 LAYOUTS
   ---------------------------------------------------------------------
   Macros dans shared/templates/lib/layout/.
   5 primitives layout : stack / cluster / grid / sidebar-layout / center.
   Inspired by Every Layout (Heydon Pickering, CC BY-SA) — adapted to JOG tokens.
   Tokens-only (--space-*, --bp-*, --container-*), mobile-first, dark-mode auto.
   ===================================================================== */

/* §18.1 .jog-stack — vertical rhythm (flex column + gap tokenisé)
   ---------------------------------------------------------------------
   Remplace les inline `display:flex;flex-direction:column;gap:...` ad-hoc.
   Tous enfants espacés uniformément par gap, pas de margin-collapsing.
   Modifier classes pour gap (enum tokens canoniques).
*/

.jog-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
}

.jog-stack--gap-2 { gap: var(--space-2); }
.jog-stack--gap-3 { gap: var(--space-3); }
.jog-stack--gap-4 { gap: var(--space-4); }
.jog-stack--gap-6 { gap: var(--space-6); }


/* §18.2 .jog-cluster — horizontal wrap (flex row + flex-wrap + gap)
   ---------------------------------------------------------------------
   Pour button groups, tag/chip lists, action bars, breadcrumbs.
   Wrap natif : items qui débordent passent à la ligne, gap symétrique.
*/

.jog-cluster {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  justify-content: flex-start;
  min-width: 0;
}

.jog-cluster--gap-2 { gap: var(--space-2); }
.jog-cluster--gap-3 { gap: var(--space-3); }
.jog-cluster--gap-4 { gap: var(--space-4); }

.jog-cluster--align-start    { align-items: flex-start; }
.jog-cluster--align-center   { align-items: center; }
.jog-cluster--align-end      { align-items: flex-end; }
.jog-cluster--align-baseline { align-items: baseline; }

.jog-cluster--justify-start   { justify-content: flex-start; }
.jog-cluster--justify-center  { justify-content: center; }
.jog-cluster--justify-end     { justify-content: flex-end; }
.jog-cluster--justify-between { justify-content: space-between; }


/* §18.3 .jog-grid — responsive auto-fill grid (zéro media query)
   ---------------------------------------------------------------------
   `grid-template-columns: repeat(auto-fill, minmax(--jog-grid-min, 1fr))`
   `auto-fill` (vs auto-fit) : préserve les pistes même si peu d'items
   (Every Layout pattern). Min-width passé en inline CSS-var pour override
   par instance.
*/

.jog-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--jog-grid-min, 280px), 1fr));
  gap: var(--space-4);
  min-width: 0;
}

.jog-grid--gap-4 { gap: var(--space-4); }
.jog-grid--gap-6 { gap: var(--space-6); }


/* §18.4 .jog-sidebar-layout — main + aside (content-level primitive)
   ---------------------------------------------------------------------
   Generic 2-col primitive (aside fixed-width + main 1fr) desktop, single
   column mobile. Drop-in composable, sans ownership de <main> ni shell.
   Distinct de §13.1 .jog-app--with-sidebar (app shell) et de shells/.
   Largeur aside via inline CSS-var --jog-sidebar-width (canon 240/280/320px).
   Mobile : aside in-flow above (left) ou below (right) main, pas drawer auto.
*/

.jog-sidebar-layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  min-width: 0;
}

.jog-sidebar-layout__aside,
.jog-sidebar-layout__main {
  min-width: 0;
}

/* --- Desktop (breakpoint md = 768px) --- */
@media (min-width: 768px) {
  .jog-sidebar-layout--bp-md.jog-sidebar-layout--position-left {
    grid-template-columns: var(--jog-sidebar-width, 280px) 1fr;
  }
  .jog-sidebar-layout--bp-md.jog-sidebar-layout--position-right {
    grid-template-columns: 1fr var(--jog-sidebar-width, 280px);
  }
}

/* --- Desktop (breakpoint lg = 1024px) --- */
@media (min-width: 1024px) {
  .jog-sidebar-layout--bp-lg.jog-sidebar-layout--position-left {
    grid-template-columns: var(--jog-sidebar-width, 280px) 1fr;
    gap: var(--space-6);
  }
  .jog-sidebar-layout--bp-lg.jog-sidebar-layout--position-right {
    grid-template-columns: 1fr var(--jog-sidebar-width, 280px);
    gap: var(--space-6);
  }
}


/* §18.5 .jog-center — centering primitive (page wrapper OU horizontal)
   ---------------------------------------------------------------------
   2 modes via modifier classes :
     --page       : max-width + margin auto + padding-inline (page wrapper,
                    pattern .ex / .re terrain).
     --horizontal : flex justify-content center (single child).
   Mode page : max-width via --max-md/--max-lg/--max-xl (tokens --container-*).
*/

.jog-center {
  min-width: 0;
}

.jog-center--page {
  width: 100%;
  margin-inline: auto;
  padding-inline: var(--space-4);
}

.jog-center--max-md { max-width: var(--container-md); }
.jog-center--max-lg { max-width: var(--container-lg); }
.jog-center--max-xl { max-width: var(--container-xl); }

.jog-center--horizontal {
  display: flex;
  justify-content: center;
}

/* Padding desktop legèrement plus large (parallèle à .jog-app__main-container) */
@media (min-width: 1024px) {
  .jog-center--page {
    padding-inline: var(--space-6);
  }
}

/* ============================================================================
 * §19 OVERLAYS
 * ============================================================================
 * Overlays "flottants" attachés à un trigger (tooltip, popover, dropdown menu).
 * Différents des feedback globaux (toast, confirm) qui ne s'attachent pas.
 *
 * Z-index hierarchy (tokens.css §Z-INDEX scale) :
 *   --z-dropdown 900 < --z-popover 950 < --z-overlay 1000 <
 *   --z-modal 1100 < --z-toast 1200 < --z-tooltip 1300
 *
 * Tooltip = au-dessus de tout (jamais bloqué par modal/toast).
 * ============================================================================
 */

/* §19.1 .jog-tooltip — info contextuelle hover/focus
 *
 * Pattern absorbé app4 .obs-info-icon + .obs-tooltip (CSS L635-697).
 * 2 variants trigger : info-icon (DNA app4) | bare (caller block générique).
 * Reveal :hover delay 500ms SOTA Apple HIG/GitHub/Stripe ; :focus-within instant a11y.
 * ============================================================================
 */

.jog-tooltip-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
}

/* Trigger info-icon (DNA app4 → V3b tokens) */
.jog-info-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--color-fg-muted);
  color: var(--color-bg);
  font-family: Georgia, "Times New Roman", serif;
  font-size: 0.7rem;
  font-weight: var(--fw-bold);
  font-style: italic;
  line-height: 1;
  cursor: help;
  flex-shrink: 0;
  user-select: none;
  transition: background var(--duration-fast) var(--ease-out);
}

.jog-info-icon:hover,
.jog-info-icon:focus-visible {
  background: var(--color-accent);
  outline: none;
}

.jog-info-icon:focus-visible {
  box-shadow: var(--shadow-focus);
}

/* Tooltip surface (toujours dans le DOM, opacity-toggled) */
.jog-tooltip {
  position: absolute;
  width: max-content;
  max-width: 280px;
  padding: var(--space-3) var(--space-4);
  background: var(--color-fg);
  color: var(--color-bg);
  font-family: var(--font-sans);
  font-size: var(--text-caption);
  font-weight: var(--fw-regular);
  font-style: normal;
  line-height: var(--lh-snug);
  text-align: left;
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  z-index: var(--z-tooltip);
  transition: opacity var(--duration-fast) var(--ease-out),
              visibility 0s linear var(--duration-fast);
}

/* Placements + arrows (border-{side}-color triangle) */
.jog-tooltip--top {
  bottom: calc(100% + var(--space-2));
  left: 50%;
  transform: translateX(-50%);
}
.jog-tooltip--top::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: var(--color-fg);
}

.jog-tooltip--bottom {
  top: calc(100% + var(--space-2));
  left: 50%;
  transform: translateX(-50%);
}
.jog-tooltip--bottom::after {
  content: "";
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-bottom-color: var(--color-fg);
}

.jog-tooltip--left {
  right: calc(100% + var(--space-2));
  top: 50%;
  transform: translateY(-50%);
}
.jog-tooltip--left::after {
  content: "";
  position: absolute;
  left: 100%;
  top: 50%;
  transform: translateY(-50%);
  border: 6px solid transparent;
  border-left-color: var(--color-fg);
}

.jog-tooltip--right {
  left: calc(100% + var(--space-2));
  top: 50%;
  transform: translateY(-50%);
}
.jog-tooltip--right::after {
  content: "";
  position: absolute;
  right: 100%;
  top: 50%;
  transform: translateY(-50%);
  border: 6px solid transparent;
  border-right-color: var(--color-fg);
}

/* Reveal — :hover avec delay 500ms (SOTA) */
.jog-tooltip-wrap:hover .jog-tooltip {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--duration-fast) 500ms var(--ease-out),
              visibility 0s 500ms;
}

/* Reveal — :focus-within instant (a11y keyboard) — override hover via source order */
.jog-tooltip-wrap:focus-within .jog-tooltip {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--duration-fast) 0s var(--ease-out),
              visibility 0s 0s;
}

/* Reduced motion : neutralise transitions/delays */
@media (prefers-reduced-motion: reduce) {
  .jog-tooltip,
  .jog-tooltip-wrap:hover .jog-tooltip,
  .jog-tooltip-wrap:focus-within .jog-tooltip {
    transition: none;
  }
}

/* §19.2 .jog-popover — anchored panel via HTML Popover API natif
 *
 * Browser support 2026 : Chrome 125+, Safari 17+, Firefox 125+ (~95% global).
 * Top-layer + light-dismiss (click outside, Esc) gratuits via popover="auto".
 * Position calculée par initPopover/positionOverlay au toggle event.
 * ============================================================================
 */

.jog-popover {
  /* Override UA top-layer defaults (inset:0; margin:auto; → centre viewport) */
  inset: auto;
  margin: 0;

  /* Visual surface */
  background: var(--color-bg);
  color: var(--color-fg);
  border: 1px solid var(--color-border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  padding: var(--space-4);
  min-width: 200px;
  max-width: 320px;
  font-family: var(--font-sans);
  font-size: var(--text-body);
  line-height: var(--lh-normal);
  z-index: var(--z-popover);

  /* Animation opacity-only fade — allow-discrete pour transition display/overlay
   * (Chrome 117+ / Safari 17.4+ / Firefox 129+). */
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-out),
              overlay var(--duration-fast) allow-discrete,
              display var(--duration-fast) allow-discrete;
}

.jog-popover:popover-open {
  opacity: 1;
}

@starting-style {
  .jog-popover:popover-open {
    opacity: 0;
  }
}

.jog-popover::backdrop {
  background: transparent;
}

@media (prefers-reduced-motion: reduce) {
  .jog-popover {
    transition: none;
  }
}

/* §19.3 .jog-dropdown-menu — ARIA APG "Menu Button" pattern
 *
 * Trigger button + menu list séparés par espace-2. Position calculée par
 * initDropdownMenu/positionOverlay au open. Keyboard nav APG strict :
 * ArrowDown/Up wrap, Home/End, Esc close + return focus, Tab close, item Enter/Space.
 * ============================================================================
 */

.jog-dropdown-menu-wrap {
  position: relative;
  display: inline-block;
}

.jog-dropdown-menu__chevron {
  margin-left: var(--space-1);
}

.jog-dropdown-menu {
  /* Floating panel positioned by initDropdownMenu via positionOverlay */
  position: fixed;
  z-index: var(--z-dropdown);
  min-width: 180px;
  max-width: 280px;
  padding: var(--space-1);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  gap: 0;

  /* Default closed : invisible + non-focusable */
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--duration-fast) var(--ease-out),
              visibility 0s linear var(--duration-fast);
}

.jog-dropdown-menu.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity var(--duration-fast) var(--ease-out),
              visibility 0s 0s;
}

/* Items */
.jog-dropdown-menu__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  background: none;
  border: 0;
  border-radius: var(--r-sm);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: var(--fw-regular);
  line-height: var(--lh-snug);
  color: var(--color-fg);
  text-align: left;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.jog-dropdown-menu__item:hover {
  background: var(--color-bg-soft);
}

.jog-dropdown-menu__item:focus-visible {
  background: var(--color-accent-soft);
  color: var(--color-accent-strong);
  outline: none;
}

.jog-dropdown-menu__item--danger {
  color: var(--color-error);
}

.jog-dropdown-menu__item--danger:hover {
  background: var(--color-error-soft);
}

.jog-dropdown-menu__item--danger:focus-visible {
  background: var(--color-error-soft);
  color: var(--color-error);
}

.jog-dropdown-menu__item:disabled,
.jog-dropdown-menu__item[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* Separator */
.jog-dropdown-menu__separator {
  height: 1px;
  margin: var(--space-1) 0;
  background: var(--color-border);
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .jog-dropdown-menu,
  .jog-dropdown-menu.is-open,
  .jog-dropdown-menu__item {
    transition: none;
  }
}

/* ============================================================================
 * §20 · CHARTS variants — pre-baked use-case wrappers (S13)
 * ============================================================================
   Macros consommatrices : shared/templates/lib/charts/{chart_trends,
   chart_distribution, chart_palier, chart_lots, chart_boxplot}.html

   Conventions :
   - chart_trends, chart_distribution, chart_lots, chart_boxplot délèguent
     à chart_card S6 (§14.4) + BUILDER additif dans apex-theme.js — zéro
     CSS spécifique nécessaire (ApexCharts gère le rendu via tokens lus
     par readTokens() au build time).
   - chart_palier = HTML/CSS uniquement (pas ApexCharts) — composant
     standalone avec sa propre structure §20.1 (data-driven Jinja).
   - Tokens S1 only, dark-mode auto, mobile-first responsive.
============================================================================ */

/* §20.1 .jog-chart-palier — distribution palier RE2020 (HTML/CSS bars + applicable highlight)
   ---------------------------------------------------------------------
   Réutilise foundation chart_card §14.4 pour structure/header/body :
     .jog-card.jog-chart-card.jog-chart-palier
       > [.jog-chart-card__header (title + subtitle, padding space-4 space-5)]
       > .jog-chart-card__body (padding space-4 space-5)
           > <ol.jog-chart-palier__list>
               <li.jog-chart-palier__row[.is-applicable]>
                 .jog-chart-palier__label
                 .jog-chart-palier__bar-track > .jog-chart-palier__bar-fill
                 .jog-chart-palier__count
                 .jog-chart-palier__pct
               </li>
             </ol>

   Padding identical à chart_trends/chart_distribution/chart_lots/chart_boxplot
   (cohérence cross-component group "Charts"). Aucune réinvention de
   __palier__header / __title / __subtitle — foundation S6 réutilisée.

   Applicable highlight :
   - .is-applicable bg --color-accent-soft + border --color-accent
   - bar-fill --color-accent (vs --color-fg-muted pour autres rows)
   - tabular-nums + mono sur count/pct
*/

.jog-chart-palier__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.jog-chart-palier__row {
  display: grid;
  grid-template-columns: minmax(140px, 1fr) 2fr 48px 56px;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  font-size: var(--text-body);
  color: var(--color-fg);
  transition: background var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out);
}

.jog-chart-palier__row.is-applicable {
  background: var(--color-accent-soft);
  border-color: var(--color-accent);
}

.jog-chart-palier__label {
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
}

.jog-chart-palier__bar-track {
  display: block;
  width: 100%;
  height: 10px;
  background: var(--color-bg-muted);
  border-radius: 5px;
  overflow: hidden;
}

.jog-chart-palier__bar-fill {
  display: block;
  height: 100%;
  background: var(--color-fg-muted);
  border-radius: inherit;
  transition: width var(--duration-base) var(--ease-out);
}

.jog-chart-palier__row.is-applicable .jog-chart-palier__bar-fill {
  background: var(--color-accent);
}

.jog-chart-palier__count,
.jog-chart-palier__pct {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
  text-align: right;
}

.jog-chart-palier__count {
  color: var(--color-fg);
  font-weight: var(--fw-semibold);
}

.jog-chart-palier__pct {
  color: var(--color-fg-muted);
  font-weight: var(--fw-regular);
}

@media (max-width: 640px) {
  .jog-chart-palier__row {
    grid-template-columns: 1fr auto auto;
    grid-template-areas:
      "label label label"
      "bar   count pct";
    gap: var(--space-1) var(--space-3);
    padding: var(--space-2);
  }
  .jog-chart-palier__label    { grid-area: label; }
  .jog-chart-palier__bar-track { grid-area: bar; }
  .jog-chart-palier__count    { grid-area: count; }
  .jog-chart-palier__pct      { grid-area: pct; }
}

@media (prefers-reduced-motion: reduce) {
  .jog-chart-palier__bar-fill,
  .jog-chart-palier__row {
    transition: none;
  }
}

/* ============================================================================
 * §21 · GRAPH — D3 Canvas force simulation (S14)
 * ============================================================================
   Macro consommatrice : shared/templates/lib/graph/chart_graph.html
   Runtime : shared/static/vendor/d3.v7.min.js + shared/static/lib/graph-d3.js

   Conventions :
   - Réutilise foundation chart_card §14.4 pour structure/header/body :
     .jog-card.jog-chart-card.jog-graph-card
       > [.jog-chart-card__header (title + subtitle + actions)]
       > .jog-chart-card__body
           > .jog-graph-body (canvas container, padding-less)
               > .jog-graph__tooltip (absolute, hidden by default)
               > <canvas.jog-graph__canvas>
   - Aucune réinvention de __header / __title / __subtitle — foundation
     §14.4 réutilisée (self-pattern S13 NEW : composer foundation classes).
   - Component-specific atoms : .jog-graph-body, .jog-graph__canvas,
     .jog-graph__tooltip — pas d'équivalent foundation.
   - Tokens S1 only, dark-mode auto, mobile-first responsive.
   - Cluster colors résolus côté graph-d3.js via tokens.palette
     (--chart-1..10, cycle modulo si > 10 clusters).
============================================================================ */

/* §21.1 .jog-graph-body — canvas wrapper inside .jog-chart-card__body
   Override le padding du body (canvas full-bleed) tout en gardant la
   hauteur min du body via la prop inline `min-height` du macro. */
.jog-chart-card__body:has(> .jog-graph-body) {
  padding: 0;
}

.jog-graph-body {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: inherit;
  background: var(--color-bg);
  overflow: hidden;
}

.jog-graph__canvas {
  display: block;
  width: 100%;
  height: 100%;
  cursor: grab;
  touch-action: none;
}

.jog-graph__canvas:active {
  cursor: grabbing;
}

/* Neighborhood mode = subgraph compact, default cursor (no pan tease) */
.jog-graph-card--neighborhood .jog-graph__canvas {
  cursor: default;
}

.jog-graph__tooltip {
  position: absolute;
  pointer-events: none;
  z-index: 5;
  max-width: 280px;
  padding: var(--space-2) var(--space-3);
  background: var(--color-bg-elevated);
  color: var(--color-fg);
  border: 1px solid var(--color-border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-2);
  font-family: var(--font-ui);
  font-size: var(--text-caption);
  line-height: var(--lh-snug);
  word-wrap: break-word;
}

.jog-graph__tooltip[hidden] {
  display: none;
}

/* Sensible default heights — consumer can override via macro `height` param.
   Full mode = exploration immersive (60vh by default).
   Neighborhood mode = compact embed (320px). */
.jog-graph-card--full .jog-chart-card__body { min-height: 60vh; }
.jog-graph-card--neighborhood .jog-chart-card__body { min-height: 320px; }

@media (max-width: 640px) {
  .jog-graph-card--full .jog-chart-card__body { min-height: 50vh; }
  .jog-graph-card--neighborhood .jog-chart-card__body { min-height: 240px; }
}


/* =====================================================================
   §22. SHELLS — page-level layout panes (pipeline / dashboard / explorer)
   =====================================================================
   Wrappers consommés par shells/{pipeline,dashboard,explorer}.html.
   Distinction vs §18 layout primitives (.jog-stack/-cluster/-grid/
   -sidebar-layout/-center) qui sont content-level réutilisables : ces
   classes-ci sont page-level, wedded à <main>, une instance par page.

   Mobile-first : single column en dessous de --bp-lg (1024px).
   Tokens-only, dark-mode auto.
   --------------------------------------------------------------------- */

/* §22.1 .jog-pane-2col — pipeline shell (cluster A, app1/2/3)
   Layout : aside config 380px + section work 1fr (desktop ≥ 1024px).
   Aside au-dessus de section sur mobile (HTML order).
   Edge-to-edge : pas de padding outer (l'app est une app, pas une webpage).
   Padding interne via les cards / stack à l'intérieur de chaque pane. */
.jog-pane-2col {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  width: 100%;
  padding: 0;
}

.jog-pane-l,
.jog-pane-r {
  min-width: 0;  /* prevent grid blowout on long content */
  padding: var(--space-4);
}
/* Pane-r becomes a flex column so its child wrappers (e.g. #work-pane
   marked .jog-stretch) can fill the pane height. Grid items already
   stretch vertically by default — flex on the item allows nested
   stretching down to the card / stepper. */
.jog-pane-r {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.jog-pane-l {
  border-bottom: 1px solid var(--color-border);
}

@media (min-width: 1024px) {
  .jog-pane-2col {
    grid-template-columns: 380px 1fr;
  }
  .jog-pane-l,
  .jog-pane-r {
    padding: var(--space-6);
  }
  .jog-pane-l {
    border-bottom: none;
  }
}

/* §22.2 .jog-pane-full — dashboard shell (cluster B, app4)
   Full-width content sous la sticky filter_bar. */
.jog-pane-full {
  width: 100%;
  padding: var(--space-6);
}

@media (min-width: 1024px) {
  .jog-pane-full {
    padding: var(--space-8);
  }
}

/* §22.3 .jog-pane-explorer — explorer shell (cluster C, app5/6)
   Layout : facets aside 280px + results 1fr + optional drawer 480px.
   data-drawer="open" → 3-col grid sur desktop, drawer rendu par template.
   data-drawer="closed" → 2-col grid (facets + results). */
.jog-pane-explorer {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6);
  width: 100%;
  padding: var(--space-6);
}

.jog-pane-explorer > aside,
.jog-pane-explorer > section {
  min-width: 0;
}

@media (min-width: 1024px) {
  .jog-pane-explorer {
    grid-template-columns: 280px 1fr;
    gap: var(--space-6);
    padding: var(--space-8);
  }
  .jog-pane-explorer[data-drawer="open"] {
    grid-template-columns: 280px 1fr 480px;
  }
}
