/* ==========================================================================
   mzyan — ui.css — the front layer, ATTEMPT 22 (2026-09-04, 22:3x; the home decided by the science, the editor from attempt 21)

   Written to _review-docs/SHIP/RESEARCH-2026-09-04-SCIENCE.md and to the owner's rejected
   list of 16:0x (sessions/2026-09-04.md). Everything the 19 earlier attempts shared is
   absent by construction:
     - no white page: a tinted canvas with two soft colour blooms;
     - no 1px hairlines or bordered white rectangles: surfaces of different sizes, no
       borders, layered shadows (figure/ground by surface — Gestalt, G2);
     - no bar of menu words on the page edge: one floating glass pill, top on desktop,
       docked at the bottom on a phone (thumb zone, C2);
     - no small-grey-everything: a display face (Bricolage Grotesque) for the number and
       the titles at real sizes; Inter for text. Four sizes on a screen (A2: low complexity);
     - colour where it carries meaning AND on the one hero surface (A2: moderate
       colourfulness), status colours unchanged.
   Every class is `ui-`; the shared components (toasts, guide hint, charts) keep their names.
   ========================================================================== */

/* ---- 0. Fonts ------------------------------------------------------------- */
@font-face { font-family: 'Inter'; src: url('/assets/fonts/InterVariable.woff2') format('woff2-variations'); font-weight: 100 900; font-style: normal; font-display: swap; }
@font-face { font-family: 'Inter'; src: url('/assets/fonts/InterVariable-Italic.woff2') format('woff2-variations'); font-weight: 100 900; font-style: italic; font-display: swap; }
@font-face { font-family: 'Bricolage'; src: url('/assets/fonts/BricolageGrotesque-latin.woff2') format('woff2'); font-weight: 200 800; font-display: swap; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'Bricolage'; src: url('/assets/fonts/BricolageGrotesque-latin-ext.woff2') format('woff2'); font-weight: 200 800; font-display: swap; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; }

/* ---- 1. Tokens ------------------------------------------------------------ */
:root {
  /* Canvas and ink. Ink on the canvas 13.9:1; ink-2 6.4:1; ink-3 4.6:1. */
  --ui-canvas: #f3f1fa;
  --ui-surface: #ffffff;
  --ui-surface-2: #f7f5fc;
  --ui-ink: #17142b;
  --ui-ink-2: #55516b;
  --ui-ink-3: #6f6b86;

  /* The accent (his): violet. Gradient partner: a deeper indigo and a rose. */
  --ui-accent: #4c2fd0;
  --ui-accent-2: #6a4dff;
  --ui-accent-deep: #3a21b0;
  --ui-rose: #f0568c;
  --ui-accent-soft: #ece8fb;
  --ui-on-accent: #ffffff;
  --ui-hero-grad: linear-gradient(135deg, #4c2fd0 0%, #6a4dff 62%, #9a6cff 100%);

  /* Status. Each ≥ 4.5:1 on white and on its soft ground. */
  --ui-bad: #b42318;  --ui-bad-soft: #fdecea;
  --ui-good: #067647; --ui-good-soft: #e6f6ee;
  --ui-warn: #9a5b00; --ui-warn-soft: #fdf3e2;

  /* Charts (the components read these names). */
  --accent: var(--ui-accent);
  --line: #cbc3e0;
  --ui-stroke: #8d84ae;
  --seq-1: #cbbff5; --seq-2: #a390eb; --seq-3: #7a5be0; --seq-4: #4c2fd0;
  --cat-1: #4c2fd0; --cat-2: #0a6b41; --cat-3: #825200; --cat-4: #a81e64; --cat-5: #0f6e7a;
  --petrol-200: var(--seq-1); --petrol-300: var(--seq-2); --petrol-400: var(--seq-3); --petrol-600: var(--seq-4);
  --slate-300: #cdc8e0;
  --chart-bar: var(--ui-accent);

  /* Type. Two families and a scale of EIGHT steps.
   *
   * It was four — 13 / 16 / 20 and a 56 px hero — and that is what «the whole design is
   * simplistic» measured as (owner, 2026-09-09): twelve screens measured, every one of them
   * drawing on the same three notes with nothing at all between 20 px and 56 px. A page
   * title, a card title and a section title were the same size as each other, so nothing on
   * a screen was allowed to be more important than anything else.
   *
   * The fix is a scale, not a bigger font. RESEARCH-2026-09-04-SCIENCE §A2 (Reinecke 2013)
   * is an inverted U on BOTH axes: appeal peaks at LOW visual complexity and MODERATE
   * colourfulness. The product hit the low-complexity half and stopped there. Steps ~1.2
   * apart fill 20 -> 56 with 24 and 30, which is where hierarchy lives, and they add no
   * regions, no controls and no alignment axes — complexity is untouched, which is the
   * owner's «sure keep the layout».
   *
   * micro  11  table heads, eyebrows, legends — uppercase, tracked
   * small  14  metadata, hints, the second line of a row (13 until 2026-09-24: 13 px was 71-94 %
   *            of the characters on whole screens — personas FACT-ENGAGEMENT m4 / PAIE-ENGAGEMENT 13,
   *            owner rule A4. Same step, same count of sizes, no prose under 14.)
   * body   16  everything by default
   * lead   17  the ONE thing a list row is scanned for
   * title  20  card and section titles
   * head   24  the title of a screen
   * disp   30  a figure or a headline that has to carry a whole panel
   * hero   40-56 the single number a screen exists for
   */
  --ui-font: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --ui-display: 'Bricolage', 'Inter', system-ui, sans-serif;
  --ui-fs-hero: clamp(2.5rem, 5vw, 3.5rem);
  --ui-fs-display: 1.875rem;
  --ui-fs-head: 1.5rem;
  --ui-fs-title: 1.25rem;
  --ui-fs-lead: 1.0625rem;
  --ui-fs-body: 1rem;
  --ui-fs-small: 0.875rem;
  --ui-fs-micro: 0.6875rem;
  --ui-lh: 1.5;

  /* Space, radius, depth. */
  --ui-s1: 4px;  --ui-s2: 8px;  --ui-s3: 12px; --ui-s4: 16px;
  --ui-s5: 24px; --ui-s6: 32px; --ui-s7: 48px; --ui-s8: 64px;
  --ui-r: 22px;        /* surfaces */
  --ui-r-sm: 12px;     /* controls */
  --ui-r-pill: 999px;
  --ui-shadow: 0 1px 2px rgba(23, 20, 43, .04), 0 8px 24px -12px rgba(23, 20, 43, .16);
  --ui-shadow-lift: 0 2px 4px rgba(23, 20, 43, .05), 0 18px 40px -16px rgba(23, 20, 43, .24);
  --ui-shadow-pill: 0 1px 2px rgba(23, 20, 43, .06), 0 12px 32px -12px rgba(76, 47, 208, .28);
  --ui-tap: 44px;
  /* THE THUMB FLOOR. RESEARCH-2026-09-04-SCIENCE §C2 (Parhi/Karlson/Bederson 2006, NN/g):
     a thumb target on a phone must be >= 1 cm, and «a 44 px CSS minimum is NOT 1 cm on every
     phone». The arithmetic for the reference phone this app is measured on: a 6.1" screen is
     390 CSS px across 64.6 mm (1170 device px at 460 ppi), so 1 CSS px = 0.1656 mm and
     1 cm = 60.4 CSS px. 44 px is 7.3 mm; the tab bar's 52 px was 8.6 mm.
     --ui-tap stays the floor for INCIDENTAL controls (WCAG 2.5.5 AAA). --ui-thumb is for the
     controls that live in the thumb zone and are hit on every screen: the bottom tab bar and
     the primary action of the docked action bar and of a sheet. */
  --ui-thumb: 60px;
  --ui-content: 1180px;
  --ui-pill-h: 60px;
}

/* ---- 2. Reset ------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body { margin: 0; font: 400 var(--ui-fs-body)/var(--ui-lh) var(--ui-font); color: var(--ui-ink); background: var(--ui-canvas); -webkit-font-smoothing: antialiased; font-feature-settings: 'cv11', 'ss01'; min-height: 100dvh; }
h1, h2, h3, p, dl, dd, ul, ol, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
input, select, textarea { font: inherit; color: inherit; }
img, svg { display: block; max-width: 100%; }
[x-cloak] { display: none !important; }
:focus-visible { outline: 3px solid var(--ui-accent); outline-offset: 2px; border-radius: 6px; }
::selection { background: var(--ui-accent-soft); }
.ui-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.skip-link { position: absolute; left: -999px; top: 8px; z-index: 100; background: var(--ui-ink); color: #fff; padding: 12px 16px; min-height: var(--ui-tap); border-radius: 8px; }
.skip-link:focus { left: 8px; }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; } }

/* ---- 3. The canvas: two soft blooms behind everything ---------------------- */
.ui-shell { position: relative; min-height: 100dvh; isolation: isolate; }
.ui-shell::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 520px; z-index: -1; pointer-events: none;
  background:
    radial-gradient(640px 360px at 6% 4%, rgba(106, 77, 255, .16), transparent 60%),
    radial-gradient(520px 320px at 94% 2%, rgba(240, 86, 140, .12), transparent 60%);
}

/* ---- 4. Type ---------------------------------------------------------------- */
.ui-hero-text { font: 600 var(--ui-fs-hero)/1.05 var(--ui-display); letter-spacing: -0.02em; }
.ui-title { font: 600 var(--ui-fs-title)/1.2 var(--ui-display); letter-spacing: -0.015em; }
/* The title of a SCREEN, one step above the title of a card on it. `.ui-title` is shared with
   every sheet heading, so this is opted into rather than applied to all of them. */
.ui-title--page { font-size: var(--ui-fs-head); line-height: 1.15; letter-spacing: -0.02em; }
.ui-label { font-size: var(--ui-fs-small); font-weight: 600; letter-spacing: .02em; text-transform: uppercase; color: var(--ui-ink-3); }
.ui-small { font-size: var(--ui-fs-small); color: var(--ui-ink-2); }
.ui-muted { color: var(--ui-ink-2); }
.ui-strong { font-weight: 600; }
.ui-measure { max-width: 34rem; }
.ui-num { font-variant-numeric: tabular-nums lining-nums; }
.ui-lead { margin-top: var(--ui-s2); }
.ui-money { font-variant-numeric: tabular-nums; white-space: nowrap; }
.ui-money__minor { font-size: var(--ui-fs-small); font-weight: 500; opacity: .75; }
.ui-money__unit { font-size: var(--ui-fs-small); font-weight: 500; opacity: .75; margin-inline-start: .15em; }
.ui-money--hero .ui-money__minor, .ui-money--hero .ui-money__unit { font-size: var(--ui-fs-title); }
.ui-card--hero a { display: inline-block; min-height: var(--ui-tap); }
.ui-money--hero { display: inline-block; font-family: var(--ui-display); font-weight: 700; font-size: var(--ui-fs-hero); line-height: 1; letter-spacing: -0.03em; }
.ui-money--bad { color: var(--ui-bad); }
.ui-money--good { color: var(--ui-good); }
.ui-phone-only { display: none; }
@media (max-width: 899px) { .ui-phone-only { display: initial; } .ui-desk-only { display: none !important; } .ui-phone-hidden { display: none !important; } }
@media (min-width: 900px) { .ui-phone-only { display: none !important; } }

/* ---- 5. Navigation: the sidebar on a desk; the top row + tab bar on a phone -------------
   2026-09-08, the expert-comptable's point 3: «the menu would be better as a sidebar, all
   similar SaaS have the same layout». Prototypicality (Tuch et al. 2012): an interface that
   matches its category's prototype is judged more usable and more beautiful before any click,
   and the prototype for SME finance software is a LEFT SIDEBAR — QuickBooks, Xero, Zoho Books,
   Pennylane, Sage. D2 keeps its words and count (six destinations ≤ 7); only their placement
   changed on a desk. The sidebar is the same object as the cards: one tall rounded surface,
   floating with the pill's 16 px inset, glass over the canvas blooms, the pill's shadow.
   Under 900 px none of this renders: the phone keeps the top row and the tab bar (D7).
   Width: 248 px at every desk width, no icon rail. Measured 2026-09-08 with the sidebar in
   place: the editor's two panes still fit at 1280 (form 508 px, preview 436 px) and at 1100
   (411 / 353 px, the preview's «Agrandir» gives 100 %); a rail at 900–1199 px would trade the
   labels and the unfolded sections for ~176 px in a band no desk screen occupies (recognition
   over recall, Nielsen #6). Content width at 1440 is 1128 px (was 1180). */
:root { --ui-side-w: 248px; --ui-side-inset: 16px; }
.ui-side {
  position: fixed; inset-block: var(--ui-side-inset); inset-inline-start: var(--ui-side-inset); width: var(--ui-side-w); z-index: 40;
  display: flex; flex-direction: column; gap: var(--ui-s2); padding: var(--ui-s4) var(--ui-s3) var(--ui-s3);
  border-radius: var(--ui-r); background: rgba(255, 255, 255, .92);
  -webkit-backdrop-filter: blur(18px) saturate(1.2); backdrop-filter: blur(18px) saturate(1.2);
  box-shadow: var(--ui-shadow-pill);
}
.ui-brand { font: 700 1.25rem/1 var(--ui-display); letter-spacing: -0.03em; margin-inline-end: var(--ui-s3); }
.ui-brand__dot { color: var(--ui-accent); }
.ui-side__brand { padding: var(--ui-s1) var(--ui-s3) var(--ui-s3); margin: 0; }
.ui-side__create { width: 100%; justify-content: center; margin-bottom: var(--ui-s2); box-shadow: 0 6px 16px -6px rgba(76, 47, 208, .6); }
.ui-side__create-slot { height: var(--ui-tap); margin-bottom: var(--ui-s2); }
/* The list of destinations scrolls when it is taller than the window: say so at the edge
   rather than clipping «Back-office» in silence (UX review 2026-09-08, M4). */
.ui-side__scroll { scrollbar-width: thin; }
.ui-side__scroll::-webkit-scrollbar { width: 6px; }
.ui-side__scroll::-webkit-scrollbar-thumb { background: rgba(23, 20, 43, .18); border-radius: 999px; }
/* The middle scrolls on its own when the list is taller than the window; brand, create and
   the account stay put. */
.ui-side__scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; display: flex; flex-direction: column; gap: var(--ui-s3); margin-inline: calc(-1 * var(--ui-s1)); padding-inline: var(--ui-s1); }
.ui-nav { display: flex; flex-direction: column; gap: 2px; }
.ui-nav__item { display: flex; align-items: center; gap: var(--ui-s3); width: 100%; min-height: var(--ui-tap); padding: 0 var(--ui-s3); border-radius: 12px; font-weight: 500; color: var(--ui-ink-2); text-align: start; transition: background .15s, color .15s; }
.ui-nav__item .icon { flex: none; }
.ui-nav__item:hover { background: rgba(23, 20, 43, .05); color: var(--ui-ink); }
.ui-nav__item[aria-current="page"] { background: var(--ui-ink); color: #fff; }
.ui-nav__toggle[aria-current="true"] { color: var(--ui-ink); }
.ui-nav__chevron { margin-inline-start: auto; color: var(--ui-ink-3); transition: transform .15s; }
/* The sections of the current destination, one indent in, on a hairline so they read as its
   children (Gestalt: common region) — 44 px rows, the same target as everything else. */
.ui-nav__sub { display: flex; flex-direction: column; gap: 1px; margin: 2px 0 var(--ui-s2) 22px; padding-inline-start: var(--ui-s3); border-inline-start: 2px solid var(--line); }
.ui-nav__subitem { display: flex; align-items: center; min-height: var(--ui-tap); padding: 0 var(--ui-s3); border-radius: 10px; font-size: var(--ui-fs-small); font-weight: 500; color: var(--ui-ink-2); transition: background .15s, color .15s; }
.ui-nav__subitem:hover { background: rgba(23, 20, 43, .05); color: var(--ui-ink); }
.ui-nav__subitem[aria-current="page"] { background: var(--ui-accent-soft); color: var(--ui-accent-deep); font-weight: 600; }
.ui-side__group { display: flex; flex-direction: column; gap: 2px; }
.ui-side__foot { margin-top: auto; padding-top: var(--ui-s2); }
.ui-side__account { display: flex; align-items: center; gap: var(--ui-s3); width: 100%; min-height: 56px; padding: var(--ui-s2); border-radius: 14px; text-align: start; transition: background .15s; }
.ui-side__account:hover { background: rgba(23, 20, 43, .05); }
.ui-side__account .ui-avatar { width: 36px; height: 36px; flex: none; }
.ui-side__who { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.ui-side__who > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-avatar { width: var(--ui-tap); height: var(--ui-tap); border-radius: 50%; background: var(--ui-accent-soft); color: var(--ui-accent-deep); font-weight: 700; display: inline-flex; align-items: center; justify-content: center; transition: transform .15s; }
.ui-avatar:hover { transform: scale(1.04); }
.ui-menu-anchor { position: relative; }
/* THE MENU HAS TO FIT THE SCREEN, AND IT DID NOT.
   Measured 390x844, 2026-09-09: this menu computed height 2098 px with 31 rows below the fold
   and `overflow-y: visible`, anchored inside a `position: sticky` header — so it did not move
   with the page, nothing scrolled it, and its tail is «Mon compte / Aide de cette page /
   Se déconnecter». On an account with several organisations in the switcher, YOU COULD NOT
   LOG OUT ON A PHONE. Its own sibling `.ui-menu--up` has carried the cap since it was
   written; the plain one never got it. Same rule, same reason, and the fixed tail is what
   the missing cap was cutting off. */
.ui-menu {
  position: absolute; inset-inline-end: 0; top: calc(100% + 10px); z-index: 60;
  min-width: 17rem; padding: var(--ui-s2); background: var(--ui-surface);
  border-radius: 18px; box-shadow: var(--ui-shadow-lift);
  max-height: calc(100dvh - 96px); overflow-y: auto; overscroll-behavior: contain;
}
/* From the foot of the sidebar the menu opens UPWARD, as wide as the sidebar, and never
   taller than the window above the account block. */
.ui-menu--up { top: auto; bottom: calc(100% + 8px); inset-inline: 0; min-width: 0; max-height: calc(100dvh - 2 * var(--ui-side-inset) - 96px); overflow-y: auto; }
/* On a phone the cap above ignored the tab bar: the menu ran to 862 px on an 844 px screen and its
   tail sat UNDER the bar (same z-index, the bar later in the page), so tapping «Se déconnecter» or
   «Aide de cette page» opened a new invoice (R1-FACT-SCREENS, 2026-09-24, real taps). The cap now
   stops above the bar, and the open menu's header is lifted over it for the rest. */
@media (max-width: 899px) {
  .ui-menu { max-height: calc(100dvh - 96px - var(--ui-thumb) - 12px - max(var(--ui-s3), env(safe-area-inset-bottom))); }
  .ui-top:has(.ui-menu-anchor > .ui-menu:not([style*="display: none"])) { z-index: 41; }
}
.ui-menu__label { padding: var(--ui-s2) var(--ui-s3); }
.ui-menu__sep { height: 1px; background: var(--line); margin: var(--ui-s2) var(--ui-s2); }
.ui-menu__item { display: flex; align-items: center; gap: var(--ui-s3); width: 100%; min-height: var(--ui-tap); padding: 0 var(--ui-s3); border-radius: 12px; text-align: start; color: var(--ui-ink); }
.ui-menu__item:hover { background: var(--ui-surface-2); }
.ui-menu__item[aria-current="page"] { background: var(--ui-accent-soft); color: var(--ui-accent-deep); }
.ui-menu__item--danger { color: var(--ui-bad); }
.ui-impersonating { background: var(--ui-warn-soft); color: var(--ui-warn); padding: var(--ui-s2) var(--ui-s4); display: flex; gap: var(--ui-s3); align-items: center; font-size: var(--ui-fs-small); }
/* A desk has no top row any more: the offsets that sat things under the pill (sticky preview
   panes, the toast region) start from the window edge. The phone never read this token. */
@media (min-width: 900px) {
  :root { --ui-pill-h: 0px; }
  .ui-top { display: none; }
  .ui-impersonating { margin: var(--ui-side-inset) var(--ui-side-inset) 0 calc(var(--ui-side-w) + 2 * var(--ui-side-inset)); border-radius: 14px; }
}
/* < 900 px: the top row — brand + account — exactly as it was before the sidebar. */
@media (max-width: 899px) {
  .ui-side { display: none; }
  .ui-top { position: sticky; top: 0; z-index: 40; padding: var(--ui-s3) var(--ui-s3) 0; pointer-events: none; }
  .ui-top__in {
    pointer-events: auto; height: 52px;
    display: flex; align-items: center; gap: var(--ui-s2); padding: 6px 6px 6px var(--ui-s4);
    border-radius: var(--ui-r-pill); background: rgba(255, 255, 255, .97);
    -webkit-backdrop-filter: blur(18px) saturate(1.2); backdrop-filter: blur(18px) saturate(1.2);
    box-shadow: var(--ui-shadow-pill);
  }
  .ui-top__end { display: flex; align-items: center; gap: var(--ui-s2); margin-inline-start: auto; }
}

/* Phone: the same pill, docked at the bottom (the thumb). */
.ui-tabbar { display: none; }
@media (max-width: 899px) {
  .ui-tabbar {
    position: fixed; inset-inline: var(--ui-s3); bottom: max(var(--ui-s3), env(safe-area-inset-bottom)); z-index: 40;
    display: grid; grid-template-columns: repeat(5, 1fr); align-items: center; height: calc(var(--ui-thumb) + 12px); padding: 6px;
    border-radius: var(--ui-r-pill); background: rgba(255, 255, 255, .97);
    -webkit-backdrop-filter: blur(18px) saturate(1.4); backdrop-filter: blur(18px) saturate(1.4);
    box-shadow: var(--ui-shadow-pill);
  }
  .ui-tabbar__item { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; height: var(--ui-thumb); border-radius: var(--ui-r-pill); font-size: var(--ui-fs-small); font-weight: 500; color: var(--ui-ink-2); }
  .ui-tabbar__item[aria-current="page"] { color: var(--ui-accent-deep); background: var(--ui-accent-soft); }
  .ui-tabbar__action { display: flex; align-items: center; justify-content: center; height: var(--ui-thumb); }
  .ui-tabbar__action span { width: var(--ui-thumb); height: var(--ui-thumb); border-radius: 50%; background: var(--ui-hero-grad); color: #fff; display: inline-flex; align-items: center; justify-content: center; box-shadow: 0 8px 20px -6px rgba(76, 47, 208, .6); }
}

/* ---- 6. Page ------------------------------------------------------------------ */
.ui-main { padding: var(--ui-s5) var(--ui-s4) var(--ui-s8); }
/* Beside the sidebar on a desk: the content starts one gap to its right and keeps its
   max-width, so nothing is ever under it. */
@media (min-width: 900px) { .ui-main { padding: var(--ui-s5) var(--ui-s5) var(--ui-s8) calc(var(--ui-side-w) + var(--ui-side-inset) + var(--ui-s5)); } }
.ui-page { max-width: var(--ui-content); margin: 0 auto; }
.ui-page--wide { max-width: 1440px; }
.ui-page__head { display: flex; justify-content: space-between; align-items: flex-end; gap: var(--ui-s4); flex-wrap: wrap; margin-bottom: var(--ui-s5); }
.ui-back { display: inline-flex; align-items: center; gap: var(--ui-s1); min-height: var(--ui-tap); color: var(--ui-ink-2); font-weight: 500; margin-bottom: var(--ui-s2); }
.ui-back:hover { color: var(--ui-ink); }
.ui-row { display: flex; gap: var(--ui-s3); align-items: center; flex-wrap: wrap; }
.ui-stack > * + * { margin-top: var(--ui-s4); }
.ui-section { margin-top: var(--ui-s6); }
.ui-section__head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--ui-s3); margin-bottom: var(--ui-s3); }
.ui-section__title { font: 600 1.25rem/1.25 var(--ui-display); letter-spacing: -0.015em; }
@media (max-width: 899px) { .ui-main { padding: var(--ui-s4) var(--ui-s3) 128px; } }

/* ---- 7. Surfaces and the bento -------------------------------------------------- */
.ui-card { background: var(--ui-surface); border-radius: var(--ui-r); box-shadow: var(--ui-shadow); padding: var(--ui-s5); }
.ui-bento > .ui-card { animation: ui-rise .36s cubic-bezier(.2,.7,.2,1) both; }
.ui-card--flat { box-shadow: none; background: var(--ui-surface-2); }
.ui-card--hero { background: var(--ui-hero-grad); color: #fff; box-shadow: 0 24px 48px -20px rgba(76, 47, 208, .55); position: relative; overflow: hidden; }
.ui-card--hero::after { content: ''; position: absolute; width: 360px; height: 360px; right: -120px; bottom: -200px; border-radius: 50%; background: radial-gradient(closest-side, rgba(255, 255, 255, .22), transparent); pointer-events: none; }
.ui-card--hero .ui-label { color: rgba(255, 255, 255, .78); }
.ui-card--hero .ui-small, .ui-card--hero .ui-muted { color: rgba(255, 255, 255, .84); }
.ui-card--hero a:hover { text-decoration: underline; text-underline-offset: 4px; }
.ui-card__head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--ui-s3); margin-bottom: var(--ui-s4); flex-wrap: wrap; }
.ui-card__title { font: 600 1.25rem/1.25 var(--ui-display); letter-spacing: -0.015em; }
.ui-card__more { font-size: var(--ui-fs-small); font-weight: 600; color: var(--ui-accent); min-height: var(--ui-tap); display: inline-flex; align-items: center; }
.ui-bento { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--ui-s4); align-items: stretch; }
.ui-board { display: grid; gap: var(--ui-s6); }
@media (min-width: 900px) { .ui-board { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--ui-s7); } }
.ui-bento > * { grid-column: span 12; min-width: 0; }
@media (min-width: 900px) {
  .ui-b7 { grid-column: span 7; } .ui-b5 { grid-column: span 5; } .ui-b8 { grid-column: span 8; }
  .ui-b4 { grid-column: span 4; } .ui-b6 { grid-column: span 6; } .ui-b12 { grid-column: span 12; }
}
@media (min-width: 900px) and (max-width: 1099px) { .ui-b4 { grid-column: span 6; } }
.ui-bento > :nth-child(2) { animation-delay: .05s; } .ui-bento > :nth-child(3) { animation-delay: .1s; }
.ui-bento > :nth-child(4) { animation-delay: .15s; } .ui-bento > :nth-child(5) { animation-delay: .2s; }
.ui-bento > :nth-child(6) { animation-delay: .25s; } .ui-bento > :nth-child(7) { animation-delay: .3s; } .ui-bento > :nth-child(8) { animation-delay: .35s; }
@keyframes ui-rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* Home: the hero surface. */
.ui-hero { display: flex; flex-direction: column; justify-content: center; gap: var(--ui-s4); }
.ui-hero__label { }
.ui-hero__note { margin-top: var(--ui-s3); font-size: var(--ui-fs-body); }
.ui-hero__hi { color: rgba(255, 255, 255, .85); }
.ui-hero__foot { display: flex; gap: var(--ui-s3); flex-wrap: wrap; }
/* `.ui-chip` IS FOR A DARK BAND ONLY — white ink on a 16 %-white fill. On a light card it
   paints white on white and the control disappears (two of them did, in Paie, 2026-09-15).
   On a card the pill you want is `.ui-filter` or `.ui-badge`, not this one. */
.ui-chip { display: inline-flex; align-items: center; gap: var(--ui-s2); min-height: var(--ui-tap); padding: 0 14px; border-radius: var(--ui-r-pill); background: rgba(255, 255, 255, .16); color: #fff; font-weight: 600; font-size: var(--ui-fs-small); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); }
.ui-chip:hover { background: rgba(255, 255, 255, .26); text-decoration: none !important; }
.ui-chip--bad { background: rgba(255, 255, 255, .92); color: var(--ui-ink); }
.ui-chip--bad::before { content: ''; flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--ui-bad); }
.ui-chip--bad:hover { background: #fff; }
.ui-hero__facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--ui-s3); margin-top: var(--ui-s2); }
.ui-hero__facts > div { padding: var(--ui-s3) var(--ui-s4); border-radius: 16px; background: rgba(255, 255, 255, .14); }
.ui-hero__facts dt { font-size: var(--ui-fs-small); color: rgba(255, 255, 255, .8); font-weight: 500; }
.ui-hero__facts dd { font: 700 1.25rem/1.15 var(--ui-display); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; margin-top: 2px; }
.ui-hero__facts-hint { font: 500 var(--ui-fs-small)/1 var(--ui-font); color: rgba(255, 255, 255, .8); margin-inline-start: 4px; }

