/* ============================================================================
   Mandy — Design System · Components
   ----------------------------------------------------------------------------
   Framework-free. Every class is namespaced `m-` and standalone, so each maps
   to one ERB partial. Depends only on tokens.css. Interactive behaviour lives
   in Stimulus controllers (data-controller="…") — see mandy/controllers/.
   ============================================================================ */

/* —— Base / typography helpers ———————————————————————————————————————————— */
.m-body { font-family: var(--font-sans); font-size: var(--text-base); line-height: var(--leading); color: var(--text-default); background: var(--bg); }
.m-h1 { font-size: var(--text-2xl); font-weight: var(--fw-bold); letter-spacing: -.02em; color: var(--text-strong); margin: 0; }
.m-h2 { font-size: var(--text-xl); font-weight: var(--fw-bold); letter-spacing: -.015em; color: var(--text-strong); margin: 0; }
.m-h3 { font-size: var(--text-lg); font-weight: var(--fw-semibold); color: var(--text-strong); margin: 0; }
/* Display headings and the brand lockup use the Varela Round brand face. Its
   rounded shapes don't want negative tracking, so reset letter-spacing. */
.m-h1, .m-h2, .m-h3, .m-sidebar__name, .m-auth__name { font-family: var(--font-display); letter-spacing: normal; }
.m-text-muted { color: var(--text-muted); }
.m-link { color: var(--brand-text); font-weight: var(--fw-semibold); text-decoration: none; }
.m-link:hover { text-decoration: underline; }

/* —— Buttons ——————————————————————————————————————————————————————————————
   <button class="m-btn m-btn--primary">Label</button>
   Variants: --primary --secondary --ghost --danger · Sizes: --sm --lg · --icon */
.m-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  height: 42px; padding: 0 18px;
  font-family: var(--font-sans); font-size: var(--text-base); font-weight: var(--fw-semibold);
  border: 1px solid transparent; border-radius: var(--radius-md);
  background: transparent; color: var(--text-default);
  cursor: pointer; white-space: nowrap; user-select: none; text-decoration: none;
  /* Only transition interaction effects, NOT background/border-color: those derive
     from themed custom properties, and transitioning them freezes the old value
     when the theme/brand token changes on <html>. */
  transition: filter .12s ease, box-shadow .12s ease;
}
/* :not(.m-icon) because this selector outranks `.m-icon` on specificity (0,1,1 vs
   0,1,0): left alone, its fixed 18px beat the icon's own em sizing and a small
   button's 13px label still got an 18px glyph. Raw inline SVGs passed as button
   content still need it. */
.m-btn svg:not(.m-icon) { width: 18px; height: 18px; flex-shrink: 0; }

/* The sprite is a definitions block, never drawn. `display: none` on the <svg>
   itself would stop Safari resolving `<use>` into it, so it is collapsed to
   nothing instead. */
.m-icon-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* Icons carry no colour: they take the stroke of whatever text sits beside them,
   which is what lets one icon work inside a primary, ghost and danger button
   without any of them knowing about it. Sized in `em` so an icon follows its
   button's font size rather than needing a variant of its own. */
.m-icon {
  width: 1.15em; height: 1.15em; flex-shrink: 0;
  fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.m-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--brand-ring); }
.m-btn:disabled { opacity: .5; cursor: not-allowed; }

.m-btn--primary   { background: var(--brand); color: var(--brand-on); box-shadow: var(--shadow-sm); }
.m-btn--primary:hover:not(:disabled)   { filter: brightness(1.06); }
.m-btn--secondary { background: var(--surface); color: var(--text-default); border-color: var(--border-strong); }
.m-btn--secondary:hover:not(:disabled) { border-color: var(--text-muted); }
.m-btn--ghost     { background: transparent; color: var(--text-default); border-color: transparent; }
.m-btn--ghost:hover:not(:disabled)     { background: var(--row-hover); }
.m-btn--danger    { background: var(--danger); color: var(--on-solid); }
.m-btn--danger:hover:not(:disabled)    { filter: brightness(1.06); }
/* Ghost+danger combo (a discreet destructive row action, e.g. DeleteButton::Component):
   two classes beat .m-btn--danger's one on specificity, so this always wins over the
   solid danger fill above regardless of source order — no !important needed. Without
   it, "ghost danger" silently rendered as a solid red block (the reported bug). */