/* First run: the endowed steps. */
.ui-steps { margin-top: var(--ui-s5); }
.ui-steps__bar { height: 8px; border-radius: 999px; background: rgba(255, 255, 255, .25); overflow: hidden; }
.ui-steps__fill { height: 100%; background: #fff; border-radius: 999px; transition: width .4s; }
.ui-steps__list { margin-top: var(--ui-s3); display: grid; gap: var(--ui-s1); }
.ui-step { display: flex; align-items: flex-start; gap: var(--ui-s3); min-height: var(--ui-tap); padding: var(--ui-s2) var(--ui-s3); border-radius: 14px; }
a.ui-step:hover { background: var(--ui-surface-2); }
.ui-step__mark { flex: none; width: 24px; height: 24px; margin-top: 1px; border-radius: 50%; border: 2px solid var(--slate-300); display: inline-flex; align-items: center; justify-content: center; color: transparent; }
.ui-step[data-done="1"] .ui-step__mark { background: var(--ui-good); border-color: var(--ui-good); color: #fff; }
.ui-step[data-done="1"] .ui-step__title { color: var(--ui-ink-3); text-decoration: line-through; }
.ui-step__title { font-weight: 600; }
.ui-step__text { font-size: var(--ui-fs-small); color: var(--ui-ink-2); }
.ui-step__go { margin-inline-start: auto; align-self: center; color: var(--ui-ink-3); }

/* The day: tasks as rows in a surface, no rules — spacing and a coloured mark. */
.ui-tasks { display: grid; gap: var(--ui-s1); }
.ui-task { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ui-s3); padding: var(--ui-s3) var(--ui-s3); border-radius: 16px; }
.ui-task--critical { background: var(--ui-bad-soft); }
.ui-task__mark { flex: none; width: 10px; height: 10px; border-radius: 50%; background: var(--ui-accent); }
.ui-task--critical .ui-task__mark { background: var(--ui-bad); }
.ui-task__text { display: flex; flex-direction: column; min-width: 0; flex: 1 1 11rem; }
.ui-task__title { font-weight: 600; }
.ui-task__detail { font-size: var(--ui-fs-small); color: var(--ui-ink-2); }
.ui-task .ui-btn { margin-inline-start: auto; flex: none; }
.ui-calm { display: flex; align-items: center; gap: var(--ui-s3); padding: var(--ui-s3); border-radius: 16px; background: var(--ui-good-soft); color: var(--ui-good); font-weight: 600; }

/* Facts: the month, small figures with big numbers. */
.ui-facts { display: grid; gap: var(--ui-s4); }
.ui-fact { display: flex; flex-direction: column; gap: 2px; padding: var(--ui-s3) var(--ui-s4); border-radius: 16px; background: var(--ui-surface-2); }
.ui-fact__label { font-size: var(--ui-fs-small); color: var(--ui-ink-2); font-weight: 500; }
.ui-fact__value { font: 700 1.25rem/1.15 var(--ui-display); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.ui-fact__hint { font-size: var(--ui-fs-small); color: var(--ui-ink-3); }

/* Lists: rows by spacing; the whole row is the target. */
.ui-list { display: grid; gap: var(--ui-s1); }
.ui-item { display: flex; align-items: center; justify-content: space-between; gap: var(--ui-s4); min-height: 56px; padding: var(--ui-s2) var(--ui-s3); border-radius: 14px; transition: background .15s; }
a.ui-item:hover { background: var(--ui-surface-2); }
.ui-item__primary { font-weight: 600; }
.ui-item__secondary { font-size: var(--ui-fs-small); color: var(--ui-ink-2); }
.ui-item__amount { font: 700 1rem/1 var(--ui-display); font-variant-numeric: tabular-nums; white-space: nowrap; }
.ui-badge { display: inline-flex; align-items: center; padding: 1px 8px; border-radius: 999px; font-size: var(--ui-fs-small); font-weight: 600; background: #e9e6f4; color: var(--ui-ink-2); vertical-align: middle; }
.ui-badge--good { background: var(--ui-good-soft); color: var(--ui-good); }
.ui-badge--bad { background: var(--ui-bad-soft); color: var(--ui-bad); }
.ui-badge--warn { background: var(--ui-warn-soft); color: var(--ui-warn); }
.ui-badge--accent { background: var(--ui-accent-soft); color: var(--ui-accent-deep); }

/* ---- 8. Charts (components keep their names) ----------------------------------- */
.chart { display: flex; flex-direction: column; gap: var(--ui-s3); min-width: 0; }
.chart-title { font: 600 1.25rem/1.25 var(--ui-display); letter-spacing: -0.015em; }
.chart-empty { color: var(--ui-ink-2); font-size: var(--ui-fs-small); padding: var(--ui-s4) 0; }
.chart-svg { width: 100%; height: auto; display: block; overflow: visible; }
.chart-axis, .chart-tick { font-size: 13px; fill: var(--ui-ink-3); font-family: var(--ui-font); }
.chart-datum { font-size: 13px; font-weight: 600; fill: var(--ui-ink); font-family: var(--ui-font); }
.chart-svg rect[fill="var(--bar, var(--accent))"] { transition: opacity .15s; }
.chart-svg rect:hover + rect { opacity: 1; }
.chart-table { font-size: var(--ui-fs-small); color: var(--ui-ink-2); }
.chart-table summary { cursor: pointer; min-height: var(--ui-tap); display: flex; align-items: center; }
.chart-table table { width: 100%; border-collapse: collapse; }
.chart-table td { padding: 4px 0; }
.chart-table .numeric { text-align: end; font-variant-numeric: tabular-nums; }
.chart-rows { display: grid; gap: var(--ui-s1); }
.chart-row { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(3rem, 1fr) auto; gap: var(--ui-s3); align-items: center; min-height: var(--ui-tap); border-radius: 10px; }
.chart-row--linked { cursor: pointer; }
.chart-row--linked:hover .chart-row-label { color: var(--ui-accent-deep); }
.chart-row-label { display: flex; align-items: center; gap: var(--ui-s2); font-size: var(--ui-fs-small); font-weight: 500; color: var(--ui-ink-2); min-width: 0; line-height: 1.25; overflow-wrap: anywhere; }
.chart-swatch { flex: none; width: 10px; height: 10px; border-radius: 3px; background: var(--bar, var(--ui-accent)); }
.chart-row-track { height: 12px; border-radius: 999px; background: var(--ui-surface-2); overflow: hidden; }
.chart-row-fill { display: block; height: 100%; border-radius: 999px; background: var(--bar, var(--ui-accent)); transition: inline-size .4s; }
.chart-row-fill.is-critical { background: var(--ui-bad); }
.chart-row-value { display: flex; flex-direction: column; align-items: flex-end; line-height: 1.2; font-variant-numeric: tabular-nums; }
.chart-row-amount { font-weight: 600; font-size: var(--ui-fs-small); white-space: nowrap; }
.chart-row-count { font-size: var(--ui-fs-small); color: var(--ui-ink-3); white-space: nowrap; }
.chart-row-value.is-critical .chart-row-amount { color: var(--ui-bad); }
.chart--narrow { display: none; }
@media (max-width: 899px) { .chart--wide { display: none; } .chart--narrow { display: block; } }

/* ---- 8b. Accueil, attempt 22: the number on the canvas, the day as tiles, the month as a line ---- */
.ui-home__head { padding: var(--ui-s4) 0 var(--ui-s6); max-width: 60rem; }
.ui-home__hi { color: var(--ui-ink-2); }
.ui-home__title { margin-top: var(--ui-s3); }
.ui-home__cta { margin-top: var(--ui-s5); }
.ui-home__head .ui-label { margin-top: var(--ui-s4); }
.ui-home__number { display: inline-flex; align-items: center; min-height: var(--ui-tap); margin-top: var(--ui-s2); color: var(--ui-ink); }
a.ui-home__number:hover .ui-money--hero { color: var(--ui-accent-deep); }
.ui-home__note { display: flex; align-items: center; gap: var(--ui-s2); margin-top: var(--ui-s3); color: var(--ui-ink-2); font-weight: 500; }
.ui-home__overdue { color: var(--ui-ink); text-decoration: underline; text-underline-offset: 4px; text-decoration-color: var(--ui-bad); min-height: var(--ui-tap); display: inline-flex; align-items: center; }
/* flex-shrink 0 (R1-HARNESSES-FACT 2026-09-25): beside a long line in a flex row — the TVA band's «Déclarée le … déclaration
   rectificative à déposer» at 390 — the dot was squeezed to a 3×10 px sliver against the gutter. */
.ui-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: var(--ui-accent); vertical-align: middle; flex-shrink: 0; }
.ui-dot--bad { background: var(--ui-bad); }
.ui-dot--good { background: var(--ui-good); }
.ui-home__day { margin-top: var(--ui-s2); }
.ui-home__day-title { font: 600 var(--ui-fs-title)/1.25 var(--ui-display); letter-spacing: -0.015em; margin-bottom: var(--ui-s3); }
.ui-tiles { display: grid; gap: var(--ui-s4); grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); align-items: stretch; }
.ui-tiles--first { margin-top: 0; }
.ui-tile { display: flex; flex-direction: column; gap: var(--ui-s2); padding: var(--ui-s5); }
.ui-tile--wide { grid-column: 1 / -1; }
.ui-tile--critical { background: var(--ui-bad-soft); }
.ui-tile__title { font: 600 var(--ui-fs-title)/1.25 var(--ui-display); letter-spacing: -0.015em; }
.ui-tile__title .ui-dot { margin-inline-end: 4px; position: relative; top: -2px; }
.ui-tile__detail { color: var(--ui-ink-2); font-size: var(--ui-fs-small); }
.ui-tile__action { margin-top: auto; padding-top: var(--ui-s3); }
.ui-strip { display: flex; flex-wrap: wrap; gap: var(--ui-s3) var(--ui-s7); margin-top: var(--ui-s6); padding-top: var(--ui-s4); }
.ui-strip .ui-fact { flex-direction: column; gap: 2px; padding: 0; background: none; border-radius: 0; }
.ui-figures { margin-top: var(--ui-s6); }
.ui-recent { margin-top: var(--ui-s4); }
.ui-recent__row { display: flex; align-items: center; flex-wrap: wrap; gap: var(--ui-s2); }
.ui-recent__row > .ui-item { flex: 1 1 14rem; min-width: 0; }
.ui-recent__row > .ui-btn { flex: none; margin-inline-start: auto; }
@media (max-width: 899px) {
  .ui-home__head { padding: var(--ui-s2) 0 var(--ui-s5); }
  .ui-tiles { grid-template-columns: 1fr; }
  .ui-strip { gap: var(--ui-s3) var(--ui-s5); }
}

/* ---- 9. Buttons ------------------------------------------------------------------ */
.ui-btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--ui-s2); min-height: var(--ui-tap); padding: 0 var(--ui-s5); border-radius: var(--ui-r-pill); font-weight: 600; white-space: nowrap; transition: transform .12s, background .15s, box-shadow .15s; }
.ui-btn:active { transform: translateY(1px); }
/* A BUSY PRIMARY SAYS SO.
   On a 3G connection «Émettre» sat for 1 669 ms with `disabled=false`, no spinner and no
   `wire:loading` element — its only change was `data-loading` darkening the background by the
   same amount as :hover, which with the pointer already on the button is no change at all. That
   is the exact moment a user clicks again (adversarial review 2026-09-07). Doherty: under
   400 ms it must be visibly working. */
.ui-btn[data-loading="true"], .ui-btn[wire\:loading] { position: relative; }
.ui-btn--busy { position: relative; pointer-events: none; }
.ui-btn--busy > * { visibility: hidden; }
.ui-btn--busy::after {
  content: ''; position: absolute; inset-block-start: 50%; inset-inline-start: 50%;
  inline-size: 1em; block-size: 1em; margin: -0.5em 0 0 -0.5em;
  border: 2px solid currentColor; border-block-start-color: transparent; border-radius: 50%;
  animation: ui-spin .6s linear infinite;
}
@keyframes ui-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .ui-btn--busy::after { animation-duration: 2s; } }

.ui-btn--primary { background: var(--ui-accent); color: var(--ui-on-accent); box-shadow: 0 8px 20px -8px rgba(76, 47, 208, .6); }
.ui-btn--primary:hover { background: var(--ui-accent-deep); }
.ui-btn--secondary { background: var(--ui-surface); color: var(--ui-ink); box-shadow: inset 0 0 0 1.5px var(--ui-stroke); }
.ui-btn--secondary:hover { background: var(--ui-surface-2); }
.ui-btn--ghost { color: var(--ui-ink-2); }
.ui-btn--ghost:hover { background: rgba(23, 20, 43, .06); color: var(--ui-ink); }
.ui-btn--link { color: var(--ui-accent); padding-inline: var(--ui-s2); }
.ui-btn--link:hover { text-decoration: underline; text-underline-offset: 3px; }
.ui-btn--soft { background: var(--ui-accent-soft); color: var(--ui-accent-deep); }
.ui-btn--soft:hover { background: #e2dcf9; }
.ui-btn--lg { min-height: 52px; padding: 0 var(--ui-s6); }
.ui-btn--icon { width: var(--ui-tap); padding: 0; }
.ui-btn[disabled] { opacity: .55; cursor: default; }
.ui-card--hero .ui-btn--primary { background: #fff; color: var(--ui-accent-deep); box-shadow: 0 10px 24px -10px rgba(0, 0, 0, .35); }
.ui-card--hero .ui-btn--primary:hover { background: #f3f1fa; }
.ui-linkbtn { display: inline-flex; align-items: center; color: var(--ui-accent); font-weight: 500; min-height: var(--ui-tap); padding: 0 var(--ui-s1); }
.ui-linkbtn:hover { text-decoration: underline; text-underline-offset: 3px; }
/* A LINK INSIDE A SENTENCE, NOT A BUTTON ON ITS OWN LINE (O-6, 2026-09-14).
   Six of these were written as `class="ui-linkbtn" style="padding:0;min-height:0"` — the
   inline style is there because `.ui-linkbtn`'s 44 px would push the sentence apart. The
   result measured 154x20 on the phone: the ONLY way from a late invoice to its client
   (batch D) was a 20 px target, under WCAG 2.5.8's 24 px AA floor for an inline link.
   `display: inline` (not inline-flex) means vertical padding enlarges the HIT BOX without
   changing the line box, so the sentence keeps its shape and the target becomes 32 px. */
.ui-linkbtn--inline { display: inline; min-height: 0; padding: 6px var(--ui-s1); }
/* «Pourquoi ?» — the law one tap under a plain sentence (x-ui.why, 2026-09-24). The summary is a
   44 px-tall target; the text keeps the hint's size and colour. */
.ui-why > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; min-height: var(--ui-tap); padding-inline: 0; font-size: var(--ui-fs-small); }
.ui-why > summary::-webkit-details-marker { display: none; }
.ui-why__text { color: var(--ui-ink-2); margin: 0 0 var(--ui-s2); max-width: 70ch; }
.ui-why.ui-declare__foot { margin-top: 0; }

/* ---- 10. Fields -------------------------------------------------------------------- */
.ui-field { display: block; }
.ui-field + .ui-field { margin-top: var(--ui-s4); }
.ui-field__label { display: block; font-size: var(--ui-fs-small); font-weight: 600; color: var(--ui-ink-2); margin-bottom: 6px; }
.ui-input, .ui-select, .ui-textarea { display: block; width: 100%; min-height: var(--ui-tap); padding: 10px 14px; border-radius: var(--ui-r-sm); border: 1.5px solid transparent; background: var(--ui-surface-2); color: var(--ui-ink); transition: border-color .15s, background .15s, box-shadow .15s; }
.ui-input::placeholder, .ui-textarea::placeholder { color: var(--ui-ink-3); }
.ui-input:hover, .ui-select:hover, .ui-textarea:hover { background: #f1eef9; }
/* Mouse focus keeps the quiet treatment; KEYBOARD focus gets the same 3 px ring as every other
   control. Text and money inputs — where a keyboard user spends the most time — spoke a
   different, paler language than buttons and links: a 1 px border and a lilac halo on a
   near-white card (adversarial review 2026-09-07). */
.ui-input:focus, .ui-select:focus, .ui-textarea:focus { outline: 0; background: #fff; border-color: var(--ui-accent); box-shadow: 0 0 0 4px var(--ui-accent-soft); }
.ui-input:focus-visible, .ui-select:focus-visible, .ui-textarea:focus-visible { outline: 3px solid var(--ui-accent); outline-offset: 2px; }
.ui-input[aria-invalid="true"], .ui-select[aria-invalid="true"] { border-color: var(--ui-bad); }
.ui-input--money { text-align: end; font-variant-numeric: tabular-nums; }
.ui-select { appearance: none; -webkit-appearance: none; padding-inline-end: 40px; 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='%2355516b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 14px center; }
.ui-textarea { resize: vertical; min-height: 84px; }
/* A LOCKED FIELD DOES NOT LOOK EDITABLE (FACT-OWNER #11, 2026-09-24): the numbering selects, frozen once a number
   is out, were the same filled boxes as the fields around them. Disabled = no fill, a dashed outline, the chevron
   replaced by a padlock, the ink one step quieter. Also what a read-only role sees on every form. */
.ui-input:disabled, .ui-input:disabled:hover, .ui-textarea:disabled, .ui-textarea:disabled:hover { background: transparent; border: 1.5px dashed #d6d1e8; color: var(--ui-ink-2); -webkit-text-fill-color: var(--ui-ink-2); cursor: not-allowed; opacity: 1; }
.ui-select:disabled, .ui-select:disabled:hover { background: transparent 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='%236f6b86' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='5' y='11' width='14' height='10' rx='2'/%3E%3Cpath d='M8 11V7a4 4 0 0 1 8 0v4'/%3E%3C/svg%3E") no-repeat right 14px center; border: 1.5px dashed #d6d1e8; color: var(--ui-ink-2); -webkit-text-fill-color: var(--ui-ink-2); cursor: not-allowed; opacity: 1; }
.ui-field__error { display: block; margin-top: 6px; font-size: var(--ui-fs-small); color: var(--ui-bad); font-weight: 500; }
.ui-field__hint { display: block; margin-top: 6px; font-size: var(--ui-fs-small); color: var(--ui-ink-3); }
.ui-notice { display: flex; gap: var(--ui-s3); padding: var(--ui-s3) var(--ui-s4); border-radius: 16px; background: var(--ui-surface-2); font-size: var(--ui-fs-small); }
/* A flex child defaults to `min-width: auto`, so it cannot shrink below its content's
   intrinsic width. A notice holding a long sentence and a pair of buttons therefore forced
   442px into a 366px box at 390, and Chrome answered by rendering the WHOLE PAGE at 83 %
   (measured on /documents/en-masse: innerWidth 470 vs visualViewport 390, body text
   16px → 13.3px). Every notice in the app had this shape; none had been given a long
   enough child before. (phone review 2026-09-07, MAJOR 2) */
.ui-notice > * { min-width: 0; }
.ui-notice--warn { background: var(--ui-warn-soft); color: var(--ui-warn); }
.ui-notice--bad { background: var(--ui-bad-soft); color: var(--ui-bad); }
.ui-notice--outline { background: var(--ui-accent-soft); color: var(--ui-ink); font-size: var(--ui-fs-body); }
.ui-notice a { display: inline-flex; align-items: center; min-height: var(--ui-tap); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }
/* UXUI-12 (review 2026-09-23): a BUTTON inside a notice is a button — the notice's link underline leaked into it. */
.ui-notice a.ui-btn { text-decoration: none; }
.ui-disclosure { border-radius: 16px; }
.ui-disclosure > summary { list-style: none; cursor: pointer; display: flex; align-items: center; gap: var(--ui-s2); min-height: var(--ui-tap); font-weight: 500; color: var(--ui-ink); }
.ui-disclosure > summary::-webkit-details-marker { display: none; }
.ui-disclosure > summary::before { content: '+'; display: inline-flex; width: 22px; height: 22px; align-items: center; justify-content: center; border-radius: 50%; background: var(--ui-accent-soft); color: var(--ui-accent-deep); font-weight: 700; flex: none; }
.ui-disclosure[open] > summary::before { content: '–'; }
.ui-disclosure > summary .ui-small { font-weight: 400; }
.ui-disclosure__body { padding: var(--ui-s3) 0 var(--ui-s2) 30px; }
.ui-dates { display: grid; gap: var(--ui-s4); grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); }
.ui-dates .ui-field + .ui-field { margin-top: 0; }

/* Typeahead (client). */
.ui-typeahead { position: relative; }
.ui-typeahead__list { position: absolute; inset-inline: 0; top: calc(100% + 6px); z-index: 20; background: var(--ui-surface); border-radius: 16px; box-shadow: var(--ui-shadow-lift); padding: var(--ui-s2); max-height: 320px; overflow: auto; }
.ui-typeahead__item { display: flex; align-items: center; gap: var(--ui-s2); width: 100%; min-height: var(--ui-tap); padding: 0 var(--ui-s3); border-radius: 12px; text-align: start; color: var(--ui-ink); }
.ui-typeahead__item:hover { background: var(--ui-surface-2); }
.ui-typeahead__new { color: var(--ui-accent); font-weight: 600; margin-top: var(--ui-s1); background: var(--ui-accent-soft); }
.ui-typeahead__none { padding: var(--ui-s2) var(--ui-s3); color: var(--ui-ink-2); }

/* ---- 11. The editor ------------------------------------------------------------- */
.ui-editor { display: grid; gap: var(--ui-s5); align-items: start; }
.ui-editor > * { min-width: 0; }
@media (min-width: 1100px) { .ui-editor { grid-template-columns: minmax(0, 7fr) minmax(0, 6fr); } .ui-editor__preview { position: sticky; top: calc(var(--ui-pill-h) + var(--ui-s6)); } .ui-preview-btn { display: none !important; } }
/* Under 1100 px the document is a button away: the pane exists only as the full layer. */
@media (max-width: 1099px) { .ui-editor__preview:not(.is-full) { display: none; } }
.ui-editor__main { min-width: 0; }
.ui-editor__form { padding: var(--ui-s5); }
.ui-editor__preview { min-width: 0; }
.ui-editor__phead { display: flex; align-items: center; gap: var(--ui-s3); margin-bottom: var(--ui-s3); flex-wrap: wrap; }
.ui-editor__phead > div:first-child { margin-inline-end: auto; }
.ui-editor__model { display: inline-flex; align-items: center; gap: var(--ui-s2); flex-wrap: wrap; }
.ui-editor__model .ui-select { min-height: var(--ui-tap); padding: 6px 36px 6px 14px; width: auto; max-width: 100%; background-color: var(--ui-surface); box-shadow: var(--ui-shadow); border-radius: 999px; font-weight: 600; }
/* The document pane (editor, record page): docked beside the work, or the same element as a
   full-screen layer («Agrandir»; on the editor under 1100 px, «Aperçu»). */
.ui-docpane { min-width: 0; }
.ui-docpane__head { display: flex; align-items: center; gap: var(--ui-s3); margin-bottom: var(--ui-s3); flex-wrap: wrap; }
.ui-docpane__head > div:first-child { margin-inline-end: auto; }
.ui-docpane.is-full { position: fixed; inset: 0; top: 0 !important; z-index: 60; overflow: auto; background: var(--ui-canvas); padding: 0 var(--ui-s4) var(--ui-s8); animation: ui-rise .2s both; }
.ui-docpane.is-full .ui-docpane__head { position: sticky; top: 0; z-index: 1; max-width: 900px; margin: 0 auto var(--ui-s4); padding: var(--ui-s3) 0; background: var(--ui-canvas); }
.ui-docpane.is-full .ui-editor__plead { display: none; }
.ui-docpane.is-full .ui-docframe { margin: 0 auto; }
/* At 100 % on a phone the A4 is 794 px in a 390 px viewport: the pane must scroll sideways
   as well as down, and the frame must not be squeezed back to the viewport width by
   `.ui-docframe { max-width: 100% }` (phone review 2026-09-07, MAJOR 1). */
@media (max-width: 899px) { .ui-docpane.is-full .ui-docframe { max-width: none; margin: 0; } }
body.ui-has-full { overflow: hidden; }
/* The page behind an OPEN sheet does not scroll (UX-MOBILE 15).
   `:has(.ui-sheet[role="dialog"])` matched on PRESENCE, and the help drawer and the phone's
   «Plus» sheet are on every screen, hidden by `x-show` but still in the DOM. So `body` computed
   `overflow: hidden` on EVERY page of the app and the whole product could not be scrolled by
   wheel or touch — the owner hit it the moment he logged in (2026-09-07). Programmatic
   scrolling still worked, which is why every harness and every fullPage screenshot missed it.
   A sheet that is always present now declares its state, and only an open one locks the page. */
body:has(.ui-sheet[role="dialog"]:not([data-sheet-open="false"])) { overflow: hidden; }
@media (max-width: 899px) { .ui-docpane.is-full { padding-inline: var(--ui-s3); } .ui-docpane.is-full .ui-docpane__head { display: grid; grid-template-columns: 1fr auto; } .ui-docpane.is-full .ui-docpane__head > div:first-child { grid-row: 1; grid-column: 1; } .ui-docpane.is-full [data-preview-close] { grid-row: 1; grid-column: 2; } .ui-docpane.is-full .ui-editor__model { grid-row: 2; grid-column: 1 / -1; } .ui-docpane.is-full .ui-editor__model .ui-select { flex: 1 1 auto; } }

/* ---- 11b. The record page (pass 2): one rail beside the document ---------------------- */
.ui-record { display: grid; gap: var(--ui-s5); align-items: start; }
.ui-record > * { min-width: 0; }
@media (min-width: 1100px) { .ui-record { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); } .ui-record__doc { position: sticky; top: calc(var(--ui-pill-h) + var(--ui-s6)); } }
.ui-record__rail { padding: var(--ui-s5); }
.ui-record__status { display: flex; align-items: center; gap: var(--ui-s2); flex-wrap: wrap; margin-top: var(--ui-s2); }
/* The record's client link was 180×28 (FACT-ENGAGEMENT m9, 2026-09-24): 14 px of vertical padding makes
   it a 44 px target without moving the status line (an inline box's padding does not grow its line);
   above it is the title and below it a label, neither of them a control. */
.ui-record__status [data-record-client] { position: relative; padding-block: 14px; }
.ui-record__figure { margin-top: var(--ui-s4); }
.ui-record__meta { margin-top: var(--ui-s2); color: var(--ui-ink-2); font-weight: 500; }
.ui-record__meta--alert { color: var(--ui-bad); }
.ui-meter { height: 8px; border-radius: 999px; background: var(--ui-surface-2); overflow: hidden; margin-top: var(--ui-s3); }
.ui-meter__fill { height: 100%; border-radius: 999px; background: var(--ui-good); }
.ui-record__actions { margin-top: var(--ui-s5); }
.ui-record__actions .ui-btn--primary { flex: 0 1 auto; min-width: 0; }
.ui-record__facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--ui-s4) var(--ui-s5); margin-top: var(--ui-s6); }
.ui-record__facts dt { font-size: var(--ui-fs-small); color: var(--ui-ink-2); font-weight: 500; }
.ui-record__facts dd { margin-top: 2px; font-weight: 600; }
.ui-record__facts dd .ui-small { display: block; font-weight: 400; }
.ui-record__facts--wide { grid-column: 1 / -1; }
.ui-record__more { display: flex; flex-wrap: wrap; gap: var(--ui-s2); margin-top: var(--ui-s5); }
/* «Plus d'actions»: past the first two, the quieter actions fold into one menu (FACT-OWNER #8,
   FACT-ENGAGEMENT m8, 2026-09-24 — a devis piled up seven buttons, a phone five full-width outlines). */
.ui-record__overflow > summary { gap: var(--ui-s1); }
.ui-record__overflow[open] > summary svg { transform: rotate(180deg); }
.ui-record__overflow .ui-overflow__menu { inset-inline-end: auto; inset-inline-start: 0; }
.ui-relance { align-items: center; gap: var(--ui-s3); flex-wrap: wrap; }
/* «Pourquoi ?»: the law behind a plain line, folded (rule B4). */
.ui-why > summary { display: inline-flex; align-items: center; gap: 4px; min-height: 32px; cursor: pointer; list-style: none; color: var(--ui-accent); font-weight: 500; }
.ui-why > summary::-webkit-details-marker { display: none; }
.ui-why > summary::after { content: '›'; transition: transform .15s; }
.ui-why[open] > summary::after { transform: rotate(90deg); }
.ui-why > span { display: block; margin-top: var(--ui-s1); color: var(--ui-ink-2); font-weight: 400; }
@media (max-width: 899px) {
  .ui-why > summary { min-height: var(--ui-tap); }
  .ui-record__overflow > summary { width: 100%; justify-content: center; }
  .ui-record__overflow .ui-overflow__menu { inset-inline: 0; min-width: 0; }
}
.ui-record__section { margin-top: var(--ui-s6); }
.ui-record__section-title { font: 600 var(--ui-fs-title)/1.25 var(--ui-display); letter-spacing: -0.015em; margin-bottom: var(--ui-s3); }
.ui-pay { display: flex; justify-content: space-between; gap: var(--ui-s4); align-items: flex-start; padding: var(--ui-s3) 0; }
.ui-pay + .ui-pay { border-top: 1px solid var(--line); }
.ui-pay__amount { font: 700 1rem/1.2 var(--ui-display); font-variant-numeric: tabular-nums; white-space: nowrap; }
.ui-record__danger { margin-top: var(--ui-s7); padding-top: var(--ui-s5); }
.ui-record__danger .ui-btn { color: var(--ui-bad); box-shadow: inset 0 0 0 1.5px var(--ui-bad-soft); background: var(--ui-surface); }
.ui-record__danger .ui-btn:hover { background: var(--ui-bad-soft); }
.ui-pay-sheet__amount { font-size: 1.5rem; font-weight: 700; font-family: var(--ui-display); min-height: 56px; }
@media (max-width: 899px) { .ui-record__rail { padding: var(--ui-s4); } .ui-record__facts { grid-template-columns: 1fr 1fr; } }
.ui-mode { display: inline-flex; padding: 3px; border-radius: 999px; background: rgba(23, 20, 43, .06); }
.ui-mode__btn { min-height: var(--ui-tap); padding: 0 16px; border-radius: 999px; font-weight: 600; color: var(--ui-ink-2); }
.ui-mode__btn[aria-pressed="true"] { background: var(--ui-ink); color: #fff; }
.ui-steps-nav { display: flex; gap: var(--ui-s4); margin: 0 0 var(--ui-s5); flex-wrap: wrap; }
.ui-steps-nav__item { display: inline-flex; align-items: center; gap: var(--ui-s2); color: var(--ui-ink-3); font-size: var(--ui-fs-small); font-weight: 600; }
.ui-steps-nav__item[aria-current="step"] { color: var(--ui-ink); }
.ui-steps-nav__n { width: 24px; height: 24px; border-radius: 50%; background: var(--ui-surface-2); display: inline-flex; align-items: center; justify-content: center; font-size: 12px; }
.ui-steps-nav__item[aria-current="step"] .ui-steps-nav__n { background: var(--ui-accent); color: #fff; }
.ui-steps-nav__item[data-done="1"] .ui-steps-nav__n { background: var(--ui-good-soft); color: var(--ui-good); }

.ui-lines { display: grid; gap: var(--ui-s2); }
.ui-line { padding: var(--ui-s3); border-radius: 16px; background: var(--ui-surface-2); }
.ui-line--head { background: none; padding: 0 var(--ui-s3); display: grid; grid-template-columns: 1fr 5rem 8rem 7rem 5.5rem; gap: var(--ui-s3); font-size: var(--ui-fs-small); color: var(--ui-ink-3); font-weight: 600; }
.ui-line--head .ui-line__qty, .ui-line--head .ui-line__price, .ui-line--head .ui-line__total { text-align: end; }
.ui-line__main { display: grid; grid-template-columns: 1fr 5rem 8rem 7rem 5.5rem; gap: var(--ui-s3); align-items: start; }
.ui-line .ui-input { background: var(--ui-surface); }
.ui-line__cell { display: block; min-width: 0; }
.ui-line__label { display: none; }
.ui-line__total { text-align: end; padding-top: 11px; font-weight: 600; font-variant-numeric: tabular-nums; }
.ui-line__figure { font-weight: 600; }
.ui-line__tools { display: flex; justify-content: flex-end; }
.ui-line__summary { margin: var(--ui-s2) 0 0; }
.ui-line__more { display: grid; gap: var(--ui-s3); grid-template-columns: repeat(3, minmax(0, 1fr)); margin-top: var(--ui-s3); padding-top: var(--ui-s3); }
.ui-line__more .ui-field + .ui-field { margin-top: 0; }
.ui-line__more .ui-input, .ui-line__more .ui-select, .ui-line__more .ui-textarea { background: var(--ui-surface); }
.ui-line__more-wide { grid-column: 1 / -1; }
@media (max-width: 899px) {
  .ui-line--head { display: none; }
  .ui-line__main { grid-template-columns: 1fr 1fr 1fr; grid-template-areas: "desig desig desig" "qty price total"; }
  .ui-line__desig { grid-area: desig; } .ui-line__qty { grid-area: qty; } .ui-line__price { grid-area: price; } .ui-line__total { grid-area: total; }
  .ui-line__tools { display: none; }
  .ui-line__label { display: block; font-size: var(--ui-fs-small); color: var(--ui-ink-2); margin-bottom: 2px; }
  .ui-line__total { padding-top: 0; }
  .ui-line__more { grid-template-columns: 1fr; }
}
.ui-review { display: grid; gap: var(--ui-s4); grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); }
.ui-review dd { margin: 2px 0 0; font-weight: 600; }
.ui-totals { margin-top: var(--ui-s5); }
.ui-totals__list { display: grid; gap: var(--ui-s2); max-width: 22rem; margin-inline-start: auto; }
.ui-totals__list > div { display: flex; justify-content: space-between; gap: var(--ui-s4); }
.ui-totals__list dt { color: var(--ui-ink-2); }
.ui-totals__list dd { font-weight: 600; }
.ui-totals__grand { padding-top: var(--ui-s2); margin-top: var(--ui-s1); align-items: baseline; font-size: 1.25rem; }
.ui-totals__grand dt { color: var(--ui-ink); font-weight: 700; }

/* The document frame (editor preview + end screen): A4 scaled to its width. */
.ui-docframe { position: relative; width: 100%; max-width: 100%; margin: 0 auto; border-radius: 12px; overflow: hidden; background: #fff; box-shadow: var(--ui-shadow-lift); }
.ui-docframe iframe { display: block; width: 794px; height: 1123px; border: 0; transform-origin: 0 0; background: #fff; }
.ui-docframe--live iframe { transition: opacity .15s; }
.ui-docframe.is-loading iframe { opacity: .6; }

/* ---- 11c. Lists (pass 3): segments, filters, one surface of rows, a table on desk ---------- */
.ui-page__head .ui-mode a { text-decoration: none; }
.ui-mode__btn { display: inline-flex; align-items: center; }
.ui-toolbar { display: flex; gap: var(--ui-s3); align-items: center; flex-wrap: wrap; margin-bottom: var(--ui-s4); }
.ui-toolbar .ui-input { max-width: 24rem; background: var(--ui-surface); box-shadow: var(--ui-shadow); }
.ui-toolbar__end { margin-inline-start: auto; display: flex; gap: var(--ui-s2); align-items: center; }
.ui-filters { display: flex; gap: var(--ui-s2); flex-wrap: wrap; margin-bottom: var(--ui-s4); }
.ui-filter { display: inline-flex; align-items: center; gap: var(--ui-s2); min-height: var(--ui-tap); padding: 0 14px; border-radius: var(--ui-r-pill); background: rgba(23, 20, 43, .05); color: var(--ui-ink-2); font-weight: 500; transition: background .15s, color .15s; }
.ui-filter:hover { background: rgba(23, 20, 43, .09); color: var(--ui-ink); }
.ui-filter[aria-current="true"] { background: var(--ui-ink); color: #fff; }
.ui-filter__count { font-size: var(--ui-fs-small); font-weight: 600; font-variant-numeric: tabular-nums; }
.ui-filter--alert:not([aria-current="true"]) { color: var(--ui-bad); }
.ui-filter-note { display: flex; gap: var(--ui-s2); align-items: center; flex-wrap: wrap; margin-bottom: var(--ui-s4); font-size: var(--ui-fs-small); color: var(--ui-ink-2); }
.ui-toggle { display: inline-flex; align-items: center; gap: var(--ui-s2); min-height: var(--ui-tap); font-size: var(--ui-fs-small); color: var(--ui-ink-2); cursor: pointer; }
.ui-toggle input { width: 18px; height: 18px; accent-color: var(--ui-accent); }
.ui-card--list { padding: var(--ui-s2); }
.ui-table td:first-child, .ui-table th:first-child { min-width: 14rem; }
.ui-table .ui-badge { white-space: nowrap; }
/* UXUI-1 (review 2026-09-23): the list's row action («Facturer») was pushed out of the card at 1440/1280. The
   contact's badges sit under its name; an e-mail may break anywhere rather than widen its column; a table that
   opens on a DATE does not reserve the 14 rem meant for a name. */
.ui-row-badges { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
.ui-table td.ui-cell-contact { overflow-wrap: anywhere; min-width: 8rem; }
.ui-table--lead-date td:first-child, .ui-table--lead-date th:first-child { min-width: 0; }
.ui-table .ui-row-actions .ui-btn--ghost { padding-inline: var(--ui-s3); }
.ui-sort-phone { display: none; }
.ui-list-total { margin: calc(var(--ui-s2) * -1) 0 var(--ui-s4); font-weight: 600; color: var(--ui-ink-2); }
/* Filters and the phone's sort on ONE line (2026-09-09). Two controls that both narrow a
   list were on two rows costing 44 + 46 px plus their gaps, on a screen where 1.7 of 650
   invoices were visible. The filters scroll; the sort keeps its width at the end and does
   not, so the control that is always in the same place stays in the same place. */
.ui-listbar { display: contents; }
@media (max-width: 899px) {
  .ui-listbar { display: flex; align-items: center; gap: var(--ui-s2); margin-bottom: var(--ui-s4); min-width: 0; }
  .ui-listbar .ui-filters { margin-bottom: 0; flex: 1 1 auto; min-width: 0; }
  .ui-sort-phone { display: flex; align-items: center; gap: var(--ui-s2); margin: 0; flex: 0 0 auto; }
  /* The word «Trier» goes: the select's own options say what it does, and at 390 the label
     was costing width the filters needed. It stays for a screen reader. */
  .ui-sort-phone > .ui-small { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  /* The sort is an icon button (owner persona 2026-09-10: the 150-px select covered «En retard»
     and «En attente de réponse», the two chips the list is opened for). The native select
     stays — transparent over the icon — so the phone's own picker opens and a screen reader
     hears «Trier». A dot says a sort other than the default is on. */
  .ui-sort-phone { position: relative; width: var(--ui-tap); height: var(--ui-tap); justify-content: center; border-radius: var(--ui-r-pill); background: var(--ui-surface); color: var(--ui-ink); }
  .ui-sort-phone__icon { pointer-events: none; }
  .ui-sort-phone .ui-select { position: absolute; inset: 0; width: 100%; max-width: none; height: 100%; min-height: 0; padding: 0; opacity: 0; cursor: pointer; }
  .ui-sort-phone.is-sorted::after { content: ""; position: absolute; top: 9px; right: 9px; width: 8px; height: 8px; border-radius: 50%; background: var(--ui-accent); box-shadow: 0 0 0 2px var(--ui-surface); pointer-events: none; }
  .ui-sort-phone:focus-within { outline: 2px solid var(--ui-accent); outline-offset: 2px; }
}
.ui-card.is-stale { opacity: .55; transition: opacity .2s; }
.ui-empty { padding: var(--ui-s7) var(--ui-s5); text-align: center; }
.ui-empty__title { font: 600 var(--ui-fs-title)/1.25 var(--ui-display); letter-spacing: -0.015em; }
.ui-empty__text { margin: var(--ui-s2) auto 0; max-width: 34rem; color: var(--ui-ink-2); }
.ui-empty .ui-btn { margin-top: var(--ui-s4); }
/* Phone rows (≤ 899): one anchor per row, the amount and the badge on the right. */
.ui-rows { display: grid; gap: var(--ui-s1); }
.ui-row-item { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--ui-s3); align-items: center; min-height: 56px; padding: var(--ui-s2) var(--ui-s3); border-radius: 14px; color: inherit; }
a.ui-row-item:hover { background: rgba(23, 20, 43, .05); }
/* A row can be handed a long unbroken string it did not choose — an exception class name, a
   URL, a reference with no spaces. Without a break rule it pushes the whole page sideways at
   390 (`DirectlyCallingLifecycleHooksNotAllowedException`, measured 2026-09-07). */
/* The phone's version of the same rank. THE PHONE IS THE PRODUCT (FINISH PLAN §4), so the
   row that carries the whole list on a 390 px screen gets the anchor first, not last. */
.ui-row-item__primary { font-size: var(--ui-fs-lead); font-weight: 600; letter-spacing: -0.011em; overflow-wrap: anywhere; }
.ui-row-item__secondary { font-size: var(--ui-fs-small); color: var(--ui-ink-3); overflow-wrap: anywhere; }
.ui-row-item.is-late { box-shadow: inset 3px 0 0 var(--ui-bad); }
.ui-row-item.is-late .ui-row-item__amount { color: var(--ui-bad); }
.ui-row-item__end { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.ui-row-item__amount { font: 700 1rem/1.2 var(--ui-display); font-variant-numeric: tabular-nums; white-space: nowrap; }
.ui-row-item__actions { display: flex; gap: var(--ui-s1); grid-column: 2; }
.ui-field__req { color: var(--ui-bad); font-weight: 700; }
.ui-row-item__link { display: block; min-width: 0; color: inherit; min-height: var(--ui-tap); padding: var(--ui-s1) 0; }
/* The table (≥ 900): headers sort, money columns right-aligned with their headers (L-2). */
/* Wide content scrolls inside its OWN box, never the page. There was no base rule here at all
   — only a media query hiding the wrapper on phones — so every table in the app depended on
   happening to fit, and the surveillance console's six columns pushed the document to 1641 px
   at a 1440 px viewport (measured 2026-09-07). `min-width: 0` because a grid/flex child will
   not shrink below its content without it, which is what makes the scrollbar appear on the
   page instead of on the box. */
.ui-table-wrap { position: relative; overflow-x: auto; max-width: 100%; min-width: 0; -webkit-overflow-scrolling: touch; }
/* `position: relative` (2026-09-08): an absolutely positioned descendant — the `.ui-sr` labels in
   the back-office tables — takes the nearest positioned ancestor as its box, and with the wrap
   unpositioned that was `.ui-shell`: the label escaped the wrap's clip and sat at x 1510 in a
   1440 window, so /admin scrolled sideways the moment the sidebar narrowed the content
   (check-no-sideways.mjs). Positioning the wrap keeps its own absolutes inside it. */

/* A LIST HAS TO HAVE A RANK. Measured 2026-09-09: the invoice list drew on two font weights
   and three text colours for six columns, so the document, the date, the client, the status
   and two money columns all read at exactly one level — 620 rows, and nothing on any of them
   said which one needed you. That is the owner's «blunt».

   Nothing below moves an element or adds one. The columns, the order and the markup are
   untouched; what changes is which of them the eye is allowed to reach first:
     the head recedes (11 px, tracked, ink-3 — a label, not a row)
     the document is the anchor (17 px / 600 — the one thing the row is scanned for)
     the date recedes (13 px, ink-3 — it is metadata, and it was at body size)
     the figures come forward (600, tabular — money is why the screen exists)
     and a LATE invoice says so before it is read: the balance in the bad colour and a bar
     down the start of the row. */
.ui-table { width: 100%; border-collapse: collapse; }
.ui-table th {
  text-align: start; padding: var(--ui-s2) var(--ui-s3);
  font-size: var(--ui-fs-micro); font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ui-ink-3);
}
/* UXUI-2 (review 2026-09-23): a sortable header is a <button>, which never took the uppercase, so a list mixing
   both read «Nom · FORME · ICE · Ville». In a table that has sortable headers, the plain ones read like them. */
.ui-table:has(.ui-th-sort) th { letter-spacing: normal; text-transform: none; }
.ui-table th.col-figure, .ui-table td.col-figure { text-align: end; font-variant-numeric: tabular-nums; }
.ui-table td { padding: var(--ui-s1) var(--ui-s3); vertical-align: middle; border-top: 1px solid var(--line); }
.ui-table tbody tr { transition: background .12s; }
.ui-table tbody tr:hover { background: rgba(23, 20, 43, .05); }
.ui-table .cell-link, .ui-cell-link {
  font-size: var(--ui-fs-lead); font-weight: 600; letter-spacing: -0.011em;
  color: var(--ui-ink); display: inline-flex; align-items: center; min-height: var(--ui-tap);
}
.ui-table .row--linked, .ui-row--linked { position: relative; }
.ui-table .row--linked .cell-link::after { content: ''; position: absolute; inset: 0; }
.ui-table .cell-date, .ui-cell-date { white-space: nowrap; font-size: var(--ui-fs-small); color: var(--ui-ink-3); }
.ui-col-figure { text-align: end; font-variant-numeric: tabular-nums; }
.ui-table td.ui-col-figure { font-weight: 600; }

/* Late. The row carries it twice — a colour on the figure and a bar at the start — because
   colour alone is not a signal for everyone (WCAG 2.2, 1.4.1 «use of colour»). The bar is an
   inset shadow, not a border, so it adds no width and the table's column widths do not move. */
.ui-table tbody tr.is-late td:first-child { box-shadow: inset 3px 0 0 var(--ui-bad); }
.ui-table tbody tr.is-late .ui-col-figure:last-child { color: var(--ui-bad); }
.ui-table .t-micro { font-size: var(--ui-fs-small); color: var(--ui-ink-2); }
.ui-table td .ui-btn { position: relative; z-index: 1; }
/* «Facturer» is always visible (it starts the 3-click job); only the archive icon waits for the hover (UX review). */
@media (hover: hover) { .ui-table .ui-row-actions .ui-btn--icon { opacity: 0; transition: opacity .12s; } .ui-table tr:hover .ui-row-actions .ui-btn--icon, .ui-table tr:focus-within .ui-row-actions .ui-btn--icon { opacity: 1; } }
.th-sort, .ui-th-sort { display: inline-flex; align-items: center; gap: 4px; min-height: var(--ui-tap); min-width: var(--ui-tap); color: inherit; font: inherit; border-radius: 8px; padding: 0 6px; margin: 0 -6px; }
.th-sort:hover { background: rgba(23, 20, 43, .05); color: var(--ui-ink); }
th[aria-sort="ascending"] .th-sort, th[aria-sort="descending"] .th-sort { color: var(--ui-ink); }
th[aria-sort="none"] .th-sort::after { content: '↕'; font-size: 11px; opacity: 0; transition: opacity .12s; }
th[aria-sort="none"] .th-sort:hover::after { opacity: .7; }
@media (max-width: 899px) {
  .ui-table-wrap { display: none; } .ui-toolbar .ui-input { max-width: none; } .ui-toolbar__end { margin-inline-start: 0; width: 100%; }
  /* Nothing hides on the phone: the filters WRAP (UX review) and the segments become a 2-column grid
     (journey-20: a segment in a scrolling strip sat outside the viewport). */
  /* THE TYPE SWITCHER IS ONE ROW ON A PHONE, NOT A GRID.
     Measured 2026-09-09 at 390x844: the two-column grid turned five document types into a
     2+2+1 block 132 px tall — 16 % of the screen — with the LONGEST label («Factures
     proforma») in a half-width cell and the shortest alone on a full-width row. Below it the
     filter chips wrapped to their own orphan row (272 px of dead space on Avoirs). Between
     them, 72 % of the first screen was chrome and 1.7 of 650 invoices were visible.
     A phone shows five filters the way phones show filters: one scrollable row of chips,
     which is also what the row beneath it already is — so the two now read as one system
     instead of two different mechanisms doing the same job.
     `scroll-snap` plus `ui-scroller-into-view` (app.js) keeps the CURRENT one on screen —
     journey-20 once caught a segment parked outside the viewport, and that is the failure
     this has to avoid. */
  .ui-page__head .ui-mode {
    display: flex; flex-wrap: nowrap; width: 100%; border-radius: var(--ui-r-pill);
    overflow-x: auto; scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .ui-page__head .ui-mode::-webkit-scrollbar { display: none; }
  /* `start`, not `center` (handed over by the Paie agent, 2026-09-15): a segment near the end
     of the scroller CANNOT be centred, so a proximity snap left the last segment of every
     segmented control clipped — 5 px on the Paie paramètres screen, the same family as the
     «En retard» chip. Snapping to the start lands whole segments at the row's own edge. */
  .ui-page__head .ui-mode__btn { flex: 0 0 auto; justify-content: center; text-align: center; padding: 0 14px; scroll-snap-align: start; }
  /* The filters follow the same rule, for the same reason: [Tous | Impayés | En retard] then
     [Brouillons | Soldées] left 116 px of dead row on Factures and 272 px on Avoirs. */
  .ui-filters {
    flex-wrap: nowrap; overflow-x: auto; scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
    margin-inline: calc(var(--ui-s3) * -1); padding-inline: var(--ui-s3);
    /* A snap lands a chip flush against the row's own start, not centred: centring leaves
       BOTH neighbours half-painted, which is the thing being fixed below. */
    scroll-padding-inline: var(--ui-s3) var(--ui-s5);
    gap: 6px;
  }
  .ui-filters::-webkit-scrollbar { display: none; }
  .ui-filter { flex: 0 0 auto; scroll-snap-align: start; }
  /* A row that scrolls has to END in a fade, never in a sliced letter. Every filter row
     measured at 390 is wider than the screen by design (852 px of chips on /documents, 741 on
     /achats, 506 on /catalogue), so SOMETHING is always at the edge: the fade is what says
     «there is more to the right», where a hard cut through a word reads as a broken button —
     the owner's «buttons just thrown away». This is the device the listbar has used since
     2026-09-10, now on every phone-width chip row; a row whose chips do not fill it leaves the
     fade region empty and is unchanged. */
  .ui-filters {
    /* 16 px, not the listbar's 24: the fade has to be a dissolve the eye reads as «more this
       way» without eating a chip's last word — at 24 the alert chip's own label ended one
       pixel inside it (measured, /documents at 390, 2026-09-15). */
    padding-inline-end: 16px;
    -webkit-mask-image: linear-gradient(to left, transparent 0, #000 16px);
    mask-image: linear-gradient(to left, transparent 0, #000 16px);
  }
  /* O-6, measured 2026-09-15 on /documents at 390: the row has 314 px of visible width and
     the first three chips cost 98 + 126 + 122 px, so «En retard» — the alert chip the list is
     opened FOR — was cut at 74 of its 122 px. Its word broke into «En retar» and its count
     was not on screen at all: a button thrown away, in the owner's words. The width is in the
     chip's own padding and in the 8 px it holds before a 13 px number; at 10 and 4 the same
     three chips cost 296 px, so the alert chip and its count are whole and the fourth chip
     starts as a visible hint instead of a broken word. Verified on the render by
     check-phone.mjs (CLIP-TEXT), not by reading this file. */
  .ui-filter { padding-inline: 10px; gap: var(--ui-s1); }
  .ui-filter .ui-filter__count { font-variant-numeric: tabular-nums; }
  .ui-mode__btn { white-space: nowrap; }
  .ui-filter { white-space: nowrap; }
  .ui-rows .ui-row-item__actions { min-width: 10.25rem; justify-content: flex-start; }
  .ui-row-item__of { display: block; text-align: end; }
  .ui-list-lead { display: none; }
}
@media (min-width: 900px) { .ui-rows { display: none; } }
/* The list foot + pagination (the view is production's; only its look lives here). */
.list-foot, .ui-list-foot { display: flex; justify-content: space-between; align-items: center; gap: var(--ui-s4); flex-wrap: wrap; margin-top: var(--ui-s4); }
.list-foot__size, .ui-list-foot__size { display: inline-flex; align-items: center; gap: var(--ui-s2); }
.list-foot .t-small { font-size: var(--ui-fs-small); color: var(--ui-ink-2); }
.list-foot .select { min-height: var(--ui-tap); padding: 6px 36px 6px 14px; border-radius: 999px; border: 0; background: var(--ui-surface) 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='%2355516b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") no-repeat right 14px center; box-shadow: var(--ui-shadow); font-weight: 600; appearance: none; -webkit-appearance: none; }
.pagination { display: flex; justify-content: space-between; align-items: center; gap: var(--ui-s4); flex-wrap: wrap; margin-top: var(--ui-s4); }
.list-foot .pagination { margin: 0; margin-inline-start: auto; }
.pagination__summary { font-size: var(--ui-fs-small); color: var(--ui-ink-2); }
.pagination__controls { display: flex; gap: var(--ui-s2); }
.pagination .btn { display: inline-flex; align-items: center; min-height: var(--ui-tap); padding: 0 var(--ui-s4); border-radius: var(--ui-r-pill); font-weight: 600; background: var(--ui-surface); color: var(--ui-ink); box-shadow: inset 0 0 0 1.5px var(--ui-stroke); }
.pagination .btn[disabled] { opacity: .45; }
/* The inline field tip (ICE, IF, RC, patente) — the component is shared, its look is the layer's. */
.tip, .ui-tip { position: relative; display: inline-block; margin-inline-start: 4px; vertical-align: middle; }
/* The HIT AREA stays 44x44 (D7), but what is PAINTED must not. With the background on the
   button itself, the hovered/focused circle covered the characters either side of it — «(IF)»
   disappeared under it on the first-invoice setup sheet, measured at 390 on 2026-09-07. The
   ring is drawn on an inset pseudo-element instead, so the target is still a thumb wide and
   the label stays readable. */
.tip-trigger, .ui-tip__trigger { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; margin: -12px -10px; border-radius: 50%; color: var(--ui-ink-3); vertical-align: middle; }
.tip-trigger::before, .ui-tip__trigger::before { content: ''; position: absolute; inset: 10px; border-radius: 50%; transition: background .12s; }
.tip-trigger > *, .ui-tip__trigger > * { position: relative; }
.tip-trigger:hover, .ui-tip__trigger:hover { color: var(--ui-accent-deep); }
.tip-trigger:hover::before, .ui-tip__trigger:hover::before,
.tip-trigger:focus-visible::before, .ui-tip__trigger:focus-visible::before { background: var(--ui-accent-soft); }
.tip-bubble, .ui-tip__bubble { position: absolute; z-index: 30; top: calc(100% + 6px); inset-inline-start: 0; width: min(20rem, 80vw); padding: var(--ui-s3) var(--ui-s4); border-radius: 14px; background: var(--ui-ink); color: #fff; font-size: var(--ui-fs-small); font-weight: 400; text-transform: none; letter-spacing: 0; line-height: 1.45; box-shadow: var(--ui-shadow-lift); }
.tip-title, .ui-tip__title { display: block; font-weight: 600; margin-bottom: 4px; }
.ui-tip__text { display: block; }
.tip-link, .ui-tip__link { display: inline-flex; align-items: center; gap: 4px; margin-top: 6px; color: #fff; text-decoration: underline; }
/* Forms on the layer (client form): one column, sections set apart by whitespace. */
.ui-form { max-width: 44rem; }
/* A screen whose whole content IS one form shares the form's right edge — head, notices and
   card on ONE axis. /documents/en-masse had the head and the notices running the full width
   of the shell while the card stopped 476 px short of it, which is what "five alignment axes
   on one screen" looks like (UX review 2026-09-06, M6). */
.ui-formpage { max-width: 44rem; }
.ui-formpage .ui-form { max-width: none; }
.ui-form .ui-dates { grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
.ui-form__section { margin-top: var(--ui-s6); }
.ui-form__section:first-child { margin-top: 0; }
.ui-form__title { font: 600 var(--ui-fs-title)/1.25 var(--ui-display); letter-spacing: -0.015em; }
.ui-form__lead { margin-top: var(--ui-s1); font-size: var(--ui-fs-small); color: var(--ui-ink-2); }
.ui-form__fields { margin-top: var(--ui-s4); }
.ui-check { display: flex; align-items: flex-start; gap: var(--ui-s3); min-height: var(--ui-tap); cursor: pointer; }
.ui-check input { width: 20px; height: 20px; margin-top: 2px; accent-color: var(--ui-accent); flex: none; }
.ui-check__note { display: block; font-size: var(--ui-fs-small); color: var(--ui-ink-2); }

/* ---- 11d. Declarations (pass 4): the return due now, its number on the canvas, the evidence ---- */
.ui-declare { max-width: 60rem; }
.ui-declare__head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--ui-s4); flex-wrap: wrap; }
.ui-declare__kicker { }
.ui-declare__title { font: 600 var(--ui-fs-title)/1.25 var(--ui-display); letter-spacing: -0.015em; margin-top: var(--ui-s2); }
.ui-declare__period { font: 700 var(--ui-fs-title)/1.25 var(--ui-display); letter-spacing: -0.015em; margin-top: 4px; }
.ui-declare__state { margin-top: 6px; color: var(--ui-ink-2); font-weight: 500; display: flex; align-items: center; gap: var(--ui-s2); }
.ui-declare--critical .ui-declare__state { color: var(--ui-bad); }
.ui-declare--done .ui-declare__state { color: var(--ui-good); }
.ui-declare__nav { display: flex; gap: var(--ui-s2); align-items: center; flex-wrap: wrap; }
.ui-declare__answer { margin-top: var(--ui-s5); }
.ui-declare__number { margin-top: var(--ui-s1); }
.ui-declare__lines { margin-top: var(--ui-s4); display: grid; gap: 0; max-width: 34rem; }
.ui-declare__row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--ui-s4); padding: var(--ui-s2) 0; align-items: baseline; }
.ui-declare__row--rate { grid-template-columns: 4rem minmax(0, 1fr) auto; }
.ui-declare__row--sum { font-weight: 600; }
.ui-declare__row--grand { font-weight: 700; padding-top: var(--ui-s3); }
/* The deduction's three lines of the return, under «− TVA déductible» (FACT-CPA F5, 2026-09-24), and the
   «Non taxable (débours)» rate line: labels wider than a rate, indented, in the secondary ink. */
.ui-declare__row.ui-declare__row--nature { grid-template-columns: 8.5rem minmax(0, 1fr) auto; padding-inline-start: var(--ui-s4); color: var(--ui-ink-2); padding-block: 2px; }
.ui-declare__row.ui-declare__row--wide { grid-template-columns: 8.5rem minmax(0, 1fr) auto; }
@media (max-width: 899px) {
  /* On a phone the label keeps the line; its HT value goes under it, the TVA stays on the right edge. */
  .ui-declare__row.ui-declare__row--nature, .ui-declare__row.ui-declare__row--wide { grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--ui-s3); row-gap: 0; }
  .ui-declare__row.ui-declare__row--nature { padding-inline-start: var(--ui-s3); }
  .ui-declare__row--nature > .ui-small, .ui-declare__row--wide > .ui-small { grid-column: 1; grid-row: 2; }
  .ui-declare__row--nature > .ui-num, .ui-declare__row--wide > .ui-num { grid-column: 2; grid-row: 1; }
}
.ui-declare__presets { display: flex; flex-wrap: wrap; gap: var(--ui-s2); margin-top: var(--ui-s3); }
.ui-evidence__lead { padding: 0 var(--ui-s3) var(--ui-s2); }
.declare__row--carried { color: var(--ui-ink-2); }
.ui-declare__rule { border: 0; height: 1px; background: var(--ui-surface-2); margin: var(--ui-s2) 0; }
.ui-declare__actions { display: flex; gap: var(--ui-s3); align-items: center; flex-wrap: wrap; margin-top: var(--ui-s5); }
.ui-declare__hint { flex-basis: 100%; }
.ui-declare__foot { margin-top: var(--ui-s5); font-size: var(--ui-fs-small); color: var(--ui-ink-2); max-width: 60rem; }
/* The rule a LATE declaration missed, on the band that leads with it (CPA review M5).
   Ink, not a red panel: the band already carries the critical tone. */
.ui-declare__foot--alert { color: var(--ui-bad); margin-top: var(--ui-s4); }
.ui-declare__section-title { font: 600 var(--ui-fs-title)/1.25 var(--ui-display); letter-spacing: -0.015em; margin: var(--ui-s7) 0 var(--ui-s3); }
.ui-declare__section-head { display: flex; justify-content: space-between; align-items: center; gap: var(--ui-s3); margin: var(--ui-s7) 0 var(--ui-s3); }
.ui-declare__section-head .ui-declare__section-title { margin: 0; }
.ui-declare__year .ui-select { width: auto; min-width: 7rem; background-color: var(--ui-surface); box-shadow: var(--ui-shadow); }
.ui-declare__evidence { display: grid; gap: var(--ui-s4); }
@media (min-width: 900px) { .ui-declare__evidence { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.ui-evidence { min-width: 0; }
.ui-evidence__title { font: 600 1rem/1.25 var(--ui-display); padding: var(--ui-s3) var(--ui-s3) var(--ui-s2); }
.ui-evidence__rows { display: grid; gap: var(--ui-s1); }
.ui-evidence__empty { padding: var(--ui-s5) var(--ui-s3); }
.ui-evidence__empty .ui-empty__title { font-size: 1rem; }
.ui-row-item--total { font-weight: 700; background: var(--ui-surface-2); }
.ui-row-item--static { align-items: center; }
.ui-text-bad { color: var(--ui-bad); font-weight: 600; }
.ui-declare__custom { margin-top: var(--ui-s2); }
.ui-declare__custom > summary { display: none; }
.ui-field--action { display: flex; flex-direction: column; justify-content: flex-end; }
.ui-lines__mode { display: flex; align-items: center; gap: var(--ui-s3); flex-wrap: wrap; margin: var(--ui-s2) 0 var(--ui-s3); }
.ui-mode--small .ui-mode__btn { padding: 0 12px; }
.ui-declare__nav { margin-top: var(--ui-s3); }
.ui-evidence__more { padding: var(--ui-s2) var(--ui-s3); }
.ui-filter--on { background: var(--ui-accent-soft); color: var(--ui-accent-deep); }
.ui-filters-row { margin: calc(var(--ui-s2) * -1) 0 var(--ui-s4); padding: var(--ui-s4); }
.ui-purchase-state { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ui-s2); margin-top: var(--ui-s2); }
@media (max-width: 899px) { .ui-page__head { flex-wrap: wrap; } .ui-page__head > .ui-btn { flex: 0 1 auto; max-width: 100%; } }
/* (removed 2026-09-09: the phone switcher is a scroll row, not a grid — see §media above) */
@media (min-width: 900px) {
  .ui-evidence__rows .ui-row-item--static { grid-template-columns: minmax(0, 1fr) auto auto; }
  .ui-evidence__rows .ui-row-item--static .ui-row-item__actions { grid-column: 3; }
}
@media (max-width: 899px) {
  .ui-declare__row--rate { grid-template-columns: 3.5rem minmax(0, 1fr) auto; }
  .ui-evidence__rows .ui-row-item--static .ui-row-item__actions { grid-column: 1 / -1; justify-content: flex-start; flex-wrap: wrap; }
}
.ui-export__period .ui-dates { grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); }
.ui-export__row .ui-row-item__secondary { display: inline-block; max-width: 44rem; }
.ui-toolbar__select { width: auto; max-width: 18rem; background-color: var(--ui-surface); box-shadow: var(--ui-shadow); }
.ui-file { display: flex; align-items: center; gap: var(--ui-s3); flex-wrap: wrap; position: relative; }
.ui-file__input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; }
.ui-file__input:focus-visible + label { outline: 3px solid var(--ui-accent); outline-offset: 2px; }
.ui-file__name { color: var(--ui-ink-2); }
.ui-file__progress { color: var(--ui-accent); font-weight: 600; }
.ui-filters-more > summary { min-height: var(--ui-tap); padding: 0 14px; border-radius: var(--ui-r-pill); background: rgba(23, 20, 43, .05); font-weight: 500; }
.ui-filters-more > summary::before { display: none; }
.ui-filters-more[open] { display: block; }
.ui-filters-more .ui-disclosure__body { padding-inline-start: 0; }
.ui-lines--purchase .ui-line--head, .ui-lines--purchase .ui-line__main { grid-template-columns: 1fr 9rem 7rem 5.5rem; }
.ui-check--line { display: inline-flex; align-items: center; gap: var(--ui-s2); min-height: var(--ui-tap); font-size: var(--ui-fs-small); }
.ui-check--line input { width: 18px; height: 18px; accent-color: var(--ui-accent); }
@media (max-width: 899px) {
  .ui-lines--purchase .ui-line__main { grid-template-columns: 1fr 1fr; grid-template-areas: "desig desig" "price total"; }
  .ui-toolbar__select { max-width: none; width: 100%; }
  /* (removed 2026-09-09: the phone switcher is a scroll row, not a grid — see §media above) */
  .ui-list-total { order: 2; }
}

/* ---- 11e. Réglages (pass 5): settings forms whose folds state their values, the template gallery, plans, the team ---- */
.ui-settings__kicker { margin-bottom: 4px; }
/* A fold that says what it holds: the summary carries the section's current value, so nothing is hidden by folding it. */
/* UX review 2026-09-05 (U3): this used to clip with an ellipsis. The whole point of the fold is
   that its SUMMARY states the current value, so a user does not have to open all seven to find
   out what is set — and the two that matter most were the ones that overflowed ("RC 145823 —
   Casablanca · Patente 35420187 · CNSS 9384756" needed 390 px and got 327; the numbering line
   needed 549 and got 382, cut at "…figé depuis FA-202"). A value that must be read is allowed a
   second line; the card has room. */
.ui-disclosure__value { flex: 1 1 0; min-width: 0; font-size: var(--ui-fs-small); font-weight: 400; font-family: var(--ui-font); letter-spacing: 0; color: var(--ui-ink-2); text-align: end; overflow-wrap: anywhere; }
.ui-disclosure--section > summary { font: 600 var(--ui-fs-title)/1.25 var(--ui-display); letter-spacing: -0.015em; flex-wrap: wrap; }
.ui-disclosure__title { flex: 0 1 auto; min-width: 0; }
@media (max-width: 899px) { .ui-disclosure__title { flex: 1 1 0; } }
.ui-disclosure--section > .ui-disclosure__body { padding-inline-start: 0; }
.ui-form__actions { display: flex; gap: var(--ui-s3); align-items: center; flex-wrap: wrap; margin-top: var(--ui-s5); }
.ui-logo { display: flex; align-items: center; gap: var(--ui-s3); flex-wrap: wrap; margin-top: var(--ui-s3); }
/* FACT-OWNER #11 (2026-09-24): a FIXED 48 px-high preview — a small file (the sandbox's 10×10) showed as a 10 px dot. */
.ui-logo__img { height: 48px; width: auto; min-width: 48px; max-width: 12rem; object-fit: contain; padding: 4px; border-radius: 8px; background: #fff; box-shadow: var(--ui-shadow); box-sizing: content-box; }
.ui-color { width: 72px; padding: 4px 6px; min-height: var(--ui-tap); cursor: pointer; }
/* Choice tiles (the activity profile, the way to pay): the whole tile is the control. */
.ui-choices { display: grid; gap: var(--ui-s3); grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }
.ui-choice { position: relative; display: flex; gap: var(--ui-s3); align-items: flex-start; min-height: var(--ui-tap); padding: var(--ui-s3) var(--ui-s4); border-radius: 16px; background: var(--ui-surface-2); box-shadow: inset 0 0 0 1.5px transparent; cursor: pointer; transition: background .15s, box-shadow .15s; }
.ui-choice:hover { background: #f1eef9; }
.ui-choice input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; margin: 0; cursor: pointer; }
.ui-choice:has(input:checked) { background: var(--ui-accent-soft); box-shadow: inset 0 0 0 2px var(--ui-accent); }
.ui-choice:has(input:focus-visible) { outline: 3px solid var(--ui-accent); outline-offset: 2px; }
/* A checkbox is a TARGET, not a glyph: the signup terms box measured 20x20 at 390 — half a
   thumb (first-run review 2026-09-07). The box keeps its drawn size; the hit area does not. */
input[type="checkbox"], input[type="radio"] { inline-size: 20px; block-size: 20px; min-inline-size: 20px; }
@media (pointer: coarse) {
  input[type="checkbox"], input[type="radio"] { inline-size: 24px; block-size: 24px; min-inline-size: 24px; margin-block: 10px; margin-inline: 10px -2px; }
}
.ui-choice__icon { flex: none; width: 36px; height: 36px; border-radius: 12px; background: var(--ui-surface); color: var(--ui-accent-deep); display: inline-flex; align-items: center; justify-content: center; }
.ui-choice__body { min-width: 0; }
.ui-choice__title { display: block; font-weight: 600; }
.ui-choice__text { display: block; font-size: var(--ui-fs-small); color: var(--ui-ink-2); }
.ui-demo { margin-top: var(--ui-s6); background: var(--ui-warn-soft); box-shadow: none; }
.ui-demo .ui-label { color: var(--ui-warn); }
.ui-check-grid { display: grid; gap: 0 var(--ui-s4); grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }
/* The block order: a numbered row with ↑ ↓. */
.ui-order { display: grid; gap: var(--ui-s1); }
.ui-order__row { display: flex; align-items: center; gap: var(--ui-s3); min-height: 56px; padding: var(--ui-s2) var(--ui-s3); border-radius: 14px; background: var(--ui-surface-2); }
.ui-order__n { flex: none; width: 28px; height: 28px; border-radius: 50%; background: var(--ui-surface); display: inline-flex; align-items: center; justify-content: center; font-size: var(--ui-fs-small); font-weight: 600; }
.ui-order__title { flex: 1 1 auto; min-width: 0; font-weight: 600; }
.ui-order__tools { display: flex; gap: var(--ui-s1); }
/* The template gallery: paper-sized previews, scaled down — never cropped (P-8). The real A4 is
   794 × 1123 px; CSS cannot divide two lengths, so the factor steps with the container's width,
   each step rounded DOWN against its breakpoint. */
.ui-gallery { display: grid; gap: var(--ui-s4); grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); align-items: stretch; }
.ui-tplcard { display: flex; flex-direction: column; padding: 0; overflow: hidden; min-width: 0; }
.ui-docthumb { position: relative; aspect-ratio: 794 / 1123; background: var(--ui-surface-2); overflow: hidden; container-type: inline-size; }
/* Every step must satisfy `794 x scale <= the container width at that step`, or the page is
   CROPPED by the box's own overflow:hidden — and "paper-sized previews, never cropped" is this
   screen's stated acceptance. The base step was 0.36, which is 285.8 px inside a 283 px desk
   card: 3 px shaved off the right edge of all seven thumbnails (dev review #9, UX U14). 0.35
   leaves a few pixels of background instead, which is the honest way round. */
.ui-docthumb iframe { position: absolute; top: 0; inset-inline-start: 0; width: 794px; height: 1123px; border: 0; pointer-events: none; background: #fff; transform-origin: top left; transform: scale(0.35); }
@container (min-width: 320px) { .ui-docthumb iframe { transform: scale(0.40); } }
@container (min-width: 360px) { .ui-docthumb iframe { transform: scale(0.45); } }
@container (min-width: 420px) { .ui-docthumb iframe { transform: scale(0.52); } }
@container (min-width: 480px) { .ui-docthumb iframe { transform: scale(0.60); } }
@container (min-width: 540px) { .ui-docthumb iframe { transform: scale(0.67); } }
@container (min-width: 620px) { .ui-docthumb iframe { transform: scale(0.78); } }
@container (min-width: 700px) { .ui-docthumb iframe { transform: scale(0.88); } }
@container (min-width: 794px) { .ui-docthumb iframe { transform: scale(1); } }
/* D7 — the phone is its own layout, and for many customers it is the ONLY surface they will see.
   Stacking seven whole A4 pages made this screen 5 356 px tall (about six full swipes) with the
   document body rendering at roughly 3 px: nothing on the first screen was even interactive, and
   the very thing that tells «Classique» from «Moderne» from «Bandeau latéral» — the header band,
   the logo position, where the identifiers sit — was an illegible sliver at the top of each one.
   So the phone shows THAT REGION, cropped deliberately and marked as a crop by the fade, with the
   name, the one-line description and the actions right under it. Five layouts now fit in about
   two screens instead of six, and they can actually be told apart. The whole page stays one tap
   away behind «Aperçu», which already exists and works. */
@media (max-width: 899px) {
  .ui-docthumb { aspect-ratio: auto; height: 190px; }
  .ui-docthumb::after { content: ''; position: absolute; inset-inline: 0; bottom: 0; height: 40px; background: linear-gradient(to bottom, rgba(255, 255, 255, 0), #fff); pointer-events: none; }
}
.ui-tplcard__body { display: flex; flex-direction: column; gap: var(--ui-s2); flex: 1 1 auto; padding: var(--ui-s4); }
.ui-tplcard__name { display: flex; align-items: center; gap: var(--ui-s2); flex-wrap: wrap; font-weight: 600; }
.ui-tplcard__actions { display: flex; gap: var(--ui-s1); flex-wrap: wrap; margin-top: auto; padding-top: var(--ui-s2); }
.ui-tplcard__actions .ui-btn--ghost, .ui-tplcard__actions .ui-btn--secondary { padding-inline: var(--ui-s3); }
/* The team: one row per person — identity, then the controls. */
.ui-seats { display: flex; align-items: center; gap: var(--ui-s3); flex-wrap: wrap; margin-bottom: var(--ui-s4); }
.ui-member { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: var(--ui-s3); align-items: center; padding: var(--ui-s3); border-radius: 16px; }
.ui-member + .ui-member { border-top: 1px solid var(--line); border-radius: 0 0 16px 16px; }
.ui-member__avatar { width: 40px; height: 40px; border-radius: 50%; background: var(--ui-accent-soft); color: var(--ui-accent-deep); display: inline-flex; align-items: center; justify-content: center; font-weight: 700; }
.ui-member__id { min-width: 0; }
/* An invitation row puts the invited ADDRESS here, and an e-mail is one unbreakable token: a
   long one ran off the right edge of a 390 px screen while `.ui-member__meta` beneath it wrapped
   correctly (found 2026-09-06 by a test invitation with a 38-character address). */
.ui-member__name { display: flex; align-items: center; gap: var(--ui-s2); flex-wrap: wrap; font-weight: 600; min-width: 0; overflow-wrap: anywhere; }
.ui-member__meta { font-size: var(--ui-fs-small); color: var(--ui-ink-2); overflow-wrap: anywhere; }
.ui-member__controls { display: flex; gap: var(--ui-s1); align-items: center; flex-wrap: wrap; justify-content: flex-end; }
.ui-member__controls .ui-select { width: auto; min-width: 11rem; background-color: var(--ui-surface); box-shadow: var(--ui-shadow); }
.ui-member__controls .ui-btn--ghost { padding-inline: var(--ui-s3); }
.ui-btn--danger-ghost { color: var(--ui-bad); }
.ui-btn--danger-ghost:hover { background: var(--ui-bad-soft); color: var(--ui-bad); }
/* UX review 2026-09-05 (U4): the design system owned `--ui-bad` but had no SOLID destructive
   button, so "Retirer de l'organisation" and "Supprimer le modèle" wore the violet accent — the
   same colour that means "yes, go ahead" on every other sheet in the app (issue an invoice,
   change plan). Training the user to click the accent through irreversible actions is Nielsen #5
   inverted. A removal now looks like a removal. */
.ui-btn--danger { background: var(--ui-bad); color: #fff; }
.ui-btn--danger:hover { background: var(--ui-bad); filter: brightness(.92); color: #fff; }
.ui-btn--danger:focus-visible { outline: 3px solid var(--ui-bad); outline-offset: 2px; }
/* The plans: cards, the featured one tinted, the current one outlined. */
.ui-plans { display: grid; gap: var(--ui-s4); grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); align-items: stretch; margin-top: var(--ui-s4); }
.ui-plan { position: relative; display: flex; flex-direction: column; gap: var(--ui-s3); min-width: 0; }
.ui-plan--featured { background: var(--ui-accent-soft); }
.ui-plan--current { box-shadow: inset 0 0 0 2px var(--ui-accent); }
.ui-plan__flag { position: absolute; top: var(--ui-s4); inset-inline-end: var(--ui-s4); }
.ui-plan__name { font: 600 var(--ui-fs-title)/1.25 var(--ui-display); letter-spacing: -0.015em; }
.ui-plan__price { display: flex; align-items: baseline; gap: var(--ui-s2); flex-wrap: wrap; }
.ui-plan__amount { font: 700 var(--ui-fs-hero)/1 var(--ui-display); letter-spacing: -0.03em; font-variant-numeric: tabular-nums; }
.ui-plan__period { font-size: var(--ui-fs-small); color: var(--ui-ink-2); }
.ui-plan__features { display: grid; gap: var(--ui-s1); font-size: var(--ui-fs-small); color: var(--ui-ink-2); }
.ui-plan__features li { display: flex; gap: var(--ui-s2); align-items: flex-start; }
.ui-plan__features li svg { flex: none; margin-top: 3px; color: var(--ui-good); }
/* UX review 2026-09-05 (U19): strikethrough means "this was here and has been removed" — a
   price that was crossed out, a task that is done. On a plan card it means "not included in this
   formula", which is a different statement. The greyed text and the ✗ already carry it. */
/* UXUI-20 (review 2026-09-23): ink-3 on the plan card measured 4.24:1 (< 4.5) — the text takes ink-2, the ✕ says «off». */
.ui-plan__features li.is-off { color: var(--ui-ink-2); }
.ui-plan__features li.is-off svg { color: var(--ui-ink-3); }
.ui-plan__cta { margin-top: auto; padding-top: var(--ui-s2); }
.ui-plan__cta .ui-btn { width: 100%; }
.ui-plans__head { flex-wrap: wrap; }
@media (max-width: 899px) { .ui-plans__head .ui-mode { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; border-radius: 22px; } .ui-plans__head .ui-mode__btn { justify-content: center; text-align: center; padding: 0 10px; white-space: normal; } }
.ui-meter__fill--warn { background: var(--ui-warn); }
.ui-meter__fill--bad { background: var(--ui-bad); }
.ui-rowlist { display: grid; gap: var(--ui-s1); }
.ui-facts-band { display: grid; gap: var(--ui-s4) var(--ui-s6); grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); margin-top: var(--ui-s5); }
.ui-facts-band dt { font-size: var(--ui-fs-small); color: var(--ui-ink-2); font-weight: 500; }
.ui-facts-band dd { margin-top: 2px; font-weight: 600; overflow-wrap: anywhere; }
.ui-facts-band dd .ui-small { display: block; font-weight: 400; }
/* The account: the two-step enrolment, and the recovery codes shown once. */
.ui-mfa { display: grid; gap: var(--ui-s4); grid-template-columns: auto minmax(0, 1fr); align-items: start; }
.ui-mfa__qr { width: 176px; height: 176px; padding: 8px; border-radius: 12px; background: #fff; box-shadow: var(--ui-shadow); }
.ui-mfa__steps { display: grid; gap: var(--ui-s3); }
/* The key beside its copy button; the button keeps the 44 px target the row would otherwise eat. */
.ui-mfa__secret-row { display: flex; align-items: center; gap: var(--ui-s2); flex-wrap: wrap; }
.ui-mfa__secret-row .ui-mfa-secret { flex: 1 1 auto; min-width: 0; }
.ui-mfa__open { justify-content: center; }
/* The two legal pages: long prose inside the guest card, so it needs a reading rhythm the rest
   of the door never asks for (2026-09-05). */
.ui-legal { margin-top: var(--ui-s5); display: grid; gap: var(--ui-s3); }
.ui-legal h2 { font: 600 1rem/1.3 var(--ui-display); margin-top: var(--ui-s4); }
.ui-legal h2:first-child { margin-top: 0; }
.ui-legal p, .ui-legal li { color: var(--ui-ink-2); font-size: var(--ui-fs-small); line-height: 1.6; }
.ui-legal ul { margin: 0; padding-inline-start: 1.1rem; display: grid; gap: 4px; }
.ui-legal a { color: var(--ui-accent); text-decoration: underline; text-underline-offset: 3px; }
.ui-mfa-secret { display: inline-block; font-family: var(--ui-font); padding: var(--ui-s2) var(--ui-s3); border-radius: 12px; background: var(--ui-surface-2); font-weight: 600; letter-spacing: .08em; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.ui-recovery { display: grid; gap: var(--ui-s2); grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); margin-top: var(--ui-s3); font-weight: 600; letter-spacing: .06em; font-variant-numeric: tabular-nums; }
.ui-recovery span { padding: var(--ui-s2) var(--ui-s3); border-radius: 12px; background: var(--ui-surface); text-align: center; }
.ui-security { display: grid; gap: var(--ui-s4); }
.ui-security__row { display: flex; align-items: center; gap: var(--ui-s3); flex-wrap: wrap; }
/* The sheet head's close icon keeps its 44 px inside the flex row (measured 29 px on 2026-09-05). */
.ui-sheet__head > .ui-btn--icon { flex: none; }
@media (max-width: 899px) {
  /* UX review 2026-09-05 (U5): the controls drop to their own line here, and with the row's
     own gap the trash button ended up 16 px from the NEXT member's name and 123 px from the one
     it actually acts on — proximity (Wertheimer) pointing the highest-consequence control at the
     wrong person. The control row is pulled tight to its own member, and the members are pushed
     apart, so the grouping says what it does. */
  .ui-member { grid-template-columns: auto minmax(0, 1fr); row-gap: var(--ui-s2); padding-block: var(--ui-s4); }
  /* The 52 px indent that first expressed this grouping pushed the role select and the remove
     button past the 390 px viewport. Proximity is carried by the tight row-gap above and the
     margin between members below, which costs no width. */
  .ui-member__controls { grid-column: 1 / -1; justify-content: flex-start; }
  .ui-member__controls .ui-select { flex: 1 1 auto; min-width: 0; }
  .ui-member + .ui-member { margin-top: var(--ui-s3); }
  .ui-mfa { grid-template-columns: 1fr; }
  .ui-disclosure__value { display: none; }
  .ui-disclosure--section > summary .ui-disclosure__value { display: block; flex-basis: 100%; text-align: start; padding-inline-start: 30px; white-space: normal; font-family: var(--ui-font); letter-spacing: 0; }
  .ui-plan__flag { position: static; align-self: flex-start; }
  .ui-tplcard__actions .ui-btn { flex: 1 1 auto; }
}

/* ---- 11f. Pass 6b: import, récurrentes, en masse, conformité, assistant, back-office ---- */
/* Nothing above this line is changed. Everything here is a structure §7–§13 does not carry. */

/* Import: the kind of data is a pair of choice tiles even on a phone (four one-per-row tiles
   pushed the file control and the one button below the fold). */
.ui-import__kinds { border: 0; padding: 0; margin: var(--ui-s5) 0 0; min-width: 0; }
.ui-import__kinds > legend { padding: 0; margin-bottom: var(--ui-s2); }
.ui-choices--pair { grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr)); }
.ui-import__mapping { margin-top: var(--ui-s5); }

/* The progress of a run (import, en masse): a bar on the band, not a widget. */
.ui-import__meter { height: 8px; border-radius: 999px; background: var(--ui-surface-2); overflow: hidden; margin-top: var(--ui-s4); }
.ui-import__meter-fill { display: block; height: 100%; border-radius: 999px; background: var(--ui-accent); transition: inline-size .4s; }

/* Lines that describe what WILL be billed (a recurrence, a batch): no running total to show,
   so the row is Désignation · Qté · P.U. HT and the TVA rate sits behind the line's «plus». */
.ui-lines--plan .ui-line--head, .ui-lines--plan .ui-line__main { grid-template-columns: 1fr 5rem 9rem 5.5rem; }
.ui-lines--plan .ui-line__more { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 899px) {
  .ui-lines--plan .ui-line__main { grid-template-columns: 1fr 1fr; grid-template-areas: "desig desig" "qty price"; }
  .ui-lines--plan .ui-line__more { grid-template-columns: 1fr; }
}

/* En masse: the clients are a list with its own scroll, so the form below stays reachable
   (UX-MOBILE 4: 30 rows of 23 px pushed every other control off the screen). */
.ui-picker { margin-top: var(--ui-s3); max-height: 17rem; overflow: auto; border-radius: 16px; background: var(--ui-surface-2); padding: var(--ui-s2); }
.ui-picker__row { display: flex; align-items: center; gap: var(--ui-s3); min-height: var(--ui-tap); padding: 0 var(--ui-s3); border-radius: 12px; cursor: pointer; }
.ui-picker__row:hover { background: var(--ui-surface); }
.ui-picker__row input { width: 20px; height: 20px; accent-color: var(--ui-accent); flex: none; }

/* Conformité: the register's folds sit apart, and a one-line row (a security measure, a
   retention limit) keeps the row rhythm without pretending to be a two-column row. */
.ui-register { display: grid; gap: var(--ui-s2); }
.ui-register__line { grid-template-columns: minmax(0, 1fr); align-items: center; }

/* Assistant: a calm column. A turn is a speaker label and its words, set apart by whitespace —
   no card inside a card (D8). The composer docks in the thumb zone on a phone (D7). */
.ui-chatpage { max-width: 46rem; }
.ui-chat { display: grid; gap: var(--ui-s4); }
/* The assistant, rebuilt 2026-09-10 («the chatbot interface is terrible»): the category's
   prototype — your turn in a soft bubble on the right, the answer as text on the canvas, one
   rounded composer with the send button inside it. No speaker labels (they are screen-reader
   text now), no card around the empty state. */
.ui-chat__thread { display: grid; gap: var(--ui-s5); }
.ui-chat__hello { display: grid; justify-items: start; gap: var(--ui-s3); padding-top: var(--ui-s4); }
.ui-chat__hello-title { font-size: var(--ui-fs-head); font-weight: 600; letter-spacing: -.01em; line-height: 1.25; }
.ui-chat__starters { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--ui-s2); width: 100%; margin-top: var(--ui-s2); }
.ui-chat__starter { display: flex; align-items: center; justify-content: space-between; gap: var(--ui-s3); min-height: 56px; padding: var(--ui-s3) var(--ui-s4); border: 1.5px solid transparent; border-radius: 16px; background: var(--ui-surface); box-shadow: var(--ui-shadow); color: var(--ui-ink); font: inherit; font-weight: 500; text-align: start; cursor: pointer; transition: border-color .15s, box-shadow .15s; }
.ui-chat__starter:hover { border-color: var(--ui-accent-soft); box-shadow: var(--ui-shadow-lift); }
.ui-chat__starter:focus-visible { outline: 2px solid var(--ui-accent); outline-offset: 2px; }
.ui-chat__starter svg { flex: none; color: var(--ui-accent); }
.ui-chat__mark { flex: none; display: inline-grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; background: var(--ui-hero-grad); color: var(--ui-on-accent); font-weight: 700; font-size: 14px; line-height: 1; }
.ui-chat__mark--big { width: 44px; height: 44px; font-size: 20px; }
.ui-chat__mark--warn { background: var(--ui-warn-soft); color: var(--ui-warn); }
.ui-msg { display: flex; align-items: flex-start; gap: var(--ui-s3); }
.ui-msg--you { justify-content: flex-end; }
.ui-msg__bubble { max-width: min(80%, 36rem); padding: 10px 16px; border-radius: 18px 18px 4px 18px; background: var(--ui-accent-soft); color: var(--ui-ink); white-space: pre-wrap; overflow-wrap: anywhere; }
.ui-msg__text { flex: 1; min-width: 0; padding-top: 3px; overflow-wrap: anywhere; }
.ui-msg__text.ui-notice { padding-top: var(--ui-s3); }
.ui-chat__dock { position: sticky; bottom: var(--ui-s4); z-index: 20; display: grid; gap: var(--ui-s2); }
.ui-chat__suggest { display: flex; gap: var(--ui-s2); overflow-x: auto; scrollbar-width: none; }
.ui-chat__suggest::-webkit-scrollbar { display: none; }
.ui-chat__suggest .ui-filter { flex: none; background: var(--ui-surface); }
.ui-chat__composer { display: grid; gap: var(--ui-s1); }
.ui-chat__box { display: flex; align-items: flex-end; gap: var(--ui-s2); padding: 6px 6px 6px 18px; border: 1.5px solid transparent; border-radius: 26px; background: var(--ui-surface); box-shadow: var(--ui-shadow-pill); transition: border-color .15s, box-shadow .15s; }
.ui-chat__box:focus-within { border-color: var(--ui-accent); box-shadow: 0 0 0 3px var(--ui-accent-soft), var(--ui-shadow-pill); }
.ui-chat__input { flex: 1; min-width: 0; max-height: 168px; padding: 10px 0; border: 0; outline: 0; background: transparent; color: var(--ui-ink); font: inherit; line-height: 1.5; resize: none; }
.ui-chat__input::placeholder { color: var(--ui-ink-3); }
.ui-chat__sendbtn { flex: none; display: grid; place-items: center; width: 44px; height: 44px; border: 0; border-radius: 50%; background: var(--ui-accent); color: var(--ui-on-accent); cursor: pointer; transition: background .15s; }
.ui-chat__sendbtn:hover { background: var(--ui-accent-deep); }
.ui-chat__sendbtn:focus-visible { outline: 2px solid var(--ui-accent); outline-offset: 2px; }
.ui-chat__sendbtn:disabled { opacity: .5; cursor: default; }
.ui-chat__sendbtn .thinking span { background: var(--ui-on-accent); }
.ui-chat__meta { padding-inline: 18px; color: var(--ui-ink-3); font-size: var(--ui-fs-micro); }
.ui-chat__privacy summary { display: flex; align-items: center; min-height: var(--ui-tap); color: var(--ui-ink-2); font-size: var(--ui-fs-small); cursor: pointer; }
.ui-chat__privacy-body { display: grid; gap: var(--ui-s3); padding-bottom: var(--ui-s3); }
.ui-chat__privacy-body .ui-btn { justify-self: start; }
.ui-consent__facts dd { overflow-wrap: anywhere; }
.ui-consent__points { display: grid; gap: var(--ui-s2); margin: var(--ui-s4) 0; padding-inline-start: var(--ui-s5); list-style: disc; color: var(--ui-ink-2); }
.ui-consent__actions { margin-top: 0; }
@media (max-width: 599px) {
  .ui-chat__starters { grid-template-columns: 1fr; }
}
/* The consent decision on a phone: it was 1.6 screens below the fold (audit 2026-09-09, C6).
   The row now rides above the tab bar (64 px + its 12 px inset + a gap) while the text scrolls
   under it, and stops at the end of the card. */
@media (max-width: 899px) {
  .ui-consent__actions {
    position: sticky; bottom: calc(88px + env(safe-area-inset-bottom, 0px)); z-index: 5;
    margin-inline: calc(-1 * var(--ui-s2)); padding: var(--ui-s2);
    border-radius: var(--ui-r-pill); background: var(--ui-surface); box-shadow: var(--ui-shadow-pill);
  }
  .ui-consent__points { font-size: var(--ui-fs-small); }
}
/* The answer arrives as markdown with its HTML stripped: give it the layer's prose rhythm. */
.answer > * + * { margin-top: var(--ui-s3); }
.answer ul, .answer ol { padding-inline-start: var(--ui-s5); list-style: disc; }
.answer ol { list-style: decimal; }
.answer li + li { margin-top: var(--ui-s1); }
.answer strong { font-weight: 600; }
.answer code { font-variant-numeric: tabular-nums; background: var(--ui-surface-2); border-radius: 6px; padding: 1px 6px; }
.answer table { width: 100%; border-collapse: collapse; }
.answer td { font-variant-numeric: tabular-nums; }
.answer th, .answer td { text-align: start; padding: var(--ui-s1) var(--ui-s3); border-top: 1px solid var(--line); }
/* Waiting for the first fragment. */
.thinking { display: inline-flex; gap: 4px; align-items: center; height: 1.5em; }
.thinking span { width: 6px; height: 6px; border-radius: 50%; background: var(--ui-ink-3); animation: ui-think 1.2s infinite ease-in-out; }
.thinking span:nth-child(2) { animation-delay: .15s; }
.thinking span:nth-child(3) { animation-delay: .3s; }
@keyframes ui-think { 0%, 80%, 100% { opacity: .25; transform: none; } 40% { opacity: 1; transform: translateY(-3px); } }

/* An import's one next step is the end of a job (D5): on a phone it docks in the thumb zone,
   above the tab bar, like the editor's save pill. Measured 2026-09-06: the inline row put it at
   y=557 of 844, six pixels above the bottom third. */
@media (max-width: 899px) {
  .ui-declare__actions--dock {
    position: fixed; inset-inline: var(--ui-s3); bottom: calc(64px + 2 * var(--ui-s3)); z-index: 40;
    margin: 0; padding: var(--ui-s2) var(--ui-s2) var(--ui-s2) var(--ui-s4);
    border-radius: var(--ui-r-pill); background: rgba(255, 255, 255, .97);
    -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px); box-shadow: var(--ui-shadow-pill);
    flex-wrap: nowrap;
  }
  .ui-declare__actions--dock .ui-btn { flex: 0 1 auto; min-width: 0; padding-inline: var(--ui-s4); }
  .ui-declare__actions--dock .ui-btn--primary { flex: 1 1 auto; }
  .ui-import__screen { padding-bottom: 84px; }
}

/* Back-office rows carry long staff verbs («Ouvrir le compte», «Fermer les sessions»), and a
   .ui-btn never wraps its own words: on a phone the controls take their own line under the
   identity instead of pushing the row past the viewport. Measured 2026-09-06 at 390:
   /admin scrolled sideways by 176 px with 20 «Ouvrir le compte» outside it, /admin/utilisateurs
   by 89 px with 17 «Fermer les sessions». */
@media (max-width: 899px) {
  .ui-row-item--stack { grid-template-columns: minmax(0, 1fr); row-gap: var(--ui-s2); }
  .ui-row-item--stack .ui-row-item__actions { grid-column: 1 / -1; flex-wrap: wrap; min-width: 0; }
  .ui-row-item--stack .ui-row-item__actions .ui-btn { flex: 0 1 auto; min-width: 0; max-width: 100%; white-space: normal; }
}

/* Three staff verbs per row: compact them so two fit a phone line instead of stacking three
   deep (measured /admin at 390: 5 741 px of page for 20 organisations). */
@media (max-width: 899px) {
  .ui-row-item--stack .ui-row-item__actions .ui-btn { padding-inline: var(--ui-s3); font-size: var(--ui-fs-small); }
}
/* An import issue is keyed by a line NUMBER; the list archetype's 14rem first column is for a
   name. */
.ui-import__issues .ui-table td:first-child, .ui-import__issues .ui-table th:first-child { min-width: 5rem; }

/* Back-office: a platform setting is a short figure, not a full-width field. */
.ui-admin__days { max-width: 8rem; }

/* ---- 11g. The help drawer, the pager and the toast body (2026-09-06) ---------------
   These three were markup on the new layer with no rule in the only stylesheet the new
   layer loads, so they rendered raw. StylesheetTest missed them because ds/*.css was in
   its `next` bucket; that is corrected in the same change.                            */

/* The help panel is the shell's sheet, docked to the side on a desk (a reference panel is
   read beside the work, not over it) and a bottom sheet on the phone, which .ui-sheet's own
   media query already does. */
@media (min-width: 900px) {
  /* .ui-sheet.ui-sheet--drawer, not .ui-sheet--drawer: section 13's own min-width rule for
     .ui-sheet is declared LATER in this file, so at equal specificity it would win and the
     panel would come out as the 560px centred modal (measured 2026-09-06). */
  .ui-sheet.ui-sheet--drawer {
    inset: 0 0 0 auto; transform: none;
    width: min(460px, calc(100vw - 48px)); max-height: 100dvh;
    border-radius: 24px 0 0 24px;
    animation: ui-drawer-in .28s cubic-bezier(.2, .7, .2, 1) both;
  }
  @keyframes ui-drawer-in { from { opacity: 0; transform: translateX(24px); } to { opacity: 1; transform: none; } }
}
.ui-sheet--drawer .ui-sheet__body { padding-bottom: var(--ui-s5); }
.ui-sheet--drawer .ui-sheet__body > .ui-section:first-child { margin-top: var(--ui-s4); }
.ui-sheet--drawer .ui-disclosure + .ui-disclosure { margin-top: var(--ui-s1); }

/* The steps are a numbered list: position on a common scale is the strongest ordinal cue,
   and the number is the thing being followed. */
.ui-guide__steps { margin: var(--ui-s3) 0 0; padding: 0; list-style: none; counter-reset: ui-guide-step; display: grid; gap: var(--ui-s4); }
.ui-guide__steps > li { counter-increment: ui-guide-step; position: relative; padding-inline-start: 34px; }
.ui-guide__steps > li::before {
  content: counter(ui-guide-step); position: absolute; inset-inline-start: 0; top: 1px;
  width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; background: var(--ui-accent-soft); color: var(--ui-accent-deep);
  font-size: var(--ui-fs-small); font-weight: 700;
}
.ui-guide__foot { flex-direction: column; align-items: stretch; gap: var(--ui-s2); border-top: 1px solid var(--line); }
.ui-guide__foot .ui-btn--primary { max-width: none; }
.ui-guide__hint { text-align: center; }

/* One control per breakpoint, not three squeezed into 390 px. `ui-phone-hide` drops a
   secondary out of the action bar on a phone; `ui-desk-hide` is its counterpart in the form. */
.ui-desk-hide { display: none; }
.ui-generate { margin-top: var(--ui-s5); display: flex; gap: var(--ui-s3); flex-wrap: wrap; }
@media (max-width: 899px) { .ui-generate .ui-btn { flex: 1 1 100%; } }
@media (max-width: 899px) {
  .ui-phone-hide { display: none !important; }
  .ui-desk-hide { display: inline-flex; }
}

/* Back-office → Paiements: a transfer waiting to be confirmed. One row, one decision, and the
   two buttons stay beside the amount rather than wrapping under it on a laptop. */
.ui-admin-order { display: flex; justify-content: space-between; align-items: center; gap: var(--ui-s4); flex-wrap: wrap; border-inline-start: 3px solid var(--ui-accent); }
.ui-admin-order__actions { flex: none; }
@media (max-width: 599px) { .ui-admin-order__actions { width: 100%; } .ui-admin-order__actions .ui-btn { flex: 1 1 auto; } }

/* «Ailleurs dans l'application» — the cross-app pulse under the day.
   Deliberately NOT tiles and NOT a chart grid: the count leads (position on a common scale is
   what the eye compares), the label follows, and the whole row is the target so Fitts is kind
   on a phone. Grouped by whitespace and a hairline, not by boxes (D8). */
.ui-pulse { margin-top: var(--ui-s6); }
.ui-pulse__title { display: block; margin-bottom: var(--ui-s2); }
.ui-pulse__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr)); }
.ui-pulse__item {
  display: flex; align-items: center; gap: var(--ui-s3);
  min-height: var(--ui-tap); padding: var(--ui-s2) var(--ui-s3);
  border-radius: 12px; color: var(--ui-ink-2); transition: background .15s, color .15s;
}
a.ui-pulse__item:hover { background: rgba(23, 20, 43, .05); color: var(--ui-ink); }
/* 1rem, not 1.125: a fifth distinct type size on the Accueil breaches D1's ceiling, and the
   count reads perfectly well at the size the rest of the row uses. */
.ui-pulse__count { font: 700 1rem/1 var(--ui-display); font-variant-numeric: tabular-nums; color: var(--ui-ink); min-width: 2.25rem; }
.ui-pulse__label { flex: 1 1 auto; font-size: var(--ui-fs-small); }
.ui-pulse__go { flex: none; color: var(--ui-ink-3); }
.ui-pulse__item--warn .ui-pulse__count { color: var(--ui-warn); }
@media (max-width: 899px) { .ui-pulse__list { grid-template-columns: 1fr; } }

/* The demo-set offer on the first-run Accueil: a sentence, not a button — it must be findable
   and must not compete with «Créer ma première facture». */
.ui-home__demo { margin-top: var(--ui-s4); color: var(--ui-ink-2); }
/* inline-flex + min-height, not a bare inline link: measured 2026-09-06 at 207×16, and a
   16 px tall target fails the 44 px floor (C2 / NN-g touch targets) on the surface where most
   of these customers will meet it. */
.ui-home__demo a { display: inline-flex; align-items: center; min-height: var(--ui-tap);
  color: var(--ui-accent); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }

/* «Vos chiffres»: open on a desk, a disclosure on a phone (see the blade for why). */
.ui-figures--foldable .ui-card__head { align-items: center; }
/* --ui-tap, not 40px: it is a real control and the 44 px floor is not waived for small text. */
.ui-figures__toggle { min-height: var(--ui-tap); padding: 0 var(--ui-s3); font-size: var(--ui-fs-small); gap: var(--ui-s1); }
.ui-figures__toggle svg { transition: transform .2s; }
.ui-figures__toggle svg.is-open { transform: rotate(180deg); }
.ui-figures__body { margin-top: var(--ui-s2); }

/* The tools row on the Factures list: quieter than a segment (these are not document types)
   and louder than a footnote (they were reachable from nowhere at all before 2026-09-06). */
.ui-tools { display: flex; flex-wrap: wrap; gap: var(--ui-s2); }
/* On the phone these two would take two full rows between the search and the status filters
   — prime space for the least-used control on the screen. There they live behind «Plus»,
   which is what a phone's overflow menu is for (the shell publishes them via Navigation's
   `sub`). Measured at 390 before this rule: filters pushed to y 430. */
@media (max-width: 899px) { .ui-tools { display: none; } }
.ui-tool {
  display: inline-flex; align-items: center; gap: var(--ui-s2);
  min-height: var(--ui-tap); padding: 0 var(--ui-s4);
  border-radius: var(--ui-r-pill); background: var(--ui-surface);
  color: var(--ui-ink-2); font-weight: 500; font-size: var(--ui-fs-small);
  box-shadow: inset 0 0 0 1.5px var(--ui-stroke); transition: background .15s, color .15s;
}
.ui-tool:hover { background: var(--ui-surface-2); color: var(--ui-ink); }
.ui-tool svg { color: var(--ui-accent); flex: none; }

/* The pager. resources/views/vendor/pagination/mzyan.blade.php is production-authoritative
   (standing rule since pass 3) and carries `btn btn--secondary`, a name ui.css does not
   define — so Précédent/Suivant were unstyled on every rebuilt list. Styled by scope here
   rather than by resurrecting the legacy `.btn` vocabulary, and rather than editing a file
   the deploy script refuses to carry. */
.pagination .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--ui-s2);
  min-height: var(--ui-tap); padding: 0 var(--ui-s5); border-radius: var(--ui-r-pill);
  font-weight: 600; white-space: nowrap; background: var(--ui-surface); color: var(--ui-ink);
  box-shadow: inset 0 0 0 1.5px var(--ui-stroke); transition: background .15s;
}
.pagination .btn:hover:not([disabled]):not([aria-disabled="true"]) { background: var(--ui-surface-2); }
.pagination .btn[disabled], .pagination .btn[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }

/* The toast's own text node — .toast and .toast-close were defined, its body was not. */
.toast-text { flex: 1 1 auto; min-width: 0; }

/* ---- 12. Action bar: a floating pill --------------------------------------------- */
.ui-actionbar { display: flex; gap: var(--ui-s3); align-items: center; }
.ui-actionbar--sticky { position: sticky; bottom: var(--ui-s4); z-index: 30; margin-top: var(--ui-s5); padding: var(--ui-s2) var(--ui-s2) var(--ui-s2) var(--ui-s5); border-radius: var(--ui-r-pill); background: rgba(255, 255, 255, .97); -webkit-backdrop-filter: blur(18px) saturate(1.4); backdrop-filter: blur(18px) saturate(1.4); box-shadow: var(--ui-shadow-pill); }
.ui-actionbar__total { display: flex; flex-direction: column; margin-inline-end: auto; line-height: 1.2; }
.ui-actionbar__total .ui-strong { font: 700 1.25rem/1.2 var(--ui-display); font-variant-numeric: tabular-nums; }
.ui-actionbar--phone { display: none; }
/* FACT-CPA-BIZ-1 (review 2026-09-23): beside the live preview the editor card is 460–546 px wide; the guided step 3
   bar (total · Retour · Enregistrer le brouillon · Émettre) needs ~630, and «Émettre» hung outside the card, over the
   preview. The card is a size container on a desktop only (the phone's bar is position: fixed — a container would
   capture it); when it is narrow the buttons that do not fit wrap to a second row, right-aligned, inside the bar. */
@media (min-width: 900px) { .ui-editor__form { container: ui-editor-form / inline-size; } }
@container ui-editor-form (max-width: 700px) {
  .ui-actionbar--sticky { flex-wrap: wrap; justify-content: flex-end; row-gap: var(--ui-s2); border-radius: 24px; }
}
/* «Ajouter une ligne» never under the bar (FACT-CLICK-3, 2026-09-23). At 1440×900 the editor opened with the
   add-line row exactly where the sticky bar sits (807–851 px in a 900 px window): a click on it hit the bar. A
   spacer at the end of the form cannot help — the row is covered on FIRST view, before any scroll. The row is
   sticky itself, just above the bar (whose height the bar publishes as --ui-actionbar-h; 4.5rem until it has),
   and only inside the lines section, so it returns to its place once the section ends above the bar.
   Desktop only: on a phone the bar is fixed, the row is below the fold on first view (reached by scrolling, measured
   2026-09-23), and a second sticky strip would sit on the line being typed on a 844 px screen. */
@media (min-width: 900px) {
  /* One line (button + catalogue), so the strip costs one row of the lines above it, not two. */
  .ui-editor__addrow { position: sticky; bottom: calc(var(--ui-s4) + var(--ui-actionbar-h, 4.5rem) + var(--ui-s2)); z-index: 29; background: var(--ui-surface); padding-block: var(--ui-s2); border-radius: var(--ui-r-sm); flex-wrap: nowrap; }
  .ui-editor__addrow > .ui-btn { flex: none; }
  .ui-editor__addrow > .ui-select { flex: 1 1 auto; }
  /* …which makes TWO strips docked at the bottom, and a field focused by Tab or by the editor landed under
     them (FACT-ENGAGEMENT M3, FACT-CPA F3, FACT-OWNER #2 — WCAG 2.2 SC 2.4.11). Focus and scrollIntoView keep
     clear of both: the bar's and the strip's published heights plus their gaps. */
  html:has(.ui-editor__addrow) { scroll-padding-bottom: calc(var(--ui-s4) + var(--ui-actionbar-h, 4.5rem) + var(--ui-s2) + var(--ui-addrow-h, 3.75rem) + var(--ui-s3)); } }
@media (max-width: 899px) {
  .ui-actionbar--sticky, .ui-actionbar--phone { position: fixed; inset-inline: var(--ui-s3); bottom: max(var(--ui-s3), env(safe-area-inset-bottom)); display: flex; z-index: 40; margin: 0; padding: var(--ui-s2); border-radius: var(--ui-r-pill); background: rgba(255, 255, 255, .97); -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px); box-shadow: var(--ui-shadow-pill); }
  .ui-actionbar--sticky { padding-inline-start: var(--ui-s4); }
  .ui-actionbar--sticky .ui-btn { flex: 0 1 auto; padding-inline: var(--ui-s4); }
  .ui-actionbar--sticky .ui-btn--primary { flex: 1 1 auto; }
  /* Three things in the pill (Retour · total · save): the total shrinks first, the ghost keeps its word. */
  /* AN AMOUNT IS NEVER CUT. This was `overflow: hidden` with no ellipsis and the sticky bar
     sliced «14 400,00 MAD» 3 px short, so the currency read as «MAC» against the primary
     button (check-phone.mjs, /documents/{id} at 390, 2026-09-15). A truncated number in an
     invoicing app is not a cosmetic defect: it is a number that says something else. The
     amount keeps its intrinsic width; if the row still runs out it is the BUTTON's label that
     ellipsises, which is a designed truncation with an affordance. 4 px of the bar's own
     start padding pays for the 3 px that were missing. */
  .ui-actionbar--sticky .ui-actionbar__total { flex: 0 0 auto; min-width: 0; white-space: nowrap; }
  /* A SENTENCE in the total slot (/admin/parametres' spend line) wraps and shrinks: kept on one line it pushed
     «Enregistrer» 134 px off a 390 px screen (R1-FACT-SCREENS, 2026-09-24). Amounts keep the rule above. */
  .ui-actionbar--sticky .ui-actionbar__total--text { flex: 1 1 0; white-space: normal; overflow-wrap: anywhere; }
  .ui-actionbar--sticky { padding-inline-start: var(--ui-s3); }
  .ui-actionbar--sticky .ui-btn { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .ui-actionbar--sticky .ui-actionbar__total .ui-strong { font-size: 1rem; }
  .ui-actionbar--sticky .ui-btn--ghost { padding-inline: var(--ui-s3); }
  .ui-actionbar--sticky .ui-btn--primary { min-width: 0; }
  .ui-actionbar--phone .ui-btn { flex: 1 1 auto; }
  /* The first-run home keeps the dock: its thumb-zone action sits just above it. */
  .ui-actionbar--phone { bottom: calc(var(--ui-thumb) + 12px + 2 * var(--ui-s3)); }
  .ui-page--has-actionbar { padding-bottom: 96px; }
}

/* RECLICK-PAIE-4 (2026-09-23): a docked action bar and the tab bar shared the same fixed bottom and z-index, so on a
   screen that keeps the tab bar (Paie «Nouveau / Modifier le salarié», CRM prospect, Assistant settings, the import,
   the first-run home) the tab bar lay on the save button — a tap on «Enregistrer» opened «Périodes». Pre-existing in
   production (22/09), not a night regression. The editors hide the tab bar (tabbar=false) and keep their bar at the
   bottom; wherever the tab bar is present, every docked bar sits ABOVE it: the tab bar's own bottom (safe area
   included) + its height + one gap. The chat hides the tab bar itself, so it is excluded. */
@media (max-width: 899px) {
  :root { --ui-tabbar-clear: calc(max(var(--ui-s3), env(safe-area-inset-bottom)) + var(--ui-thumb) + 12px + var(--ui-s2)); }
  body:has(.ui-tabbar):not(:has(.ui-chat__composer)) :is(.ui-actionbar--sticky, .ui-actionbar--phone, .ui-declare__actions--dock) { bottom: var(--ui-tabbar-clear); }
  /* Two docked strips now: the last field and a focused input scroll clear of both. */
  body:has(.ui-tabbar):has(.ui-actionbar--sticky) .ui-page--has-actionbar { padding-bottom: calc(var(--ui-thumb) + 12px + 96px); }
  html:has(.ui-tabbar):has(:is(.ui-actionbar--sticky, .ui-actionbar--phone, .ui-declare__actions--dock)) { scroll-padding-bottom: 200px; }
}

/* ---- 13. Sheets, scrim, menus ----------------------------------------------------- */
.ui-scrim { position: fixed; inset: 0; z-index: 50; background: rgba(23, 20, 43, .4); -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px); }
.ui-sheet { position: fixed; z-index: 60; background: var(--ui-surface); box-shadow: var(--ui-shadow-lift); display: flex; flex-direction: column; max-height: min(92dvh, 760px); animation: ui-sheet-in .28s cubic-bezier(.2,.7,.2,1) both; }
@media (min-width: 900px) { .ui-sheet { inset: 50% auto auto 50%; transform: translate(-50%, -50%); width: min(560px, calc(100vw - 48px)); border-radius: 24px; } @keyframes ui-sheet-in { from { opacity: 0; transform: translate(-50%, -46%); } to { opacity: 1; transform: translate(-50%, -50%); } } }
@media (max-width: 899px) { .ui-sheet { inset: auto 0 0 0; border-radius: 24px 24px 0 0; padding-bottom: env(safe-area-inset-bottom); } @keyframes ui-sheet-in { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } } }
.ui-sheet__head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--ui-s3); padding: var(--ui-s5) var(--ui-s5) var(--ui-s3); }
.ui-sheet__body { padding: var(--ui-s2) var(--ui-s5); overflow: auto; }
.ui-sheet__foot { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--ui-s3); padding: var(--ui-s4) var(--ui-s5) var(--ui-s5); }
.ui-sheet__foot .ui-btn--primary { flex: 1 1 auto; max-width: 20rem; }
/* UXUI-7 (review 2026-09-23): two long nowrap labels («Garder le document» + «Annuler le document») did not fit a
   phone's row and the row overflowed to the LEFT — the secondary started at x −54, off the screen. On a phone the
   buttons now share the row when they fit and stack full-width when they do not; never off-screen. */