.m-btn--ghost.m-btn--danger  { background: transparent; color: var(--danger-text); }
.m-btn--ghost.m-btn--danger:hover:not(:disabled) { background: var(--danger-soft); filter: none; }

/* 34px is the VISUAL height; the hit target is grown to 40px by the overlay, the
   same technique as the breadcrumb. Raising the height itself would make these
   buttons look like the big ones — the design system promises a >=40px target, not
   a 40px button — and forty of them exist: row actions, toolbar buttons, form
   submits. So this belongs on the size, not on one context.

   `margin-block` reserves the overlay's 3px overhang ON THE BUTTON. Keying that
   reserve on the previous sibling, as an earlier version did, was wrong in both
   directions: a control wrapped one level deeper broke the adjacency and got no
   reserve at all, while a filter bar — where the field and the submit ARE siblings —
   got 8px it never needed, dropping the submit 4px below the field it belongs to.
   The overhang belongs to the button, so the reserve does too: symmetric, hence
   inert under `align-items: center`, and insensitive to any wrapping.

   It costs 6px of height wherever a small button sets the line height — a table row
   included. Only the delta is stated, deliberately: it follows from `margin-block`
   alone and holds anywhere. Absolute row heights were written here three times and
   were wrong three times, because each was derived from the declared sizes instead
   of observed, and the cell's line box adds a baseline strut that no arithmetic on
   this rule predicts. If you need the absolute, measure the row you care about.

   Beware `box-sizing: content-box`, which this design system uses throughout: every
   height declared in `mandy/` is a CONTENT height, so a control at `height: 36px`
   with a 1px border is a 38px box. Two people recomputing this target from the
   declared values both got it wrong in the same direction.

   In compact density the reserve buys nothing: `[data-density="compact"] .m-btn`
   lifts the height to 38px, which with the 1px borders is already a 40px box, so
   there is no overhang to reserve — and it still costs the 6px. Making the rule
   conditional would make it contextual again, which is exactly what was just
   removed, so this is a known and accepted waste rather than an oversight.

   One latent trap, not reachable today: `.m-btn--block` sets `display: flex`, which
   makes the button block-level, and block-level vertical margins DO collapse — two
   stacked `--sm` block buttons would sit 3px apart and their overlays would overlap
   by 3px. No caller combines `block: true` with `size: :sm`; every `block: true` in
   the repository uses the default size, which has no overlay.

   That is a real reflow, and the earlier claim of none held only while the reserve
   sat on somebody else. Six pixels is what honouring a 40px target costs on a 34px
   control, and shrinking the target is the thing we are not doing. */
.m-btn--sm { position: relative; margin-block: 3px; height: 34px; padding: 0 13px; font-size: var(--text-sm); }
.m-btn--sm::after { content: ""; position: absolute; left: 50%; top: 50%; width: max(100%, 40px); height: 40px; transform: translate(-50%, -50%); }
.m-btn--lg   { height: 48px; padding: 0 24px; font-size: var(--text-md); }
.m-btn--icon { width: 42px; height: 42px; padding: 0; }
.m-btn--icon.m-btn--sm { width: 34px; height: 34px; }
.m-btn--block { display: flex; width: 100%; }

/* —— Form fields ——————————————————————————————————————————————————————————
   <label class="m-label">…</label><input class="m-input"> */
.m-label { display: block; font-size: var(--text-sm); font-weight: var(--fw-semibold); color: var(--text-strong); margin-bottom: var(--space-2); }
.m-input, .m-select, .m-textarea {
  width: 100%; height: 42px; padding: 0 13px;
  font-family: var(--font-sans); font-size: var(--text-base); color: var(--text-default);
  background: var(--input-bg); border: 1px solid var(--border-strong); border-radius: var(--radius-md);
  transition: box-shadow .12s ease;
}
.m-textarea { height: auto; min-height: 96px; padding: 11px 13px; resize: vertical; line-height: var(--leading); }
.m-input::placeholder, .m-textarea::placeholder { color: var(--text-muted); }
.m-input:focus, .m-select:focus, .m-textarea:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-ring); }
.m-input:disabled, .m-select:disabled { background: var(--surface-2); color: var(--text-muted); cursor: not-allowed; }
.m-input--error, .m-input--error:focus { border-color: var(--danger); box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 22%, transparent); }
.m-select {
  appearance: none; cursor: pointer; padding-right: 38px;
  /* A data URI is a string, so no var() can reach inside it — this chevron's stroke
     cannot be a token. The value is --text-muted's light-theme grey; painting it from a
     token would mean masking a pseudo-element, which a bare <select> cannot carry, so
     it would take a wrapper around every select in the product. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%238b938d' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); /* colour-literal-ok */
  background-repeat: no-repeat; background-position: right 12px center;
}
.m-help  { font-size: var(--text-sm); color: var(--text-muted); margin-top: var(--space-2); }
.m-error { font-size: var(--text-sm); color: var(--danger-text); margin-top: var(--space-2); }