@media (max-width: 899px) {
  .ui-sheet__foot > .ui-btn { flex: 1 1 auto; min-width: 0; }
  .ui-sheet__foot .ui-btn--primary { max-width: none; }
}

/* ---- 14. Shared components carried by the layer ---------------------------------- */
.toast-region { position: fixed; z-index: 70; inset-inline-end: var(--ui-s4); top: calc(var(--ui-pill-h) + var(--ui-s5)); display: grid; gap: var(--ui-s2); max-width: 22rem; }
.toast { display: flex; align-items: flex-start; gap: var(--ui-s3); padding: var(--ui-s3) var(--ui-s4); border-radius: 16px; background: var(--ui-ink); color: #fff; box-shadow: var(--ui-shadow-lift); font-size: var(--ui-fs-small); animation: ui-rise .3s both; }
.toast-success { background: var(--ui-good); }
.toast-error, .toast-danger { background: var(--ui-bad); }
/* The ✕ was a 28×28 target (FACT-ENGAGEMENT m1, 2026-09-24): 44 px now, drawn over the toast's
   own padding (negative margins) so a one-line toast does not grow. */
.toast-close { flex: none; display: inline-flex; align-items: center; justify-content: center; margin-block: -10px; margin-inline: auto -12px; border-radius: 12px; color: inherit; opacity: .8; min-width: var(--ui-tap, 44px); min-height: var(--ui-tap, 44px); }
/* A toast may carry the ONE next step (paie, 2026-09-24: « Septembre est à recalculer » → Voir le mois). */
.toast-action { flex: none; align-self: center; min-height: var(--ui-tap, 44px); display: inline-flex; align-items: center; padding: 0 var(--ui-s3); border-radius: 999px; background: #fff; color: var(--ui-accent-deep, #2d1a8f); font-weight: 600; text-decoration: none; }
@media (max-width: 899px) { .toast-region { inset-inline: var(--ui-s3); top: 68px; max-width: none; } }
/* The welcome toast landed exactly on the «Première visite ? / Commencer» band, hiding the
   offer to be guided during the five seconds a new user is deciding whether to accept it
   (first-run review 2026-09-07). The launcher already marks the root when the band is up. */
.has-guide-hint .toast-region { top: calc(var(--ui-pill-h) + var(--ui-s5) + 96px); }
@media (max-width: 899px) { .has-guide-hint .toast-region { top: 172px; } }
/* Inside the app shell (both apps) a toast never lies over a control or the page title
   (FACT-ENGAGEMENT m1, PAIE-ENGAGEMENT M4, 2026-09-24: at 1440 it hid «Corriger maintenant», at
   390 the search, bell and avatar, or the title). It TAKES ROOM instead of covering: the toast
   component measures itself into --ui-toast-h and places itself (components/toasts.blade.php).
   Desk: in the sidebar, just above the account — the destinations' scrolling list gives up that
   height, so nothing visible moves and nothing is under the toast. Phone: under the top pill, and
   the sticky top row grows by the same height, so the page starts below the toast. The rules
   above stay for the screens without the shell (sign-in, sign-up). Clicks pass through the toast
   body wherever it may still lie over the page (a phone scrolled down), not through ✕ or its button. */
:root { --ui-toast-h: 0px; }
.ui-shell ~ .toast-region { pointer-events: none; }
.ui-shell ~ .toast-region .toast-close, .ui-shell ~ .toast-region .toast-action { pointer-events: auto; }
@media (min-width: 900px) {
  .ui-side__scroll { margin-bottom: var(--ui-toast-h); }
  .ui-shell ~ .toast-region .toast { flex-wrap: wrap; }
  .ui-shell ~ .toast-region .toast-text { flex: 1 1 calc(100% - 56px); }   /* the text and ✕ fill line one; a button goes under */
  .ui-shell ~ .toast-region .toast-action { order: 2; }
  /* The account menu opens upward, onto the toast's place: the toast steps aside while it is open. */
  .ui-shell:has(.ui-side__account[aria-expanded="true"]) ~ .toast-region { visibility: hidden; }
}
@media (max-width: 899px) { .ui-top { padding-bottom: var(--ui-toast-h); } }
/* ---- The guided tour ------------------------------------------------------------------
   `.tour-spot` and `.tour-card` are created by `public/assets/js/app.js`, so NO blade names
   them — which is why StylesheetTest, which reads blades, cannot see them, and why their only
   rules lived in `ds/parts.css` + `app.css`, neither of which `layouts/ui.blade.php` loads.
   Measured on the new layer 2026-09-07: the card computed transparent with no border, padding
   or shadow and `z-index: auto`, the spotlight sat in the document flow at y=1586, and at 390
   tapping «Commencer» produced no visible change at all. Same failure as the help drawer had
   on 2026-09-06, from the same cause.

   Ported here in the ui- vocabulary so the tour works on the layer the product actually uses;
   the legacy copies go when pass 7 deletes ds/*.
   ---------------------------------------------------------------------------------------- */
.tour-spot {
  position: absolute; z-index: 70;
  border-radius: 12px;
  /* The scrim IS the spotlight: one enormous spread painted around the cut-out, so the rest of
     the screen dims without a second element to keep in sync. */
  box-shadow: 0 0 0 100vmax rgba(23, 20, 43, .55), 0 0 0 3px var(--ui-accent);
  pointer-events: none;
}
.tour-card {
  position: absolute; z-index: 71;
  inline-size: 22rem; max-inline-size: calc(100vw - var(--ui-s5));
  display: flex; flex-direction: column; gap: var(--ui-s3);
  padding: var(--ui-s4);
  background: var(--ui-surface);
  border-radius: 18px;
  box-shadow: var(--ui-shadow-pill), 0 24px 48px -20px rgba(23, 20, 43, .35);
}
.tour-count { font-size: var(--ui-fs-small); letter-spacing: .08em; text-transform: uppercase; color: var(--ui-ink-3); font-weight: 600; }
.tour-title { font-size: var(--ui-fs-body); font-weight: 600; color: var(--ui-ink); }
.tour-text  { font-size: var(--ui-fs-small); color: var(--ui-ink-2); line-height: var(--ui-lh); }
.tour-actions { display: flex; align-items: center; gap: var(--ui-s2); flex-wrap: wrap; }
.tour-dots { display: flex; gap: 4px; margin-inline-end: auto; }
.tour-dot { inline-size: 6px; block-size: 6px; border-radius: 999px; background: var(--ui-surface-2); }
.tour-dot.is-active { background: var(--ui-accent); }
.tour-skip { align-self: flex-start; }

/* On a phone there is nowhere to float a card, so it docks above the tab bar — where the
   thumb already is. */
@media (max-width: 899px) {
  .tour-card {
    position: fixed;
    inset-inline: var(--ui-s3);
    inset-block-end: calc(72px + var(--ui-s3) + env(safe-area-inset-bottom));
    inline-size: auto; max-inline-size: none;
  }
}

/* The one-time tour offer.
   Two vocabularies on purpose. The markup moved onto ui- classes on 2026-09-06, but THIS FILE
   is in a deploy manifest that does NOT carry Guide/Views/launcher.blade.php — the stylesheet
   reaches production ahead of the markup it was written for. Dropping the old selectors would
   leave the strip unstyled on the live site, which is the exact defect this session was fixing
   elsewhere. They come out when the launcher ships. */
.guide-hint, .ui-guide-hint { display: flex; justify-content: space-between; gap: var(--ui-s4); align-items: center; flex-wrap: wrap; max-width: var(--ui-content); margin: 0 auto var(--ui-s5); padding: var(--ui-s4) var(--ui-s5); border-radius: var(--ui-r); background: var(--ui-surface-2); font-size: var(--ui-fs-small); }
.guide-hint strong, .ui-guide-hint strong { font-weight: 600; }
.guide-hint svg, .ui-guide-hint svg { display: none; }
.guide-hint .row, .ui-guide-hint .ui-row { display: flex; gap: var(--ui-s3); align-items: flex-start; flex-wrap: nowrap; }
.guide-hint .stack-sm > * + * { margin-top: 2px; }
.guide-hint .muted, .guide-hint .guide-hint__text, .ui-guide-hint .ui-guide-hint__text { color: var(--ui-ink-2); font-size: var(--ui-fs-small); margin-top: 2px; }
.guide-hint .btn, .ui-guide-hint .ui-btn { display: inline-flex; align-items: center; gap: var(--ui-s2); min-height: var(--ui-tap); padding: 0 var(--ui-s4); border-radius: var(--ui-r-pill); font-weight: 600; font-size: var(--ui-fs-small); }
.guide-hint .btn-primary { color: var(--ui-accent); }
.guide-hint .btn-ghost { color: var(--ui-ink-2); }

/* THE `@media` OPENER WAS MISSING HERE. The four rules below are phone rules — the comment
   says so — but with no `@media` around them they applied at EVERY width, so
   `.ui-guide-hint__text { display: none }` hid the guide hint's explanation on the desktop
   too, and the stray `}` that followed left the stylesheet brace-unbalanced (1074 open, 1075
   close). Found 2026-09-09 by counting braces after a rule silently failed to apply. */
@media (max-width: 899px) {
  /* One compact line on a phone: as a four-row block at the top of the page it pushed the
     user's own figure to y 370 of 844 (measured 2026-09-06). */
  .guide-hint, .ui-guide-hint { gap: var(--ui-s2); padding: var(--ui-s3) var(--ui-s4); align-items: center; }
  .guide-hint .guide-hint__text, .ui-guide-hint .ui-guide-hint__text { display: none; }
  .guide-hint .row:last-child, .ui-guide-hint .ui-row:last-child { margin-inline-start: auto; }
  .guide-hint .btn, .ui-guide-hint .ui-btn { flex: 0 0 auto; padding: 0 var(--ui-s3); }
}
.guide-scrim { position: fixed; inset: 0; background: rgba(23, 20, 43, .45); z-index: 50; }

/* ---- 15. Print ---------------------------------------------------------------------- */
@media print {
  body { background: #fff; }
  .ui-shell::before, .ui-top, .ui-side, .ui-tabbar, .ui-actionbar, .toast-region, .guide-hint, .ui-guide-hint { display: none !important; }
  .ui-main { padding: 0; }
  .ui-card { box-shadow: none; }
  .ui-page { padding: 0; max-width: none; }
}

/* ---- 16. 2026-09-08 editor: catalogue type-ahead, dates, avoir warning ---- */
/* The client picker with a visible «+ Nouveau client» beside the input (expert-comptable,
   point 1). The list hangs under the FIELD, not under the whole row. */
.ui-typeahead--row { display: flex; gap: var(--ui-s3); align-items: flex-start; flex-wrap: wrap; }
.ui-typeahead__field { position: relative; flex: 1 1 16rem; min-width: 0; }
.ui-typeahead__add { flex: 0 0 auto; white-space: nowrap; }
@media (max-width: 599px) { .ui-typeahead__add { flex: 1 1 100%; } }
/* Dates in view on arrival (point 2), the délai beside the échéance (point 8): date + [date | jours]. */
.ui-dates--editor { margin-top: var(--ui-s4); align-items: start; }
/* A date input renders its own «jj/mm/aaaa» plus a picker button: under about 150 px Chrome
   truncates it («10/0»), which is worse than useless on an échéance (UX review 2026-09-08, M5).
   The pair is a wrapping row, so the délai drops under the date rather than squeezing it. */
.ui-dates__pair { display: flex; flex-wrap: wrap; gap: var(--ui-s3); align-items: end; }
.ui-dates__pair > .ui-field:first-child { flex: 1 1 10.5rem; min-width: 10.5rem; }
.ui-dates__pair > .ui-field + .ui-field { flex: 0 1 7.5rem; }
.ui-dates--editor input[type="date"] { min-width: 9.5rem; }
.ui-dates__pair .ui-field + .ui-field { margin-top: 0; }
.ui-dates__days { display: flex; align-items: center; gap: var(--ui-s2); }
.ui-dates__days .ui-input { min-width: 0; }
.ui-dates__days-unit { flex: none; font-size: var(--ui-fs-small); color: var(--ui-ink-2); }
.ui-dates__line { flex: 1 1 100%; margin-top: calc(var(--ui-s2) * -1); }
/* The ICE counter (« 12/15 ») as the person types. */
.ui-field__count { float: inline-end; font-variant-numeric: tabular-nums; color: var(--ui-ink-3); font-weight: 400; }
.ui-field__count[data-ok="1"] { color: var(--ui-good, #1a7f4b); }
/* The client sheet's two-tile choice on a phone: side by side is still readable at 390 px. */
.ui-choices--kind { grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr)); margin-bottom: var(--ui-s4); }

/* ---- 18. 2026-09-08 tiers: the client / supplier record, the legal-form choice, the ICE counter ---- */
.ui-tiers { min-width: 0; }
.ui-tiers__badges { display: flex; flex-wrap: wrap; gap: var(--ui-s2); align-items: center; margin-top: var(--ui-s2); }
.ui-tiers__grid { display: grid; gap: var(--ui-s5); align-items: start; }
/* The money card holds three figures of up to «1 396 105,34 MAD»; the identity card is a
   two-column dl of short values. 5fr/7fr the other way round ran the figures into each other
   at 1440 and 1280 once the sidebar took 248 px (UX review 2026-09-08, B1). */
@media (min-width: 900px) { .ui-tiers__grid { grid-template-columns: minmax(0, 7fr) minmax(0, 6fr); } }
.ui-tiers__figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr)); gap: var(--ui-s4); }
.ui-tiers__figures > div { display: flex; flex-direction: column; min-width: 0; }
.ui-tiers__figure { font: 700 1.25rem/1.15 var(--ui-display); letter-spacing: -0.015em; font-variant-numeric: tabular-nums; margin-top: 2px; white-space: nowrap; }
.ui-tiers__figure .ui-money__unit { font-size: var(--ui-fs-small); font-weight: 500; opacity: .75; }
@media (max-width: 599px) { .ui-tiers__figures { grid-template-columns: 1fr; gap: var(--ui-s3); } }
.ui-tiers__figure { overflow-wrap: normal; }
/* An e-mail is one long unbreakable token: in a two-column dl it ran into the next column
   («contact@atlasingnierie.ma0522766351», looked at 2026-09-08). */