/* Field group: the default form row — a label (or fieldset legend) stacked above
   its control(s), with a consistent gap to the next group. Controls inside stack
   vertically; wrap them in .m-field-row to lay several out on one line. */
.m-field-group { display: flex; flex-direction: column; gap: var(--space-2); }
/* Inside a group the gap owns the label/control/help spacing — neutralize the
   standalone margins those pieces carry for use outside a group (otherwise gap
   stacks on top of them and the rhythm doubles). */
.m-field-group > .m-label { margin-bottom: 0; }
.m-field-group > .m-help,
.m-field-group > .m-error { margin: 0; }
.m-field-group + .m-field-group,
.m-field-group + fieldset,
fieldset + .m-field-group { margin-top: var(--space-5); }
/* Strip the native fieldset chrome but leave margin-top to the rhythm rules
   above (resetting it here would clobber the gap before a fieldset group). */
fieldset.m-field-group { border: 0; padding: 0; min-width: 0; margin-inline: 0; margin-bottom: 0; }
.m-field-row { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.m-field-row > * { flex: 1 1 160px; min-width: 0; }
/* Groups laid out side by side share a baseline — the vertical rhythm above must
   not push later groups down within the row. */
.m-field-row .m-field-group { margin-top: 0; }

/* —— Filter bar ————————————————————————————————————————————————————————————
   FilterBar::Component: a form's filter/search row — controls laid out on one
   line instead of stacked (the .m-select/.m-input 100% width below stretches
   each one full-width and drops the submit onto its own line underneath),
   sized to their content, wrapping onto a new line on a narrow screen, submit
   aligned alongside them. */
.m-filter-bar { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
/* Sized to content rather than stretched full width — but with a floor: a bare
   `width: auto` select is as wide as its *current* option, so it jumps every time
   the user picks a shorter one, and a long option overflows the row. */
/* A field group stacks its label above its control, so it is taller than a bare
   button beside it — and `align-items: center` then centres the whole group,
   leaving the button above the control it belongs to. Measured on /listings before
   this rule: 13.42px too high.

   Bottom-aligning the buttons puts them level with the control, because the group's
   bottom IS its control's bottom. `:has()` scopes it to bars that actually carry a
   label; a bar of bare controls keeps centre alignment, where it is already right.

   A residual 1px remains: `box-sizing: content-box` makes the input a 44px box
   (42 + 2x1px border) against the button's 42px, so bottom-aligned their centres
   differ by one pixel. Chasing it would mean hard-coding a border width here. */
.m-filter-bar:has(.m-field-group) > .m-btn { align-self: flex-end; }
.m-filter-bar .m-select, .m-filter-bar .m-input { width: auto; min-width: 180px; max-width: 100%; }

/* Field with leading icon: <div class="m-field"><svg/><input class="m-field__input"></div> */
.m-field { display: flex; align-items: center; gap: var(--space-2); height: 42px; padding: 0 13px; background: var(--input-bg); border: 1px solid var(--border-strong); border-radius: var(--radius-md); }
.m-field:focus-within { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-ring); }
.m-field svg { width: 17px; height: 17px; color: var(--text-muted); flex-shrink: 0; }
.m-field__input { flex: 1; min-width: 0; border: none; outline: none; background: none; font: inherit; color: var(--text-default); }
.m-field__input::placeholder { color: var(--text-muted); }

/* —— Checkbox / Radio / Switch ————————————————————————————————————————————— */
.m-check, .m-radio { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-base); color: var(--text-default); cursor: pointer; }
.m-check input, .m-radio input { width: 18px; height: 18px; accent-color: var(--brand); cursor: pointer; }
.m-switch { position: relative; display: inline-flex; align-items: center; gap: var(--space-3); cursor: pointer; }
.m-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.m-switch__track { width: 40px; height: 22px; border-radius: var(--radius-pill); background: var(--border-strong); flex-shrink: 0; }
.m-switch__track::after { content: ""; position: absolute; left: 3px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; border-radius: 50%; background: var(--on-solid); box-shadow: var(--shadow-sm); transition: left .15s ease; }
.m-switch input:checked + .m-switch__track { background: var(--brand); }
.m-switch input:checked + .m-switch__track::after { left: 21px; }
.m-switch input:focus-visible + .m-switch__track { box-shadow: 0 0 0 3px var(--brand-ring); }