.ui-tiers__facts .ui-record__facts { column-gap: var(--ui-s5); }
.ui-tiers__facts .ui-record__facts > div { min-width: 0; }
.ui-tiers__facts .ui-record__facts dd { overflow-wrap: anywhere; }
/* A table wider than its card scrolls sideways; say so at the edge instead of clipping the
   last column in silence (UX review 2026-09-08, M1). Pure CSS: the two `local` layers scroll
   WITH the content, the two `scroll` shadows stay — so a shadow shows only on a side that has
   something hidden. */
.ui-table-wrap {
  background:
    linear-gradient(to right, var(--ui-surface) 30%, rgba(255, 255, 255, 0)) left center / 40px 100% no-repeat local,
    linear-gradient(to left, var(--ui-surface) 30%, rgba(255, 255, 255, 0)) right center / 40px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, rgba(23, 20, 43, .14), rgba(23, 20, 43, 0)) left center / 14px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, rgba(23, 20, 43, .14), rgba(23, 20, 43, 0)) right center / 14px 100% no-repeat scroll;
}
/* «Factures328» — the count was glued to the word (UX review 2026-09-08). */
.ui-mode__btn .ui-filter__count { margin-inline-start: 6px; opacity: .75; }
.ui-choices--two { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--ui-s3); }
@media (max-width: 599px) { .ui-choices--two { grid-template-columns: 1fr; } }
.ui-input-wrap { position: relative; display: block; }
.ui-input-wrap .ui-input { padding-inline-end: 4.5rem; width: 100%; }
.ui-input__count { position: absolute; inset-inline-end: 12px; top: 50%; transform: translateY(-50%); font-variant-numeric: tabular-nums; color: var(--ui-ink-3); pointer-events: none; }
.ui-input__count.is-good { color: var(--ui-good); }
.ui-input__count.is-bad { color: var(--ui-bad); font-weight: 600; }