/* —— Badges / status ——————————————————————————————————————————————————————
   <span class="m-badge m-badge--success">Encaissé</span> · --dot for a leading dot */
.m-badge { display: inline-flex; align-items: center; gap: var(--space-2); padding: 4px 11px; font-size: var(--text-sm); font-weight: var(--fw-semibold); line-height: 1.4; border-radius: var(--radius-pill); }
.m-badge--brand   { background: var(--brand-soft);   color: var(--brand-text); }
.m-badge--info    { background: var(--info-soft);    color: var(--info); }
.m-badge--success { background: var(--success-soft); color: var(--success); }
.m-badge--warning { background: var(--warning-soft); color: var(--warning); }
.m-badge--danger  { background: var(--danger-soft);  color: var(--danger-text); }
.m-badge--neutral { background: var(--surface-2);    color: var(--text-muted); border: 1px solid var(--border); }
.m-badge--dot::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }

/* —— Work queues (dashboard) ————————————————————————————————————————————————
   A list of rows, each linking to a module with a trailing count badge. The
   whole row is the click target. */
.m-worklist { display: flex; flex-direction: column; }
.m-worklist__row { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) 0; color: inherit; text-decoration: none; border-bottom: 1px solid var(--border); }
.m-worklist__row:first-child { padding-top: 0; }
.m-worklist__row:last-child { padding-bottom: 0; border-bottom: 0; }
.m-worklist__label { font-size: var(--text-sm); font-weight: var(--fw-medium); color: var(--text-strong); }
.m-worklist__row:hover .m-worklist__label { color: var(--brand-text); }
.m-worklist__row .m-badge { margin-left: auto; }
.m-worklist__go { display: grid; place-items: center; color: var(--text-muted); }
.m-worklist__go svg { width: 16px; height: 16px; }

/* Vertical stack of full-width shortcut buttons (dashboard aside). */
.m-shortcuts { display: flex; flex-direction: column; gap: var(--space-3); }

/* —— Breadcrumb ————————————————————————————————————————————————————————————
   <nav class="m-breadcrumb"><a class="m-breadcrumb__link">…</a><svg …/>
     <span class="m-breadcrumb__current">…</span></nav> */
/* row-gap outweighs column-gap because the links carry a 40px hit target (below).
   The number is arithmetic, not taste: 13px text at line-height 1.45 is an 18.85px
   line box, so a 16px row-gap puts adjacent line centres 34.85px apart and two
   40px targets still overlap by ~5px — a tap near the boundary hits the later-painted
   crumb. 24px puts the centres 42.85px apart, clear of the targets. */
.m-breadcrumb { display: flex; align-items: center; flex-wrap: wrap; column-gap: var(--space-2); row-gap: var(--space-6); margin-bottom: var(--space-4); font-size: var(--text-sm); }
.m-breadcrumb__link { position: relative; color: var(--text-muted); text-decoration: none; }
/* A 13px inline link is a ~19px tall target, against the >=40px this design system
   promises. The target is grown with an overlay rather than padding or min-height,
   so the crumb keeps its visual rhythm: padding would push the page content down
   by 21px on every detail screen. */
/* `max(100%, 40px)` and not `right: 0`: the link is the containing block, so a
   short crumb ("Baux") left a ~30x40 target and a tap just beside the letters
   missed. Centred on the text so it grows symmetrically. */
.m-breadcrumb__link::after { content: ""; position: absolute; left: 50%; top: 50%; width: max(100%, 40px); height: 40px; transform: translate(-50%, -50%); }
.m-breadcrumb__link:hover { color: var(--brand-text); }
.m-breadcrumb__current { color: var(--text-strong); font-weight: var(--fw-medium); }
.m-breadcrumb__sep { width: 14px; height: 14px; flex-shrink: 0; color: var(--text-muted); }

/* —— Empty state ——————————————————————————————————————————————————————————
   <div class="m-empty"><span class="m-empty__icon">…</span>
     <p class="m-empty__title">…</p><p class="m-empty__msg">…</p></div> */
.m-empty { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); padding: var(--space-7) var(--space-5); text-align: center; }
.m-empty__icon { display: grid; place-items: center; width: 48px; height: 48px; margin-bottom: var(--space-2); border-radius: var(--radius-lg); background: var(--surface-2); color: var(--text-muted); }
.m-empty__icon svg { width: 24px; height: 24px; }
/* margin: 0 because the component renders these as <p>: the browser's default
   paragraph margin would stack on top of the flex gap above, making every empty
   state taller than designed and unevenly spaced between title and message. */
.m-empty__title { margin: 0; font-size: var(--text-base); font-weight: var(--fw-semibold); color: var(--text-strong); }
.m-empty__msg { margin: 0; max-width: 340px; font-size: var(--text-sm); color: var(--text-muted); }

/* —— Avatar ———————————————————————————————————————————————————————————————— */
.m-avatar { display: inline-grid; place-items: center; width: 34px; height: 34px; border-radius: 50%; background: var(--brand-soft); color: var(--brand-text); font-size: var(--text-sm); font-weight: var(--fw-bold); }
.m-avatar--sm { width: 28px; height: 28px; font-size: var(--text-xs); }
.m-avatar--lg { width: 44px; height: 44px; font-size: var(--text-md); }

/* —— Card —————————————————————————————————————————————————————————————————— */
.m-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-md); overflow: hidden; }
.m-card--flat { box-shadow: none; }
.m-card__head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); padding: var(--space-4) var(--space-5); border-bottom: 1px solid var(--border); }
.m-card__title { font-size: var(--text-md); font-weight: var(--fw-semibold); color: var(--text-strong); margin: 0; }
.m-card__body { padding: var(--space-5); }
.m-card__foot { padding: var(--space-4) var(--space-5); border-top: 1px solid var(--border); display: flex; align-items: center; justify-content: flex-end; gap: var(--space-3); }

/* —— Stat card ————————————————————————————————————————————————————————————— */
.m-stat { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-4) var(--space-5); box-shadow: var(--shadow-sm); }
.m-stat__top { display: flex; align-items: center; gap: var(--space-3); }
.m-stat__icon { display: grid; place-items: center; width: 38px; height: 38px; border-radius: var(--radius-md); background: var(--brand-soft); color: var(--brand-text); }
.m-stat__icon svg { width: 19px; height: 19px; }
.m-stat__label { font-size: var(--text-sm); font-weight: var(--fw-medium); color: var(--text-muted); }
.m-stat__value { display: flex; align-items: baseline; gap: var(--space-3); margin-top: var(--space-3); font-size: var(--text-3xl); font-weight: var(--fw-bold); letter-spacing: -.02em; color: var(--text-strong); white-space: nowrap; }
.m-stat__delta { display: inline-flex; align-items: center; gap: 3px; font-size: var(--text-sm); font-weight: var(--fw-semibold); color: var(--success); white-space: nowrap; }
.m-stat__delta svg { width: 13px; height: 13px; }

/* —— Tabs ——————————————————————————————————————————————————————————————————
   <div class="m-tabs" data-controller="tabs"> <button class="m-tab is-active">…</button> */
.m-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); }
.m-tab { padding: 12px 16px; border: none; background: none; font: inherit; font-size: var(--text-base); font-weight: var(--fw-medium); color: var(--text-muted); border-bottom: 2px solid transparent; margin-bottom: -1px; border-radius: var(--radius-sm) var(--radius-sm) 0 0; cursor: pointer; }
.m-tab:hover { color: var(--text-default); background: var(--row-hover); }
.m-tab.is-active { color: var(--brand-text); border-bottom-color: var(--brand); font-weight: var(--fw-semibold); }

/* —— Table ————————————————————————————————————————————————————————————————— */
.m-table-wrap { overflow-x: auto; }
.m-table { width: 100%; border-collapse: collapse; font-size: var(--text-base); }
.m-table th { text-align: left; padding: 13px 16px; font-size: var(--text-xs); font-weight: var(--fw-semibold); letter-spacing: .02em; color: var(--text-muted); background: var(--surface-2); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); white-space: nowrap; }
.m-table td { padding: 15px 16px; border-bottom: 1px solid var(--border); vertical-align: top; color: var(--text-default); }
.m-table tbody tr:last-child td { border-bottom: none; }
.m-table tbody tr:hover td { background: var(--row-hover); }
.m-table--compact th, .m-table--compact td { padding-top: 10px; padding-bottom: 10px; }
.m-table__num { text-align: right; font-variant-numeric: tabular-nums; }
.m-cell-strong { font-weight: var(--fw-semibold); color: var(--text-strong); }
.m-cell-ref { font-weight: var(--fw-semibold); color: var(--brand-text); }
.m-cell-sub { font-size: var(--text-sm); color: var(--text-muted); margin-top: 2px; }