/* ---- 17. 2026-09-08 import ---------------------------------------------------------- */
/* Point 11 (expert-comptable): several files, .xlsx, and supplier-invoice PDFs reviewed one
   form each. Only classes that begin with .ui-import__batch / .ui-import__refused /
   .ui-pdf-review live here; nothing above this banner is touched. */
.ui-import__batch { margin-bottom: var(--ui-s4); padding: var(--ui-s4) var(--ui-s5); }
.ui-import__batch-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--ui-s2) var(--ui-s4); }
.ui-import__batch-list li { display: inline-flex; align-items: center; gap: var(--ui-s2); min-height: 32px; }
.ui-import__refused { margin-bottom: var(--ui-s4); }
.ui-import__refused-list { margin: var(--ui-s2) 0 0; padding-left: 1.2em; }
.ui-import__refused-list li + li { margin-top: var(--ui-s1); }
.ui-import__recognised { margin-left: var(--ui-s1); font-size: .72rem; vertical-align: middle; }
.ui-pdf-review { margin-bottom: var(--ui-s5); }
.ui-pdf-review__head { display: flex; align-items: center; gap: var(--ui-s3); flex-wrap: wrap; margin-bottom: var(--ui-s4); }
.ui-pdf-review__head h2 { overflow-wrap: anywhere; }
.ui-pdf-review__grid { display: grid; gap: var(--ui-s5); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 900px) { .ui-pdf-review__grid { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); align-items: start; } }
.ui-pdf-review__form .ui-field + .ui-field, .ui-pdf-review__form .ui-dates + .ui-field, .ui-pdf-review__form .ui-field + .ui-dates, .ui-pdf-review__form .ui-dates + .ui-dates { margin-top: var(--ui-s4); }
.ui-pdf-review__form .ui-dates--three { grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); }
.ui-pdf-review__text { min-width: 0; }
.ui-pdf-review__pre { margin: 0; max-height: 32rem; overflow: auto; padding: var(--ui-s3) var(--ui-s4); border-radius: var(--ui-r-sm); background: var(--ui-surface-2); color: var(--ui-ink-2); font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; white-space: pre; -webkit-overflow-scrolling: touch; }
.ui-pdf-review--done { border-inline-start: 4px solid var(--ui-good, #2f7d4f); }
@media (max-width: 899px) { .ui-pdf-review__pre { max-height: 18rem; font-size: 11px; } }
/* Looked at on 390 px (2026-09-08): two tiles side by side wrapped their hints over five lines.
   On a phone they stack, one per row. */
@media (max-width: 599px) { .ui-choices--kind { grid-template-columns: 1fr; } }
/* The line row, measured 2026-09-08: beside the preview pane the form is ~520 px wide and the
   old 1fr 5rem 8rem 7rem 5.5rem grid left the designation 64 px. The lines group is a container:
   wide enough (≥ 44rem) the designation takes the remaining width beside fixed qté / PU / total;
   narrower — the two-pane editor at 1440 and 1280, and every phone — the designation gets its
   own full-width row above qté / PU / total. The designation cell also hosts the type-ahead list. */
.ui-lines--editor { container-type: inline-size; }
.ui-lines--editor .ui-line__desig { position: relative; }
/* The designation's catalogue list is IN THE FLOW, on every line editor (FACT-ENGAGEMENT M1 = FACT-CPA F1,
   2026-09-24). Floating, it lay over Qté and P.U. HT: its items take mousedown.prevent, so a click meant for
   Quantité picked an article and rewrote the line (FA-2026-0371 went out at 500 instead of 1 500). In the flow it
   pushes the line's figures down while it is open and can never sit on them. Shorter than the floating 320 px
   so the fields it pushes stay near. */
.ui-typeahead__list[data-desig-hits] { position: static; margin-top: 4px; max-height: 264px; }
.ui-lines--editor .ui-typeahead__item .ui-muted { white-space: nowrap; }
.ui-lines--editor .ui-line--head, .ui-lines--editor .ui-line__main { grid-template-columns: minmax(12rem, 1fr) 5rem 7rem 6rem 5rem; }
@container (max-width: 44rem) {
  .ui-lines--editor .ui-line--head { display: none; }
  .ui-lines--editor .ui-line__main { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) 5rem; grid-template-areas: "desig desig desig desig" "qty price total tools"; }
  .ui-lines--editor .ui-line__desig { grid-area: desig; } .ui-lines--editor .ui-line__qty { grid-area: qty; } .ui-lines--editor .ui-line__price { grid-area: price; } .ui-lines--editor .ui-line__total { grid-area: total; } .ui-lines--editor .ui-line__tools { grid-area: tools; align-self: end; }
  .ui-lines--editor .ui-line__label { display: block; font-size: var(--ui-fs-small); color: var(--ui-ink-2); margin-bottom: 2px; }
  .ui-lines--editor .ui-line__total { padding-top: 0; align-self: end; padding-bottom: 12px; }
}
/* A phone hides the tools column (the summary line carries «Supprimer»): three columns, no gap lost. */
@media (max-width: 899px) { .ui-lines--editor .ui-line__main { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr); grid-template-areas: "desig desig desig" "qty price total"; } }
@media (max-width: 479px) { .ui-pdf-review__form .ui-dates--three { grid-template-columns: minmax(0, 1fr); } }
.ui-pdf-review__done { padding: var(--ui-s4); border-radius: var(--ui-r-sm); background: var(--ui-good-soft); }
/* The keyboard-active option of a type-ahead (aria-selected), for both pickers. */
.ui-typeahead__list li.is-active > .ui-typeahead__item { background: var(--ui-surface-2); box-shadow: inset 0 0 0 2px var(--ui-accent); }

/* ---- 19. 2026-09-08 assistant: a key per provider -------------------------------------- */
/* Which prestataire already holds a key, under the select that chooses one. It sits BELOW the
   select's own hint, not on top of it (looked at, 2026-09-08). */
.ui-keybadges { display: flex; flex-wrap: wrap; gap: var(--ui-s2); margin-top: var(--ui-s3); }

/* ---- 21. The recurrence rule (2026-09-09) --------------------------------------
   «Tous les [2] [mois]» is one sentence, so the count and the unit sit on one line and
   read in that order. The count is deliberately narrow: it takes 1-99 and a full-width
   field would say it expects something longer. */
.ui-rule { display: grid; grid-template-columns: 7rem minmax(0, 1fr); gap: var(--ui-s3); align-items: end; }
@media (max-width: 599px) { .ui-rule { grid-template-columns: 5.5rem minmax(0, 1fr); } }

/* The days of the month. A 31-cell grid rather than a multi-select, because picking «the 1st
   and the 15th» out of a list box on a phone is a fight, and because the shape of a month is
   itself the affordance. Every cell is a real 44 px target. */
.ui-daygrid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: var(--ui-s1); margin-top: var(--ui-s2); max-width: 24rem; }
.ui-daygrid__day { position: relative; }
.ui-daygrid__day input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.ui-daygrid__day span {
  display: flex; align-items: center; justify-content: center;
  min-height: var(--ui-tap); border-radius: 10px;
  font-size: var(--ui-fs-small); font-variant-numeric: tabular-nums;
  background: var(--ui-surface-2); color: var(--ui-ink-2); transition: background .12s, color .12s;
}
.ui-daygrid__day input:checked + span { background: var(--ui-accent); color: var(--ui-on-accent); font-weight: 600; }
.ui-daygrid__day input:focus-visible + span { outline: 2px solid var(--ui-accent); outline-offset: 2px; }
.ui-daygrid__day input:disabled + span { opacity: .5; }

/* ---- 22. Reading the document on a phone (2026-09-09) --------------------------
   The preview is scaled to fit the pane; at 390 px that is 46 % of an A4 page, which puts the
   article-145 legal line at 4.55 px inside `overflow: hidden` — unreadable and unpannable.
   Two ways out now, and both of them are visible: the control says «Lire le document» instead
   of being an icon with an aria-label, and the thumbnail itself opens the full-screen view,
   because a picture of a document is something you expect to be able to tap. */
.ui-preview-open__label { display: none; }
@media (max-width: 899px) {
  .ui-preview-open { width: auto; padding-inline: var(--ui-s4); gap: var(--ui-s2); }
  .ui-preview-open__label { display: inline; font-weight: 600; }
  /* The thumbnail is a button in all but name, so it says so to the finger and to the eye. */
  .ui-docpane:not(.is-full) .ui-docframe { cursor: pointer; }
}

/* ---- 23. The document's actions, on a phone (2026-09-09) -----------------------
   Measured at 390x844: five actions in one ragged left column — 193.3 / 193.7 / 234.6 / 164 /
   207.8 px wide, four different widths, two arbitrary weights, in no order, and the fifth
   orphaned 393 px below the other four. The owner's words for the phone were «buttons just
   thrown away», and this is the screen that earns them.

   `flex-wrap: wrap` is the cause: it packs by WIDTH, so the order on screen is decided by how
   long each label happens to be. On a phone the same five become one column of full-width
   rows, which gives them one left edge, one right edge, and an order that is read top to
   bottom — the order they are actually used in.

   The ranking is deliberate and is not alphabetical or accidental:
     Télécharger le PDF · Envoyer par e-mail   what you do with a document you just made
     Marquer comme envoyé · Établir un avoir   what you do afterwards, and less often
   and «Annuler le document» keeps its own separated block, because it consumes a number
   for ever. */
@media (max-width: 899px) {
  .ui-record__more { flex-direction: column; align-items: stretch; gap: var(--ui-s2); }
  .ui-record__more > .ui-btn,
  .ui-record__more > form > .ui-btn,
  .ui-record__more > a.ui-btn { width: 100%; justify-content: center; }
  /* A ghost button in a full-width column has no edge, so the two «afterwards» actions read
     as text floating under two real buttons. They get the same shape and stay quieter by
     colour, which is the distinction that survives being stacked. */
  .ui-record__more > .ui-btn--ghost,
  .ui-record__more > form > .ui-btn--ghost { box-shadow: inset 0 0 0 1.5px var(--ui-stroke); color: var(--ui-ink-2); }
  .ui-record__danger { margin-top: var(--ui-s5); }
  .ui-record__danger .ui-btn { width: 100%; justify-content: center; }
}

/* ---- 24. The assistant's composer docks on a phone (2026-09-09) ----------------
   `chat.blade.php` says, in its own header comment, that the composer «docks in the thumb
   zone (D7)». It never did: `grep ui-chat ui.css` returned eight declarations and not one
   media query, so the composer was a static block at the END of the document, after the
   transcript. Every answer grew the thread above it and pushed it further down — measured at
   390x844 on an EMPTY conversation it already sat at y=628, and there is no scroll-into-view
   anywhere in the module. You scrolled to a moving target to type each message. That is the
   owner's «the chatbot interface is terrible», and it was one missing property.

   Docked, not sticky: a sticky composer still scrolls away when the thread is short. The
   thread gets the bar's height back as padding so the last answer is never underneath it. */
@media (max-width: 899px) {
  /* 2026-09-10: the DOCK (suggestions + composer) is what docks now, over a fade of the
     canvas so the thread scrolling under it does not read through. */
  .ui-chat__dock {
    position: fixed; inset-inline: var(--ui-s3); z-index: 40;
    bottom: calc(var(--ui-s3) + env(safe-area-inset-bottom, 0px));
    padding-top: var(--ui-s4);
    background: linear-gradient(to top, var(--ui-canvas) 72%, rgba(243, 241, 250, 0));
  }
  .ui-chat__box { box-shadow: var(--ui-shadow-lift); }
  /* Room for the docked bar under the last message AND under the privacy line. */
  .ui-chat { padding-bottom: 168px; }
  /* The tab bar would sit on top of the composer; the composer is what this screen is for. */
  body:has(.ui-chat__composer) .ui-tabbar { display: none; }
}

/* ---- 25. Phone craft, from the 2026-09-09 audit --------------------------------
   Everything here is the owner's «buttons just thrown away», measured. No element moves to a
   different place in the document; what changes is width, spacing and whether a control looks
   like a control. */