/* —— Row actions ——————————————————————————————————————————————————————————
   RowActions::Component: lays a row's actions (links, buttons, a trailing
   delete) out on one line with a consistent gap and vertical alignment. */
.m-row-actions { display: inline-flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; }
/* A button_to (Button::Component in method mode, DeleteButton::Component) renders a
   <form>, block-level by default — left alone it drops onto its own line below the
   inline links instead of sitting beside them. */
.m-row-actions form { display: inline-flex; margin: 0; }

/* —— Dropdown menu ————————————————————————————————————————————————————————
   <div class="m-menu" data-controller="dropdown">
     <button data-action="dropdown#toggle">…</button>
     <div class="m-menu__list" data-dropdown-target="menu" hidden>…</div></div> */
.m-menu { position: relative; display: inline-block; }
.m-menu--block { display: block; }
.m-menu__list { position: absolute; top: calc(100% + 6px); left: 0; min-width: 200px; padding: var(--space-2); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md); box-shadow: var(--shadow-md); z-index: 40; }
/* Open upward — for triggers pinned to the bottom of a column (e.g. the
   sidebar account menu). */
.m-menu__list--up { top: auto; bottom: calc(100% + 6px); }
.m-menu__list[hidden] { display: none; }
.m-menu__list form { margin: 0; }
.m-menu__item { display: flex; align-items: center; gap: var(--space-3); width: 100%; padding: 9px 11px; font: inherit; font-size: var(--text-base); color: var(--text-default); background: none; border: none; border-radius: var(--radius-sm); text-align: left; cursor: pointer; }
.m-menu__item svg { width: 17px; height: 17px; color: var(--text-muted); }
.m-menu__item:hover { background: var(--row-hover); }
.m-menu__item--danger { color: var(--danger-text); }
.m-menu__item--danger svg { color: var(--danger-text); }
.m-menu__sep { height: 1px; margin: var(--space-2) 0; background: var(--border); }

/* —— Modal ————————————————————————————————————————————————————————————————
   The controller goes on a wrapper, never on `.m-modal` itself: the dialog starts
   `hidden`, so a trigger inside it could never be clicked to open it. This example
   previously showed the controller on the dialog AND a `data-modal-open-value`
   the controller does not declare — two things that did not exist.

   <div data-controller="modal">
     <button data-action="modal#open"> … </button>
     <div class="m-modal" data-modal-target="dialog" hidden>
       <div class="m-modal__backdrop" data-action="click->modal#close"></div>
       <div class="m-modal__dialog"> … </div>
     </div>
   </div>                                                                      */
.m-modal { position: fixed; inset: 0; z-index: 60; display: grid; place-items: center; padding: var(--space-5); }
.m-modal[hidden] { display: none; }
.m-modal__backdrop { position: absolute; inset: 0; background: rgba(15, 22, 18, .5); backdrop-filter: blur(1px); }
.m-modal__dialog { position: relative; width: 100%; max-width: 480px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); overflow: hidden; }
.m-modal__head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); padding: var(--space-5) var(--space-5) var(--space-3); }
.m-modal__title { font-size: var(--text-lg); font-weight: var(--fw-bold); color: var(--text-strong); }
.m-modal__body { padding: 0 var(--space-5) var(--space-5); color: var(--text-default); }
.m-modal__foot { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-3); padding: var(--space-4) var(--space-5); border-top: 1px solid var(--border); background: var(--surface-2); }
.m-modal__close { display: grid; place-items: center; width: 32px; height: 32px; border: none; background: none; color: var(--text-muted); border-radius: var(--radius-sm); cursor: pointer; }
.m-modal__close:hover { background: var(--row-hover); color: var(--text-strong); }

/* —— Alert / banner ———————————————————————————————————————————————————————— */
/* Stack of flash alerts above the page content (layouts/application). */
.m-flash { display: grid; gap: var(--space-3); margin-bottom: var(--space-5); }
.m-alert { display: flex; align-items: flex-start; gap: var(--space-3); padding: var(--space-4); border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); }
.m-alert svg { width: 19px; height: 19px; flex-shrink: 0; margin-top: 1px; }
.m-alert__title { font-weight: var(--fw-semibold); color: var(--text-strong); }
.m-alert__body { font-size: var(--text-sm); color: var(--text-muted); margin-top: 2px; }
.m-alert--info    { background: var(--info-soft);    border-color: color-mix(in srgb, var(--info) 30%, transparent); }
.m-alert--info svg { color: var(--info); }
.m-alert--success { background: var(--success-soft); border-color: color-mix(in srgb, var(--success) 30%, transparent); }
.m-alert--success svg { color: var(--success); }
.m-alert--warning { background: var(--warning-soft); border-color: color-mix(in srgb, var(--warning) 30%, transparent); }
.m-alert--warning svg { color: var(--warning); }
.m-alert--danger  { background: var(--danger-soft);  border-color: color-mix(in srgb, var(--danger) 30%, transparent); }
.m-alert--danger svg { color: var(--danger-text); }

/* —— Pagination ———————————————————————————————————————————————————————————— */
.m-pager { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; }
.m-pager__info { font-size: var(--text-sm); color: var(--text-muted); }
.m-pager__btns { display: flex; gap: var(--space-1); }
/* Same 40px target as .m-btn--sm, by the same overlay and for the same reason: the
   pager sits on every index, and a rule honoured on row actions but not on the
   control right below them is a rule nobody believes. `.m-pager__btns` uses
   --space-1 horizontally, and these never stack vertically, so the overhang has
   nothing to steal from. */
.m-pager__btn { position: relative; min-width: 36px; height: 36px; padding: 0 10px; display: inline-flex; align-items: center; justify-content: center; font: inherit; font-size: var(--text-sm); font-weight: var(--fw-semibold); color: var(--text-muted); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); text-decoration: none; cursor: pointer; }
.m-pager__btn::after { content: ""; position: absolute; left: 50%; top: 50%; width: max(100%, 40px); height: 40px; transform: translate(-50%, -50%); }
.m-pager__btn:not(.is-active):not([aria-disabled="true"]):hover { border-color: var(--text-muted); color: var(--text-default); }
.m-pager__btn.is-active { background: var(--brand); color: var(--brand-on); border-color: var(--brand); cursor: default; }
.m-pager__btn:disabled, .m-pager__btn[aria-disabled="true"] { opacity: .45; cursor: not-allowed; pointer-events: none; }
.m-pager__gap { min-width: 20px; display: inline-flex; align-items: center; justify-content: center; color: var(--text-muted); }

/* —— Tooltip (CSS-only) ————————————————————————————————————————————————————
   <span class="m-tip" data-tip="Texte">…</span> */
.m-tip { position: relative; }
.m-tip::after { content: attr(data-tip); position: absolute; bottom: calc(100% + 7px); left: 50%; transform: translateX(-50%); padding: 6px 9px; font-size: var(--text-xs); font-weight: var(--fw-medium); white-space: nowrap; color: var(--on-solid); background: var(--tooltip-bg); border-radius: var(--radius-sm); opacity: 0; pointer-events: none; transition: opacity .12s ease; }
.m-tip:hover::after { opacity: 1; }

/* —— Theming variants (set on <html>) ————————————————————————————————————————
   data-primary="neutre" → neutral (dark/ink) primary button instead of brand.
   data-density="compact" → tighter controls & table rows. */
[data-primary="neutre"] .m-btn--primary { background: var(--primary-neutral); color: var(--primary-neutral-on); }
/* --primary-neutral and --primary-neutral-on are inverted for the dark theme in tokens.css. */

[data-density="compact"] .m-btn { height: 38px; }
[data-density="compact"] .m-input,
[data-density="compact"] .m-select,
[data-density="compact"] .m-field { height: 38px; }
[data-density="compact"] .m-table th,
[data-density="compact"] .m-table td { padding-top: 10px; padding-bottom: 10px; }
[data-density="compact"] .m-stat { padding: var(--space-3) var(--space-4); }
[data-density="compact"] .m-stat__value { font-size: var(--text-2xl); margin-top: var(--space-2); }
[data-density="compact"] .m-toolbar { padding: var(--space-3); }