@media (max-width: 899px) {
  /* THE PAGE WAS WIDER THAN THE PHONE. /clients/{uuid} measured scrollWidth 417 in a 390
     viewport (reproduced at 400 on another record, so it is structural): the segment row
     beside «Historique» ran x121 -> 417, clipping «Avoirs 6» and dragging the fixed tab bar
     to x12 -> 405 so «Plus» was cut. Every `.ui-mode` scrolls at this width, not only the one
     in a page head — that narrower fix left this one overflowing. */
  .ui-mode {
    max-width: 100%; overflow-x: auto; scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
    /* The snap lands a segment ONE FADE in, where app.js parks the pressed one (W3-FACT 2026-09-25):
       with no padding `scroll-snap-align: start` pulled it back flush against the row's start,
       under the left dissolve — «Devis» and «Factures proforma» half transparent at 390. */
    scroll-padding-inline-start: var(--ui-s5);
  }
  .ui-mode::-webkit-scrollbar { display: none; }
  .ui-mode__btn { flex: 0 0 auto; scroll-snap-align: start; }

  /* THE EDGE FADE THE SEGMENT ROW HAS BEEN ASKING FOR SINCE 2026-09-05. app.js toggles
     `.is-scrollable` / `.is-at-end` on a pill row that overflows — and NOTHING in this
     stylesheet ever answered, an unwired surface of exactly the recurring kind. Measured at
     390 on 2026-09-15 by check-phone.mjs: «Avoirs» was sliced 37 px into its own word on
     /documents, «Bons de livraison» 124 px on the Avoirs tab, «Factures» 55 px at the OTHER
     end (the row auto-scrolls the pressed tab into view, so both ends cut), and «Avoirs 6»
     10 px on a client record. A row dissolves at the end it can still be scrolled towards,
     and only at that end — so the fade also says which way there is more. */
  .ui-mode.is-scrollable {
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--ui-s5), #000 calc(100% - var(--ui-s5)), transparent 100%);
    mask-image: linear-gradient(to right, transparent 0, #000 var(--ui-s5), #000 calc(100% - var(--ui-s5)), transparent 100%);
  }
  .ui-mode.is-scrollable.is-at-start {
    -webkit-mask-image: linear-gradient(to left, transparent 0, #000 var(--ui-s5));
    mask-image: linear-gradient(to left, transparent 0, #000 var(--ui-s5));
  }
  .ui-mode.is-scrollable.is-at-end {
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--ui-s5));
    mask-image: linear-gradient(to right, transparent 0, #000 var(--ui-s5));
  }
  /* Both ends land on a segment boundary — which `scroll-snap-align: start` makes the normal
     resting state — so nothing is cut and nothing should dissolve. */
  .ui-mode.is-scrollable.is-at-start.is-at-end { -webkit-mask-image: none; mask-image: none; }

  /* A GHOST BUTTON IN A STACK IS NOT A BUTTON. On /timbre, 13 of 17 controls computed
     `background: transparent; border-width: 0` — grey text and nothing else — and the SAME
     action, «Marquer comme déclarée», was a bordered button once and bare text six times
     below it. On /équipe, 16 of 20. On a phone, where there is no hover to discover them,
     a ghost action in a list gets an edge. It stays quieter than a secondary by colour.

     Scoped by what a control IS, not by which container it happens to sit in — the first
     version of this rule listed four wrappers and still left 16 of 16 on /timbre bare.
     Icon-only ghosts keep their transparency: a close cross or a row's trash icon reads as a
     control from its glyph, and boxing every one of them would be the opposite mistake. */
  .ui-btn--ghost:not(.ui-btn--icon) { box-shadow: inset 0 0 0 1.5px var(--ui-stroke); color: var(--ui-ink-2); }

  /* Two tap targets 4 px apart are one tap target with a coin-flip in it. Measured 4.0-4.5 px
     on the client rows, the supplier rows, the timbre months and the team members. */
  .ui-row-item__actions { gap: var(--ui-s2); }

  /* A 15-digit ICE broke across two lines mid-number («00178945600006» / «7») because the
     identity grid gave it 145 px and the app's global `overflow-wrap: anywhere` applies to a
     number as happily as to a word. An identifier is one token or it is misread. */
  .ui-num, .ui-record__facts .ui-num { overflow-wrap: normal; word-break: keep-all; }
}

/* ---- 26. Phone leftovers (2026-09-10) -------------------------------------------
   The rest of the 2026-09-09 phone audit (sessions/2026-09-09/PHONE-AUDIT-recovered.md),
   re-measured at 390x844 before anything was touched. Nothing here picks a new look: the
   pills, the tab bar, the rows and the colours are the ones already on screen. What changes
   is what is on the first screen, what is the strongest control, and what can be reached. */

/* C12 / A12 / B11 — THE DEMO BANNER COST 75 PX AND A SECOND BORDERED BUTTON ON EVERY SCREEN.
   A three-line sentence beside «Corriger maintenant» (secondary, 176x44), above the app's own
   header, out-shouting each page's own action. On a phone the banner is now ONE target: the
   sentence and «Corriger ›» are a single link, 44 px tall, no second button chrome. The desk
   keeps its sentence and its button — it has the width for both. */
.ui-impersonating__phone { display: none; }
@media (max-width: 899px) {
  .ui-impersonating[data-demo-identity] { padding: 0 var(--ui-s4); }
  .ui-impersonating .ui-impersonating__phone { display: flex; align-items: center; gap: var(--ui-s3); width: 100%; min-height: var(--ui-tap); padding-block: 2px; color: inherit; }
  .ui-impersonating__phone > span:first-child { flex: 1 1 auto; min-width: 0; line-height: 1.35; }
  .ui-impersonating__fix { flex: none; display: inline-flex; align-items: center; gap: 2px; font-weight: 600; color: var(--ui-ink); text-decoration: underline; text-underline-offset: 3px; }
}

@media (max-width: 899px) {
  /* B3 / B4 — A LIST'S OWN ACTIONS, ONE ROW, ONE PRIMARY.
     «Importer un fichier» (ghost, 215 px) then «Nouvel achat» (outline, 174 px) wrapped onto two
     lines with two different right edges on /achats and /fournisseurs, fitted one line on
     /clients and /catalogue: the same pair, four different results. And the page's own create
     action was an OUTLINE button on all four — nothing on the list said which control the list
     is for. The row is now as wide as the page (one left edge, one right edge, the page's), the
     create action is the primary and takes the extra width. The tab bar's «+» stays the app's
     «Nouvelle facture»: it is a round button at the bottom, this is a pill in the head, and
     neither one is the other's job. If a narrower phone cannot fit the two, each takes its
     own full line — still one left edge and one right edge. */
  .ui-page__actions { width: 100%; flex-wrap: wrap; gap: var(--ui-s2); }
  .ui-page__actions > .ui-btn { flex: 1 1 auto; min-width: 0; padding-inline: var(--ui-s4); }
  .ui-page__actions > .ui-btn--primary { flex-grow: 2; }

  /* A11 / B2 — THE SEARCH ROW IS ONE ROW.
     On /documents the Devis, Proforma, Avoirs and Bons tabs put their «Nouveau …» button on a
     second toolbar line and Factures did not (the tab bar's «+» is that one), so the whole
     screen moved 44 px every time a tab was changed. On /achats «Filtrer» took a line of its
     own. The create action and «Filtrer» now sit beside the search on every tab; a tab with
     nothing to put there leaves the search the full width, and the rows start at the same y. */
  .ui-toolbar--row { flex-wrap: nowrap; gap: var(--ui-s2); }
  .ui-toolbar--row > .ui-input { flex: 1 1 auto; min-width: 0; }
  .ui-toolbar--row > .ui-toolbar__end { width: auto; flex: 0 0 auto; margin-inline-start: 0; }
  .ui-toolbar--row > .ui-toolbar__end:not(:has(.ui-btn, .ui-filter)) { display: none; }
  .ui-toolbar--row .ui-toolbar__end .ui-btn { padding-inline: var(--ui-s4); }
  /* Beside the sort, the chip row ends at its own edge and fades there — it ran 4 px under the
     select, which cut «Brouillon» into «Bro» against the select's white. 16 px of fade, not 24:
     at 24 «En retard 44» — the chip /documents is opened for — had its own label ending one
     pixel inside the dissolve at 390 (measured 2026-09-15, check-phone.mjs). */
  .ui-listbar .ui-filters { margin-inline-end: 0; padding-inline-end: 16px; -webkit-mask-image: linear-gradient(to left, transparent 0, #000 16px); mask-image: linear-gradient(to left, transparent 0, #000 16px); }

  /* C11 — ROWS 2 TO 4.5 PX APART. Each of these rows is a whole-width target; at 2 px a thumb
     that lands between two of them has a coin to toss. 8 px, on every list of rows. */
  .ui-rows, .ui-rowlist, .ui-evidence__rows, .ui-list, .ui-tasks, .ui-pulse__list, .ui-register { gap: var(--ui-s2); }

  /* C8 — the fixed bars (header, tab bar, a form's save pill) never land a focused field under
     themselves: keyboard focus and scroll-into-view keep this much clear at each edge. */
  html { scroll-padding-top: 80px; }
  html:has(.ui-tabbar), html:has(.ui-actionbar--sticky), html:has(.ui-declare__actions--dock) { scroll-padding-bottom: 120px; }
}

/* C8 — «Enregistrer» (and the tab bar) sat over live field help: the text ran on under the
   floating pill and showed, cut, in the strip below it. The page now fades to the canvas under
   the bottom bars (the element is in the layout, below the bars' z-index 40 and above the page;
   it takes no pointer events), and focus never lands a field under them (scroll-padding above). */
.ui-bottomfade { display: none; }
@media (max-width: 899px) {
  .ui-bottomfade { display: block; position: fixed; inset-inline: 0; bottom: 0; height: 124px; z-index: 39; pointer-events: none; background: linear-gradient(to top, var(--ui-canvas) 40%, rgba(243, 241, 250, 0)); }
}

/* B7 / B8 — A CLIENT OR SUPPLIER ROW ON THE PHONE.
   The action column took 164-202 px of a 350 px row, so the NAME got 116 px and «Bureau Plus
   Casablanca» broke over four lines while «Nouvel achat» sat whole beside it. And the row
   carried the name and the ICE only, while the search box above it offers to find a client by
   reference and by e-mail, and the desk table carries the town: a phone could search by what
   it could not see. Now the name has the whole line; the ICE and the actions share the second;
   the town, the e-mail and the reference are the third. The name is still the row's one link,
   stretched over the whole row, and the actions sit above that stretch. */
.ui-row-item--tiers { position: relative; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "name name" "meta acts" "more more"; row-gap: 2px; }
.ui-row-item--tiers > .ui-row-item__link { grid-area: name; min-height: 0; padding: 0; }
.ui-row-item--tiers > a.ui-row-item__link::after { content: ''; position: absolute; inset: 0; border-radius: inherit; }
.ui-row-item--tiers > .ui-row-item__meta { grid-area: meta; min-width: 0; align-self: center; }
.ui-row-item--tiers > .ui-row-item__more { grid-area: more; min-width: 0; }
.ui-rows .ui-row-item--tiers > .ui-row-item__actions { grid-area: acts; position: relative; z-index: 1; min-width: 0; align-self: center; }
.ui-row-item--tiers:not(:has(> .ui-row-item__actions > *)) { grid-template-areas: "name name" "meta meta" "more more"; }
/* Nothing for the second line (no ICE to show, no phone): the details take its place beside the
   action. The name keeps its whole line even when nothing else is on the row — putting the
   action beside it brought back the wrapped name this block exists to stop
   (check-phone-leftovers B7 caught exactly that, 2026-09-10).
   (Classes from the blade, not `:has()`: a :has() inside a :has() is invalid CSS and the whole
   rule is dropped — the first version of this did exactly that.) */
.ui-row-item--tiers.has-no-meta > .ui-row-item__meta { display: none; }
.ui-row-item--tiers.has-no-meta { grid-template-areas: "name name" "more acts"; }
.ui-row-item--tiers:not(:has(> .ui-row-item__actions > *)) > .ui-row-item__actions { display: none; }
/* 16 px of padding, not 24, on a row's own buttons: «Facturer» then leaves the ICE its 145 px on
   one line. The ICE never breaks INSIDE the number (§25); on a supplier row with a phone it
   may put «ICE» over the number, as a label over its value. A town, an e-mail and a reference
   break between each other, never inside a reference; only an e-mail may break inside. */
.ui-rows .ui-row-item--tiers > .ui-row-item__actions .ui-btn:not(.ui-btn--icon) { padding-inline: var(--ui-s4); }
.ui-nowrap { white-space: nowrap; }
.ui-row-item__more > span { white-space: nowrap; }
.ui-row-item__more > .ui-row-item__more-email { white-space: normal; overflow-wrap: anywhere; }

/* C9 — THE «PLUS» SHEET WAS CUT BY THE SCREEN EDGE WITH NOTHING SAYING IT SCROLLS.
   20 rows in 760 px: «Données personnelles» sliced in half at y 814-858 and six rows past it
   that the sheet gave no sign of. A shadow now shows at whichever edge has more behind it — the
   same pure-CSS pair the tables use (§18): two `local` covers that scroll with the content, two
   `scroll` shadows that do not — so it appears only when there IS more. */
.ui-sheet__body {
  background:
    linear-gradient(var(--ui-surface) 30%, rgba(255, 255, 255, 0)) center top / 100% 32px no-repeat local,
    linear-gradient(rgba(255, 255, 255, 0), var(--ui-surface) 70%) center bottom / 100% 32px no-repeat local,
    radial-gradient(farthest-side at 50% 0, rgba(23, 20, 43, .26), rgba(23, 20, 43, 0)) center top / 100% 16px no-repeat scroll,
    radial-gradient(farthest-side at 50% 100%, rgba(23, 20, 43, .26), rgba(23, 20, 43, 0)) center bottom / 100% 16px no-repeat scroll;
}
@media (max-width: 899px) { .ui-sheet__body { padding-bottom: calc(var(--ui-s4) + env(safe-area-inset-bottom, 0px)); } }

/* C10 — THE FIELD TIPS OVERLAPPED THEIR OWN FIELD.
   The 44x44 hit area was drawn out of a 20 px label line with -12 px margins, so its bottom sat
   6 px INSIDE the input it explains (measured on /entreprise: trigger bottom 1010, input top
   1004) — a tap on the top of the ICE box opened the tip. A label that carries a tip is now a
   44 px row, pulled up into the gap above it so the label still sits nearer its own input than
   the previous one, and every trigger clears its input by the same 4 px. */
.ui-field__label:has(> .ui-tip) { display: flex; align-items: center; flex-wrap: wrap; min-height: var(--ui-tap); margin-top: -8px; margin-bottom: var(--ui-s1); }
.ui-field__label > .ui-tip .ui-tip__trigger { margin-block: 0; }
/* The ICE counter («12/15») floated to the label's end; in the flex row it keeps that place. */
.ui-field__label:has(> .ui-tip) > .ui-field__count { float: none; margin-inline-start: auto; }

/* A7 — /documents/recurrentes: the line that says what runs on its own, and «Générer
   maintenant» under it only when something is due. */
.ui-recurring__run { margin-bottom: var(--ui-s4); }

/* A8 — «Émettre» on a blank invoice (no client, no line). It stays a button — a tap still
   answers, by marking the missing field and moving to it (confirmIssue → focus-first-invalid),
   which a `disabled` button could not — but it no longer looks like an invitation. */
.ui-btn[aria-disabled="true"] { opacity: .5; box-shadow: none; }

/* A9 — the editor's Guidé | Formulaire switch stopped 15 px short of the card under it on the
   right, with an empty track after «Formulaire». On a phone the two halves fill the row. */
@media (max-width: 899px) {
  .ui-page__head .ui-mode--fill { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; overflow: visible; }
  .ui-page__head .ui-mode--fill .ui-mode__btn { justify-content: center; }
  .ui-page__head .ui-row:has(> .ui-mode--fill) { width: 100%; }
}

/* A10 — EN MASSE: 130 CLIENTS IN A 272 PX BOX INSIDE A SCROLLING PAGE.
   A nested scroller 21 times its own height, a third of the screen wide, with its last two
   rows under the fixed «Lancer» bar at arrival: a swipe that started on it scrolled the list,
   not the page, all the way down 5 776 px of names. On a phone the choice is now its own
   full-screen step — the same element, opened by «Choisir les clients» and closed by
   «Terminé» — so the page has ONE scroll and the list has the whole screen. The desk keeps the
   inline list, where a wheel over it is not a trap. */
.ui-picker-open, .ui-picker-layer__head, .ui-picker-layer__foot { display: none; }
@media (max-width: 899px) {
  .ui-picker-open { display: flex; align-items: center; justify-content: space-between; gap: var(--ui-s3); flex-wrap: wrap; margin-top: var(--ui-s3); }
  body:not(.ui-picking) .ui-picker-layer { display: none; }
  body.ui-picking { overflow: hidden; }
  body.ui-picking .ui-picker-layer {
    position: fixed; inset: 0; z-index: 60; display: flex; flex-direction: column; gap: var(--ui-s3);
    margin: 0; padding: var(--ui-s3) var(--ui-s3) calc(var(--ui-s3) + env(safe-area-inset-bottom, 0px));
    background: var(--ui-canvas); animation: ui-rise .2s both;
  }
  body.ui-picking .ui-picker-layer__head { display: flex; align-items: center; justify-content: space-between; gap: var(--ui-s3); }
  body.ui-picking .ui-picker-layer .ui-field + .ui-field, body.ui-picking .ui-picker-layer > * { margin-top: 0 !important; }
  body.ui-picking .ui-picker-layer .ui-list-total { margin: 0; order: 0; }
  body.ui-picking .ui-picker-layer > .ui-row { flex-wrap: nowrap; gap: var(--ui-s2); }
  body.ui-picking .ui-picker-layer > .ui-row > .ui-btn { flex: 1 1 0; min-width: 0; padding-inline: var(--ui-s3); }
  body.ui-picking .ui-picker { flex: 1 1 auto; min-height: 0; max-height: none; margin: 0; overscroll-behavior: contain; }
  body.ui-picking .ui-picker-layer__foot { display: flex; align-items: center; gap: var(--ui-s2); }
  body.ui-picking .ui-picker-layer__foot .ui-btn--primary { flex: 1 1 auto; }
}

/* A1 — the document on a phone. The thumbnail is a TAP TARGET for the full-screen reader
   (§22), but the tap landed in the iframe — a separate document — and never reached the
   frame's own handler: nothing happened. The thumbnail now takes the tap itself. */
@media (max-width: 899px) { .ui-docpane:not(.is-full) .ui-docframe iframe { pointer-events: none; } }

/* B5 — /import: the one button was 437 px below the fold behind 566 px of prose. On a phone the
   kinds are four short tiles two by two with the chosen one's description under them, the two
   paragraphs that repeat the head and the button's own foot step aside, and «Déposer les
   fichiers» docks in the thumb zone like every other one-step screen (§11f). */
.ui-import__kind-note { display: none; }
@media (max-width: 899px) {
  .ui-import .ui-page__head .ui-measure, .ui-import .ui-declare__state { display: none; }
  .ui-import .ui-choices--pair { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--ui-s2); }
  .ui-import .ui-choices--pair .ui-choice { align-items: center; min-height: 52px; padding: var(--ui-s2) var(--ui-s3); }
  .ui-import .ui-choices--pair .ui-choice__text { display: none; }
  .ui-import .ui-import__kind-note { display: block; margin-top: var(--ui-s2); }
  .ui-import .ui-import__kinds { margin-top: var(--ui-s4); }
  .ui-import .ui-declare__actions--dock .ui-declare__hint { display: none; }
}

/* ---- Le relevé de compte client (2026-09-10) ------------------------------------ */
.ui-statement__dates { max-width: 30rem; margin-bottom: var(--ui-s4); }
.ui-statement { margin-bottom: var(--ui-s5); }
.ui-statement__facts { margin-bottom: var(--ui-s4); }
.ui-statement__closing dd.ui-num { font-size: var(--ui-fs-title); font-weight: 600; }
.ui-statement__amounts { flex-direction: column; align-items: flex-end; gap: 2px; white-space: nowrap; }
.ui-statement__carried td { color: var(--ui-ink-3); }
.ui-statement__cancelled { margin-top: var(--ui-s4); padding: var(--ui-s4) var(--ui-s4) 0; border-top: 1px solid var(--line); }
.ui-statement__cancelled ul { margin: var(--ui-s2) 0; padding-left: 1.1em; }
.ui-statement__cancelled li + li { margin-top: 2px; }
.ui-deposit-preview { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--ui-s1) var(--ui-s3); margin: var(--ui-s4) 0 var(--ui-s3); }
.ui-deposit-preview .ui-num { font-size: var(--ui-fs-title); }
/* Phone: the closing balance is the answer, so it leads at full width; the other three are
   one compact row under it, and the two dates share a line. */
@media (max-width: 899px) {
  .ui-statement__dates { display: grid; grid-template-columns: 1fr 1fr; gap: var(--ui-s2); }
  .ui-statement__facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--ui-s3); }
  .ui-statement__facts > div { min-width: 0; }
  .ui-statement__facts dt { font-size: var(--ui-fs-micro); }
  .ui-statement__facts dd.ui-num { font-size: var(--ui-fs-small); overflow-wrap: anywhere; }
  .ui-statement__closing { grid-column: 1 / -1; order: -1; }
  .ui-statement__facts .ui-statement__closing dd.ui-num { font-size: var(--ui-fs-head); }
}

/* ---- The documents list: period + export row (2026-09-10) ------------------------ */
.ui-period { display: grid; gap: var(--ui-s3); }
.ui-period .ui-dates { max-width: 30rem; }
.ui-period__export { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ui-s2) var(--ui-s4); }
@media (max-width: 899px) {
  .ui-period .ui-dates { display: grid; grid-template-columns: 1fr 1fr; gap: var(--ui-s2); }
}
/* A devis's answer on its page. */
.ui-quote-decision .ui-row { gap: var(--ui-s2); flex-wrap: wrap; }
.ui-quote-decision__refuse { display: grid; gap: var(--ui-s3); max-width: 30rem; }

/* ---- Spotlight: find anything (mounted 2026-09-10) ----------------------------------- */
.ui-side__search { display: flex; align-items: center; gap: var(--ui-s2); width: 100%; min-height: var(--ui-tap); margin: var(--ui-s3) 0 var(--ui-s2); padding: 0 var(--ui-s3); border: 0; border-radius: var(--ui-r-sm); background: var(--ui-surface-2); color: var(--ui-ink-2); font: inherit; font-size: var(--ui-fs-small); cursor: pointer; }
.ui-side__search:hover { background: #f1eef9; color: var(--ui-ink); }
.ui-side__search .ui-kbd { margin-inline-start: auto; }
.ui-kbd, .ui-sp kbd { display: inline-block; padding: 1px 6px; border-radius: 6px; background: var(--ui-surface); box-shadow: inset 0 -1px 0 rgba(23, 20, 43, .20); color: var(--ui-ink-3); font: inherit; font-size: var(--ui-fs-micro); }
.ui-sp-scrim { z-index: 70; }
.ui-sp { position: fixed; z-index: 71; top: 12vh; left: 50%; transform: translateX(-50%); width: min(40rem, calc(100vw - 2 * var(--ui-s3))); max-height: 70vh; display: flex; flex-direction: column; border-radius: 20px; background: var(--ui-surface); box-shadow: var(--ui-shadow-lift); overflow: hidden; }
.ui-sp__field { display: flex; align-items: center; gap: var(--ui-s3); padding: var(--ui-s2) var(--ui-s2) var(--ui-s2) var(--ui-s4); border-bottom: 1px solid var(--line); color: var(--ui-ink-3); }
.ui-sp__input { flex: 1; min-width: 0; min-height: var(--ui-tap); border: 0; outline: 0; background: transparent; color: var(--ui-ink); font: inherit; font-size: var(--ui-fs-lead); }
.ui-sp__results { overflow-y: auto; padding: var(--ui-s2); }
.ui-sp__hint { padding: var(--ui-s4); color: var(--ui-ink-3); font-size: var(--ui-fs-small); }
.ui-sp__keys { margin-inline-start: var(--ui-s2); }
.ui-sp__group { padding: var(--ui-s3) var(--ui-s3) var(--ui-s1); color: var(--ui-ink-3); font-size: var(--ui-fs-micro); font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }
.ui-sp__hit { display: flex; align-items: center; gap: var(--ui-s3); min-height: 52px; padding: var(--ui-s2) var(--ui-s3); border-radius: 12px; color: var(--ui-ink); text-decoration: none; }
.ui-sp__hit svg { flex: none; color: var(--ui-ink-3); }
.ui-sp__hit:hover, .ui-sp__hit:focus-visible { background: var(--ui-accent-soft); outline: 0; }
.ui-sp__main { display: grid; min-width: 0; flex: 1; }
.ui-sp__title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-sp__sub { color: var(--ui-ink-3); font-size: var(--ui-fs-small); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-sp__amount { flex: none; font-weight: 600; }
@media (max-width: 899px) {
  .ui-sp { top: var(--ui-s3); max-height: calc(100dvh - 2 * var(--ui-s3)); }
  .ui-sp__keys { display: none; }
}

/* A destructive action that is not the page's point: text in the danger colour, no fill. */
.ui-btn--danger-text { color: var(--ui-bad); }
.ui-btn--danger-text:hover { background: var(--ui-bad-soft); color: var(--ui-bad); }

/* ---- Richness, second slice (2026-09-10): the four screens the 09-09 pass left flat ----
   Same rule as the first slice: a RANK inside what is there, nothing moved, nothing added.
   A label recedes into the list-head vocabulary (11 px, tracked, ink-3 — held to 4.5:1),
   the value it labels comes forward, and a section title sits one step above the cards it
   holds. Measured before/after with measure-richness.mjs. */
/* The document page (and the client page): «Date d'émission» was 13 px beside a 16 px value —
   the label and the fact read at one level. */
.ui-record__facts dt { font-size: var(--ui-fs-micro); font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--ui-ink-3); }
.ui-record__facts dd { font-size: var(--ui-fs-lead); }
/* The Accueil: «Votre journée» was the same 20 px as the cards under it. */
.ui-home__day-title { font-size: var(--ui-fs-head); }
/* A chart sits INSIDE a titled card («Vos chiffres»): its own title is one step below. */
.chart-title { font-size: var(--ui-fs-lead); }
/* Entreprise: a fold nested inside a section card ranks below the card's own title. */
.ui-disclosure--sub > summary { font: 600 var(--ui-fs-lead)/1.3 var(--ui-font); letter-spacing: 0; }
/* The editor: the line columns read as a table head, like every list's head. */
.ui-line--head { font-size: var(--ui-fs-micro); letter-spacing: .06em; text-transform: uppercase; }
.ui-lines .ui-line__label { font-size: var(--ui-fs-micro); font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--ui-ink-3); }

/* ---- Gestion commerciale (2026-09-10) ------------------------------------------------
   The Commercial's own screens: the tableau, the prospects, the agenda, one prospect. Built
   from the parts already here (cards, rows, filters, facts band); only what is new is new. */
.ui-crm > * + * { margin-top: var(--ui-s5); }
.ui-crm > .ui-page__head + * { margin-top: var(--ui-s4); }
.ui-crm__figures { margin-top: 0; padding: var(--ui-s5); background: var(--ui-surface); border-radius: var(--ui-r); box-shadow: var(--ui-shadow); grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); }
.ui-crm__figures dd.ui-num { font: 600 var(--ui-fs-head)/1.15 var(--ui-display); letter-spacing: -0.02em; }
.ui-crm__figures dd.ui-num .ui-small { display: inline; font: 500 var(--ui-fs-small)/1 var(--ui-font); letter-spacing: 0; }
.ui-crm__split { display: grid; gap: var(--ui-s5); grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); align-items: start; }
.ui-crm__split > .ui-card { margin: 0; }
/* «Avec» + «Nouveau» on one line; the list takes what the button leaves (2026-09-16). */
.ui-crm-picker__row { display: flex; flex-wrap: wrap; gap: var(--ui-s2); align-items: center; }
.ui-crm-picker__row > .ui-select { flex: 1 1 12rem; min-width: 0; }
.ui-crm-quick { display: grid; gap: var(--ui-s3); margin-top: var(--ui-s3); padding: var(--ui-s4); border-radius: 16px; background: var(--ui-surface-2); }
.ui-crm-quick .ui-field + .ui-field { margin-top: 0; }
/* Its two buttons stay on one line at 390 (they wrapped into a stack that read like a second
   sheet foot, right above the real «Annuler / Enregistrer»). */
.ui-crm-quick > .ui-crm-picker__row { flex-wrap: nowrap; justify-content: flex-end; }
.ui-crm__empty { padding: var(--ui-s2) var(--ui-s3) var(--ui-s3); }
.ui-crm__actions { display: flex; flex-wrap: wrap; gap: var(--ui-s2); }
.ui-crm__lost { display: grid; gap: var(--ui-s3); margin-top: var(--ui-s4); }
.ui-crm__stage + .ui-btn, .ui-card .ui-filters + .ui-btn, .ui-card .ui-filters + .ui-crm__lost { margin-top: var(--ui-s4); }
/* Rows are a phone pattern the desk swaps for a table; these lists have no table, so they
   stay rows at every width. */
.ui-rows.ui-rows--always { display: grid; }
/* The pipeline: one column per open stage, the count first, the newest names under it. On a
   phone it scrolls sideways inside itself — never the page. */
.ui-crm__pipeline { display: grid; gap: var(--ui-s3); grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); }
.ui-crm__stage { display: flex; flex-direction: column; gap: var(--ui-s1); margin: 0; padding: var(--ui-s4); color: inherit; min-height: 7.5rem; transition: box-shadow .15s, transform .15s; }
.ui-crm__stage:hover { box-shadow: var(--ui-shadow-lift); }
.ui-crm__stage-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--ui-s2); margin-bottom: var(--ui-s2); }
.ui-crm__stage-count { font: 600 var(--ui-fs-head)/1 var(--ui-display); }
.ui-crm__stage-item { font-size: var(--ui-fs-small); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 899px) {
  .ui-crm__pipeline { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: minmax(11rem, 72%); overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: var(--ui-s2); margin-inline: calc(-1 * var(--ui-s3)); padding-inline: var(--ui-s3); }
  .ui-crm__stage { scroll-snap-align: start; }
  .ui-crm__figures { grid-template-columns: repeat(2, minmax(0, 1fr)); padding: var(--ui-s4); gap: var(--ui-s4); }
  .ui-crm__figures dd.ui-num { font-size: var(--ui-fs-title); }
  .ui-crm__figures dd.ui-num .ui-small { display: block; margin-top: 2px; }
}
/* One agenda entry: an icon, the what-when-who, and its actions. */
.ui-crm-act { grid-template-columns: auto minmax(0, 1fr) auto; }
.ui-crm-act__icon { display: grid; place-items: center; width: 36px; height: 36px; border-radius: 12px; background: var(--ui-accent-soft); color: var(--ui-accent-deep); align-self: start; margin-top: 2px; }
.ui-crm-act.is-late .ui-crm-act__icon { background: var(--ui-bad-soft); color: var(--ui-bad); }
.ui-crm-act.is-closed .ui-crm-act__icon { background: var(--ui-surface-2); color: var(--ui-ink-3); }
.ui-crm-act.is-closed .ui-row-item__primary { color: var(--ui-ink-2); }
.ui-crm-act .ui-row-item__primary a { color: inherit; text-decoration: underline; text-decoration-color: rgba(23, 20, 43, .25); text-underline-offset: 3px; }
/* The prospect's name was a 141×20 target (FACT-ENGAGEMENT m9, 2026-09-24). Vertical padding on an
   inline link grows its hit box, not its line; position: relative lets that box win over the date line
   under it (plain text). 20 + 2 × 12 = 44 px, and the row reads exactly as before. */
.ui-crm-act .ui-row-item__primary a { position: relative; padding-block: 12px; }
.ui-crm-act .ui-row-item__secondary .ui-badge { margin-inline-start: var(--ui-s1); }
.ui-crm-act__actions { display: flex; align-items: center; gap: var(--ui-s1); }
@media (max-width: 899px) {
  .ui-crm-act { grid-template-columns: auto minmax(0, 1fr); row-gap: var(--ui-s2); }
  .ui-crm-act__actions { grid-column: 2; justify-content: flex-start; }
}
.ui-crm-day--late { box-shadow: inset 3px 0 0 var(--ui-bad), var(--ui-shadow); }
.ui-crm-day--late .ui-card__title { color: var(--ui-bad); }
.ui-crm-quote { display: flex; align-items: center; gap: var(--ui-s2); flex-wrap: wrap; justify-content: flex-end; }
.ui-crm-kinds { margin-bottom: var(--ui-s4); }
.ui-crm-kinds .ui-filter { display: inline-flex; align-items: center; gap: 6px; }
.ui-crm-when { margin-top: var(--ui-s4); }

/* «⋯»: a row's secondary actions. A <details>, so the menu is the plain .ui-menu under it. */
.ui-overflow { position: relative; }
.ui-overflow > summary { list-style: none; cursor: pointer; }
.ui-overflow > summary::-webkit-details-marker { display: none; }
.ui-overflow__menu { min-width: 13rem; top: calc(100% + 4px); }

/* La cloche: beside the search in the sidebar, beside the search in the phone header. */
.ui-side__tools { display: flex; align-items: center; gap: var(--ui-s2); margin: var(--ui-s3) 0 var(--ui-s2); }
.ui-side__tools .ui-side__search { margin: 0; flex: 1 1 auto; min-width: 0; }
.ui-bell { position: relative; flex: none; }
.ui-bell__btn { position: relative; }
.ui-bell--side .ui-bell__btn { background: var(--ui-surface-2); border-radius: var(--ui-r-sm); }
.ui-bell__count { position: absolute; top: 4px; inset-inline-end: 2px; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 999px; background: var(--ui-bad); color: #fff; font-size: 11px; font-weight: 700; line-height: 18px; text-align: center; font-variant-numeric: tabular-nums; box-shadow: 0 0 0 2px var(--ui-surface); }
.ui-bell__list { display: grid; gap: 2px; padding-bottom: var(--ui-s5); }
.ui-bell__item { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: var(--ui-s3); align-items: start; width: 100%; padding: var(--ui-s3); border-radius: 14px; text-align: start; color: var(--ui-ink); }
.ui-bell__item:hover { background: var(--ui-surface-2); }
.ui-bell__item .ui-dot { margin-top: 6px; }
.ui-bell__icon { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 12px; background: var(--ui-surface-2); color: var(--ui-ink-2); }
.ui-bell__item.is-unread .ui-bell__icon { background: var(--ui-accent-soft); color: var(--ui-accent-deep); }
.ui-bell__text { display: grid; gap: 2px; min-width: 0; }
.ui-bell__title { font-weight: 500; }
.ui-bell__item.is-unread .ui-bell__title { font-weight: 700; }
.ui-bell__body { font-size: var(--ui-fs-small); color: var(--ui-ink-2); }
.ui-bell__when { font-size: var(--ui-fs-micro); color: var(--ui-ink-3); }
.ui-bell__empty { padding: var(--ui-s4) var(--ui-s2); }

/* «+ Ajouter … au catalogue» IN the flow under the field (owner review M1): it no longer
   floats over Qté and P.U. HT, so a tap meant for the price cannot land on it. */
.ui-typeahead__inline-add { display: flex; align-items: center; gap: var(--ui-s2); width: 100%; min-height: var(--ui-tap); margin-top: var(--ui-s1); padding: 0 var(--ui-s3); border-radius: 12px; background: var(--ui-accent-soft); color: var(--ui-accent-deep); font-weight: 600; text-align: start; }
.ui-typeahead__inline-add:hover, .ui-typeahead__inline-add.is-active { box-shadow: inset 0 0 0 2px var(--ui-accent); }
.ui-typeahead__inline-add span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-msg--you:has(.ui-msg__unanswered) { flex-wrap: wrap; row-gap: var(--ui-s1); }
.ui-msg__unanswered { flex-basis: 100%; margin: 0; color: var(--ui-ink-3); text-align: end; }

/* The sidebar says when there is more below (owner review): a shade at the edge that is
   scrolled to, drawn by the list itself — the covers move with the content, the shades do not. */
.ui-side__scroll {
  background:
    linear-gradient(#fff 30%, rgba(255, 255, 255, 0)) top / 100% 28px no-repeat local,
    linear-gradient(rgba(255, 255, 255, 0), #fff 70%) bottom / 100% 28px no-repeat local,
    radial-gradient(farthest-side at 50% 0, rgba(23, 20, 43, .16), rgba(23, 20, 43, 0)) top / 100% 12px no-repeat scroll,
    radial-gradient(farthest-side at 50% 100%, rgba(23, 20, 43, .16), rgba(23, 20, 43, 0)) bottom / 100% 12px no-repeat scroll;
}
/* The base rule pins a row's actions to column 2; an agenda entry has three columns. */
.ui-crm-act > .ui-row-item__actions { grid-column: 3; min-width: 0; }
@media (max-width: 899px) { .ui-crm-act > .ui-row-item__actions { grid-column: 2; } }
/* A list card's head sits on the same inset as its rows (the card itself has only 8 px). */
.ui-crm .ui-card--list > .ui-card__head { padding: var(--ui-s3) var(--ui-s3) 0; margin-bottom: var(--ui-s2); }
.ui-crm .ui-card--list > .ui-card__head .ui-card__title { font-size: var(--ui-fs-lead); }
/* The bell's head holds the title, «Tout marquer comme lu» and the close on ONE line. */
.ui-bell__sheet .ui-sheet__head { align-items: center; }
.ui-bell__sheet .ui-sheet__head > .ui-row { flex-wrap: nowrap; gap: var(--ui-s1); flex: none; }
.ui-bell__sheet .ui-sheet__head > .ui-row .ui-btn:not(.ui-btn--icon) { padding-inline: var(--ui-s3); font-size: var(--ui-fs-small); }
/* A role with fewer destinations (a Commercial: Commercial · Devis · + · Plus) fills the bar
   instead of leaving empty slots at its end: one equal column per item, whatever the count. */
@media (max-width: 899px) {
  .ui-tabbar { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); }
  .ui-crm__pipeline { scroll-padding-inline: var(--ui-s3); }
}
.ui-crm__figures dd.ui-small { font-weight: 400; }
/* Phone: «Planifier une visite · Ajouter un rappel · Noter un appel» as one even row of three
   thumb targets, icon over label, instead of three ragged full-width lines. */
@media (max-width: 899px) {
  .ui-crm__actions { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--ui-s2); }
  .ui-crm__actions .ui-btn { flex-direction: column; gap: 4px; min-height: 68px; padding: var(--ui-s2); border-radius: 16px; font-size: var(--ui-fs-small); line-height: 1.2; white-space: normal; text-align: center; }
}
/* A prospect row's only «action» is its stage: pinned to the edge, not floating mid-row. */
.ui-rows .ui-row-item__actions.ui-crm-stagecell { min-width: 0; justify-content: flex-end; align-self: start; }
/* The pipeline section: its title sits one step above the stage cards, like «Votre journée». */
.ui-crm__pipeline-wrap > .ui-home__day-title { margin-bottom: var(--ui-s3); }

/* ---- «Pack comptable» (2026-09-10): the /export hero — one period, one ZIP for the accountant.
   The screen's only hero surface; the single-file exports sit below it as secondary rows. */
.ui-pack__error { margin-bottom: var(--ui-s4); }
/* minmax(0, 1fr): a grid item's min-width is its content, so two 12rem date fields widened the
   card past a 390 px screen and the hero's overflow:hidden clipped the right edge off. */
.ui-pack { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ui-s4); }
.ui-pack > * { position: relative; z-index: 1; } /* above the hero's bloom */
.ui-pack__title { font: 700 var(--ui-fs-display)/1.15 var(--ui-display); letter-spacing: -0.02em; }
.ui-pack__lead { max-width: 46rem; }
.ui-pack__picker { display: grid; gap: var(--ui-s3); justify-items: start; }
.ui-pack__kinds { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); width: 100%; max-width: 26rem; background: rgba(255, 255, 255, .16); }
.ui-pack__kinds .ui-mode__btn { justify-content: center; padding: 0 var(--ui-s2); color: #fff; }
.ui-pack__kinds .ui-mode__btn[aria-pressed="true"] { background: #fff; color: var(--ui-accent-deep); }
.ui-pack__nav { display: flex; align-items: center; gap: var(--ui-s3); }
.ui-pack__nav .ui-btn--secondary { box-shadow: none; }
.ui-pack__period { font: 700 var(--ui-fs-head)/1.2 var(--ui-display); letter-spacing: -0.015em; min-width: 9ch; text-align: center; }
.ui-pack__period--custom { text-align: start; font-size: var(--ui-fs-title); }
.ui-pack__dates { width: 100%; max-width: 30rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr)); }
.ui-pack__picker > * { max-width: 100%; }
.ui-pack .ui-field__label { color: rgba(255, 255, 255, .9); }
.ui-pack__hint { margin: 0; }
.ui-pack__back.ui-btn--ghost { color: #fff; padding-inline: 0; text-decoration: underline; text-underline-offset: 3px; white-space: normal; text-align: start; }
.ui-pack__back.ui-btn--ghost:hover { background: none; color: #fff; }
.ui-pack__go { display: grid; gap: var(--ui-s2); justify-items: start; padding-top: var(--ui-s2); }
/* `.ui-card--hero a` (display: inline-block) outranks `.ui-btn`: without this the icon sits
   above the words and the label touches the bottom edge. */
.ui-pack .ui-pack__button { display: inline-flex; max-width: 100%; }
.ui-pack .ui-pack__button:hover { text-decoration: none; }
.ui-pack__button > span { white-space: normal; text-align: center; }
.ui-pack__notice:not(.ui-notice--warn) { color: var(--ui-ink); }
.ui-pack__notice { max-width: 46rem; }
.ui-export__others { margin-top: var(--ui-s7); }
.ui-export__hint { max-width: 46rem; margin-bottom: var(--ui-s3); }
.ui-export__type { max-width: 18rem; margin-bottom: var(--ui-s4); }
@media (max-width: 899px) {
  .ui-pack__title { font-size: var(--ui-fs-head); }
  .ui-pack .ui-pack__button { width: 100%; padding-inline: var(--ui-s4); line-height: 1.25; }
  .ui-pack__kinds { max-width: none; }
}

/* ── Les affaires : le tableau (2026-09-14) ───────────────────────────────────────────────
   A kanban is the one element in this product allowed to scroll sideways, and it does so in
   its own container — never the page body (responsive sweep, 2026-08). On a phone the columns
   become one column per screen width so a thumb swipes between them; the cards inside stay
   full width and every tap target stays at or above 44 px.

   TYPE: the first version of this block used `--ui-t1/t2/t3`, which are DEFINED NOWHERE — so
   every font-size here was an invalid declaration that the browser dropped, the cards
   inherited body size, and `measure-richness` scored the screen at 3 distinct sizes and 2
   weights against a product mean of 4.9 and 3.2. It was the flattest screen in the product
   and the cause was a typo'd token, not a design choice. It now uses the real scale and the
   rank the 2026-09-09 richness pass set for a list row: 11 px head, 17 px anchor, 13 px
   metadata, 600 on figures. */
.ui-kanban {
  display: grid;
  /* POSITION: the board is an overflow container, and a scroll container only clips an
     absolutely positioned descendant when it is also that descendant's containing block.
     Every card carries a `label.ui-sr` (position: absolute) for its «Déplacer vers» select;
     with .ui-kanban static those labels resolved against `.ui-shell` (position: relative),
     escaped the clip, and stretched the DOCUMENT to the width of the whole board — 1784 px at
     1440 and 1768 px at 390 — so the PAGE scrolled sideways on every width while the board
     itself was measured correct (366 px box, 2040 px of content). Measured 2026-09-14:
     setting this one property takes documentElement.scrollWidth to 1440 and 390 exactly. */
  position: relative;
  grid-auto-flow: column;
  grid-auto-columns: minmax(268px, 1fr);
  gap: var(--ui-s5);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  padding-bottom: var(--ui-s4);
  -webkit-overflow-scrolling: touch;
}
@media (max-width: 700px) {
  .ui-kanban { grid-auto-columns: minmax(min(84vw, 320px), 1fr); }
  /* The next column PEEKS on purpose (84vw) — the phone's way of saying the board goes on. Rule C2
     allows a cut edge only on a scroller that dissolves it, and this one cut «6 000,00» dead at the
     edge (check-phone CLIP-TEXT, 2026-09-25). So it dissolves like every other scroll row, and the
     same width of end padding lets the LAST column clear the fade once scrolled to it. */
  .ui-kanban {
    padding-inline-end: var(--ui-s5);
    -webkit-mask-image: linear-gradient(to left, transparent 0, #000 var(--ui-s5));
    mask-image: linear-gradient(to left, transparent 0, #000 var(--ui-s5));
  }
}
.ui-kanban__col {
  scroll-snap-align: start;
  background: var(--ui-surface-2);
  border-radius: var(--ui-r);
  padding: var(--ui-s4);
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--ui-s3);
}
.ui-kanban__head { display: flex; align-items: center; gap: var(--ui-s2); min-width: 0; }
.ui-kanban__dot { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.ui-kanban__name {
  font-size: var(--ui-fs-body); font-weight: 600; margin: 0; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ui-kanban__count {
  margin-left: auto; font-size: var(--ui-fs-micro); font-weight: 500; color: var(--ui-ink-3);
  font-variant-numeric: tabular-nums; background: var(--ui-surface); border-radius: 999px;
  padding: 2px var(--ui-s2);
}
/* The figure the screen exists to answer — «how much is sitting in négociation» — so it is
   the largest thing in the column, not a footnote under the name. */
.ui-kanban__total {
  margin: 0; font-size: var(--ui-fs-title); font-weight: 700; color: var(--ui-ink);
  font-variant-numeric: tabular-nums; letter-spacing: -0.015em;
  padding-bottom: var(--ui-s3); border-bottom: 1px solid var(--line);
}
.ui-kanban__cards { display: flex; flex-direction: column; gap: var(--ui-s3); min-width: 0; }
.ui-kanban__card {
  background: var(--ui-surface);
  border-radius: calc(var(--ui-r) - 4px);
  box-shadow: var(--ui-shadow);
  padding: var(--ui-s4);
  display: flex;
  flex-direction: column;
  gap: var(--ui-s2);
  min-width: 0;
  border-inline-start: 3px solid var(--line);
}
.ui-kanban__link { display: grid; gap: 2px; text-decoration: none; color: inherit; min-width: 0; }
.ui-kanban__link:hover .ui-kanban__title { text-decoration: underline; }
.ui-kanban__ref {
  font-size: var(--ui-fs-micro); font-weight: 500; color: var(--ui-ink-3);
  letter-spacing: .04em; text-transform: uppercase; font-variant-numeric: tabular-nums;
}
.ui-kanban__title { font-size: var(--ui-fs-lead); font-weight: 600; line-height: 1.3; overflow-wrap: anywhere; }
.ui-kanban__client {
  font-size: var(--ui-fs-small); color: var(--ui-ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ui-kanban__amount {
  font-size: var(--ui-fs-body); font-weight: 600; font-variant-numeric: tabular-nums;
  margin-top: var(--ui-s1); padding-top: var(--ui-s2); border-top: 1px solid var(--line);
}
.ui-kanban__move { min-height: 44px; width: 100%; font-size: var(--ui-fs-small); }
.ui-kanban__empty { color: var(--ui-ink-3); margin: 0; padding: var(--ui-s3) 0; font-size: var(--ui-fs-small); }

/* ── Les affaires sur le tableau commercial (2026-09-14) ──────────────────────────────────
   The band that replaced four empty prospect boxes. It carries the one figure the screen
   exists to answer — money in flight — at the display step, because a dashboard whose
   largest element is a section heading has no dominant number (RESEARCH-2026-09-04-SCIENCE
   §A2). Hairlines and the stage colour give the row the rank the measurement said was
   missing: the module's landing page scored 4 of 8 type steps and 0 hairlines. */
.ui-crm__deals-head { display: flex; align-items: baseline; gap: var(--ui-s3); flex-wrap: wrap; }
.ui-crm__deals-sum { margin: 0; display: flex; align-items: baseline; gap: var(--ui-s2); min-width: 0; }
.ui-crm__deals-sum .ui-num {
  font-size: var(--ui-fs-display); font-weight: 700; letter-spacing: -0.02em; color: var(--ui-ink);
}
.ui-crm__deals-head .ui-linkbtn { margin-inline-start: auto; }
.ui-crm__stage-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.ui-crm__stage-total {
  font-size: var(--ui-fs-lead); font-weight: 600; color: var(--ui-ink);
  padding-top: var(--ui-s2); border-top: 1px solid var(--line);
}

/* ── L'état vide : lui donner une présence (2026-09-14) ───────────────────────────────────
   The owner: «simple, blunt and blank», «it's a blunt user interface with colored buttons».
   Measured cause, from the empty-state audit: `.ui-empty` was four rules — padding, centred
   text, a title, a paragraph. No icon slot, no illustration, no accent. Grepping all 22
   `ui-empty` blocks for an `<x-icon>` returned ZERO. So a new customer walking Clients →
   Catalogue → Achats → Documents met four visually identical grey-text-on-white cards. The
   copy was never the problem — every one of those states explains itself well and offers its
   action. What was missing was PRESENCE.

   A tinted disc carrying the module's own glyph: it costs no complexity (one region, not a
   new box), it is the one place in a list screen where the brand can appear, and it makes the
   four screens above tell themselves apart at a glance instead of on reading. */
.ui-empty__mark {
  width: 56px; height: 56px; margin: 0 auto var(--ui-s4);
  display: grid; place-items: center; border-radius: 50%;
  background: var(--ui-accent-soft); color: var(--ui-accent);
}
.ui-empty__mark svg { width: 26px; height: 26px; stroke-width: 1.5; }
/* A filtered-empty is not a first-run: same shape, quieter, so «no result» never wears the
   same face as «you have not started». */
.ui-empty--filtered .ui-empty__mark { background: var(--ui-surface-2); color: var(--ui-ink-3); }

/* ======================================================================================
   O-6 · «THE MOBILE VERSION IS BAD REAL BAD, BUTTONS JUST THROWN AWAY» — THE LAYOUT HALF.
   2026-09-14. The paint half (invisible 1.03:1 control edges) was fixed the same day in §2.
   Everything below is a measurement taken at 390x844 (isMobile, hasTouch) by driving the
   four jobs the product exists for, not by reading the blade. Guarded by
   `_review-docs/harnesses/check-phone-jobs.mjs`, which fails on each of them.
   ====================================================================================== */
@media (max-width: 899px) {

  /* --- P1 · A CLIENT/SUPPLIER ROW PUT ITS BUTTONS IN THE MIDDLE OF ITS OWN TEXT --------
     The `--tiers` row is "name name" / "meta acts" / "more more". On 2026-09-09 the meta
     was the ICE alone and the note above §1885 reasoned it would keep its 145 px on one
     line. On 2026-09-10 «· Reste dû 57 600,00» was appended to the same span (owner review
     M8) and nobody re-measured: at 390 the meta column is 165 px (32..197) and
     «ICE 001789456000067 · Reste dû 57 600,00» wraps to TWO lines, the second one opening
     with an orphan «·», while the 149 px action block sits in the gap beside it. That is
     the screenshot the owner was looking at — text broken around buttons dropped into it.
     The actions now take their own line with ONE right edge under the text, which is the
     treatment the document page's five actions already got for the same complaint.
     Cost measured: the meta returns to one line, the row goes 133 -> 151 px. */
  .ui-row-item--tiers { grid-template-areas: "name name" "meta meta" "more more" "acts acts"; }
  .ui-row-item--tiers.has-no-meta { grid-template-areas: "name name" "more more" "acts acts"; }
  /* The action strip is now the full width of the row, and the row's one link is a stretched
     `::after` UNDER it (§1867). An empty full-width span would therefore swallow every tap on
     the left half of that line and the row would stop opening — so the strip carries no
     pointer events of its own and its buttons put them back. */
  .ui-rows .ui-row-item--tiers > .ui-row-item__actions { justify-content: flex-end; align-self: end; margin-top: var(--ui-s1); pointer-events: none; }
  .ui-rows .ui-row-item--tiers > .ui-row-item__actions > * { pointer-events: auto; }

  /* --- P2 · YOU COULD NOT SEE THE PRICE YOU HAD TYPED ----------------------------------
     The editor line was three EQUAL columns, so at 390 «QUANTITÉ», «P.U. HT» and
     «TOTAL HT» each got 90 px (88 of content). A quantity needs 50; a price does not:
     1 250 000 measures 101 px and 999999.99 measures 115, so the field clipped 13 and 27 px
     of a number the user had just entered and the total figure painted 4 px PAST the card.
     Five candidates were measured in the live editor; this one is the only one that fits the
     number without making the line taller (217 px in both): the quantity is given only what a
     quantity needs, the total is sized to its content at the small figure size, and the price
     takes the rest — 90 -> 128 px, 0 px clipped, total inside the card (342 < 354). */
  .ui-lines--editor .ui-line__main { grid-template-columns: 4rem minmax(0, 1fr) auto; }
  .ui-lines--editor .ui-line__total { font-size: var(--ui-fs-small); }
  /* «QUANTITÉ» is a longer word than a quantity is a number: at the small label size it
     measured 62 px over a 56 px column and leaned into «P.U. HT». The column is 4 rem and the
     label one step down, which fits it exactly (64/64) and still leaves the price 132 px. */
  .ui-lines--editor .ui-line__label { font-size: var(--ui-fs-micro); }

  /* --- P3 · A 15-DIGIT ICE PAINTED OVER THE NEXT COLUMN'S VALUE ------------------------
     `.ui-record__facts` is two 1fr columns = 147 px each at 390, and §25 forbids a number
     from breaking inside itself (`.ui-num { word-break: keep-all }`) — correctly, an ICE
     split over two lines is not an ICE. The two rules together put a 165 px ICE 18 px into
     the «IDENTIFIANT FISCAL» column: on /clients/{uuid} «002456789000034» and «41556677»
     were touching, with no gutter and no way to tell where one ended.
     A number that cannot break and cannot fit takes the whole row instead. `:has()` and not a
     new class, so it also covers the RC and the patente, and any identifier added later.
     (A `:has()` INSIDE a `:has()` is invalid and drops the whole rule — §1885 was bitten by
     that; this one is flat.) */
  .ui-record__facts > div:has(.ui-num) { grid-column: 1 / -1; }
  .ui-record__facts > div:has(.ui-num) > dd { font-variant-numeric: tabular-nums; }

  /* --- P4 · THE PAGER WAS THREE CONTROLS ON THREE DIFFERENT EDGES ----------------------
     `.list-foot` is `space-between` + `flex-wrap`, so on a phone it wrapped into: «Par /
     page» broken over two lines beside a select at 52..154, then a summary and two buttons
     at 43..378 — three left edges, none of them the list's own (20..370), and the label
     wrapped because the flex line had no room left. Measured on /documents at 390.
     One grid instead: the size control on its line, the summary and the pager on the next,
     «Précédent» and «Suivant» sharing the width equally so the row has one left and one
     right edge. */
  .list-foot, .ui-list-foot { display: grid; grid-template-columns: auto 1fr; align-items: center; column-gap: var(--ui-s2); row-gap: var(--ui-s3); }
  .list-foot__size, .ui-list-foot__size { grid-column: 1 / -1; justify-self: start; }
  .list-foot__size > .t-small, .ui-list-foot__size > .ui-small { white-space: nowrap; }
  .list-foot .pagination, .ui-list-foot .pagination { grid-column: 1 / -1; margin: 0; width: 100%; }
  .pagination { gap: var(--ui-s2); }
  .pagination__controls { flex: 1 1 auto; justify-content: flex-end; }
  .pagination__controls .btn { flex: 1 1 0; justify-content: center; padding-inline: var(--ui-s2); }

  /* --- P5 · THE PRIMARY ACTION IS THE ONE TARGET THAT MUST BE 1 CM ---------------------
     §C2 again: «the primary action is the largest target and the nearest to where the thumb
     rests». Both docked bars already sit at the bottom of the phone with the primary on the
     right — that part was right. Its height was 44 px (7.3 mm). The primary alone is raised
     to the thumb floor; its siblings stay at --ui-tap so the bar does not become a slab. */
  .ui-actionbar--sticky .ui-btn--primary,
  .ui-actionbar--phone .ui-btn--primary,
  .ui-sheet__foot .ui-btn--primary { min-height: var(--ui-thumb); }
}
