/*
 * Mobile overrides.
 *
 * Loaded after app.css and app-dark.css so source order decides where
 * specificity ties. EVERY rule in this file must live inside a
 * max-width media query -- that is what guarantees desktop cannot
 * regress. Do not add unscoped rules here.
 *
 * app.css is a committed build artifact with no source, so responsive
 * fixes go here rather than being hand-edited into its 18,000 lines.
 */

@media (max-width: 767.98px) {

    /* app.css:17943 sets #main{padding:0 2rem} and app.css:17961 sets
     * #main #main-content{padding:2rem}. At the 14px root those stack to
     * 112px of horizontal padding, leaving 248px of a 360px screen.
     * Collapse to a single source of padding: 339px usable. */
    #main {
        padding-left: 0;
        padding-right: 0;
    }

    /* The dark-scoped twin is listed alongside because app-dark.css re-declares
       this at higher specificity (html[data-bs-theme="dark"] #main #main-content).
       The §1 block below pairs them at <=576px; without pairing them here too,
       the 576-768px band would depend on which stylesheet happened to load. */
    #main #main-content,
    html[data-bs-theme="dark"] #main #main-content {
        padding: 1rem 0.75rem;
    }

    /* Touch targets. .btn-sm computes to ~28px and .btn-xs -- hand-defined
     * in the kanban partials as padding:2px 6px -- to ~21px, well under the
     * 44px minimum. .btn-xs carries the primary kanban card actions, several
     * of them icon-only. Fixed here rather than at 158 call sites. */
    .btn-xs,
    .btn-sm,
    .btn-group-sm > .btn {
        min-height: 44px;
        min-width: 44px;
        padding-top: 0.5rem;
        padding-bottom: 0.5rem;
    }

    .btn-xs {
        font-size: 0.8rem;
    }

    /* Keep icon-only buttons centred once they grow. */
    .btn-xs > i,
    .btn-sm > i {
        line-height: 1;
        vertical-align: middle;
    }

    /* .activities-table-wrapper sets overflow:hidden (for its border-radius),
     * which CLIPS the table instead of letting it scroll -- worse than having
     * no wrapper at all. Let it scroll horizontally on narrow screens.
     * Used by jobs/_step_details, tasks/reports and tasks/modal-scripts. */
    .activities-table-wrapper {
        overflow-x: auto;
    }

    /* Stacked card layout for the jobs listing.
     *
     * A 13-column table is unreadable at 360px even with horizontal scrolling:
     * you cannot see a row's client and its status at the same time. Each row
     * becomes a card and each cell a label/value line, using the data-label
     * written by createdCell in index.blade.php.
     *
     * Applies to every DataTables #table1 in the app, not just the jobs listing.
     * Labels come from data-label, set by createdCell on the server-side tables
     * and by drawCallback in datatables.js for the client-side ones.
     * DataTables sets inline widths on its scroll containers, so those have to
     * be overridden explicitly.
     *
     * ---------------------------------------------------------------------
     * .dt-stack_wrapper -- THE SAME STACK, OPTED INTO BY CLASS
     *
     * Every rule below was written against the id DataTables derives from the
     * table's own id: #table1 -> #table1_wrapper. That was enough while the app
     * had one DataTable per page. It is not enough on /jobs/update/{id}, whose
     * Finances tab mounts TWO of them (État financier and Demandes de paiement)
     * in one document. Only one element per document may be #table1, and the
     * job page must not claim that id anyway -- /finance/details/{id} renders
     * the same purchase-table partial and owns #table1 there.
     *
     * A table that cannot be #table1 got none of this: it stayed a full-width
     * grid and scrolled sideways at 390px, which for the Paiements panel would
     * have been a REGRESSION -- it was a stack of cards before it was a table.
     *
     * So every selector here is paired with a .dt-stack_wrapper twin, added to
     * the DataTables wrapper by the page that wants the stack (see
     * pages/jobs/partials/_finance_scripts). Strictly additive:
     *
     *   - specificity is computed per selector in a list, never for the list,
     *     so the (1,0,N) #table1_wrapper selectors keep their exact weight;
     *   - no declaration block is touched;
     *   - .dt-stack_wrapper is a new class name matched by nothing that shipped
     *     before, so the ~46 existing #table1 listings compute identically.
     *
     * Verified by capturing every property declared in §0/§7/§11 on /users,
     * /finance/tous, /report/job, /clients and /leaveBalances at 390px, light
     * and dark, before and after: byte-identical JSON.
     *
     * The stack needs BOTH halves to engage: this class, and data-label on the
     * cells. The labels come from drawCallback in the shared datatables.js, so
     * a page opting in has to build its table through those shared options
     * rather than a bespoke DataTable({...}) -- see the :has() note in §7. */
    #table1_wrapper .dataTables_scrollHead,
    #table1_wrapper .dataTables_scrollFoot,
    .dt-stack_wrapper .dataTables_scrollHead,
    .dt-stack_wrapper .dataTables_scrollFoot {
        display: none;
    }

    #table1_wrapper .dataTables_scrollBody,
    .dt-stack_wrapper .dataTables_scrollBody {
        max-height: none !important;
        overflow: visible !important;
    }

    /* DataTables writes inline widths on the table and its scroll wrappers for
       scrollX; they have to be overridden or the card stack keeps the old
       13-column width and the page scrolls sideways. */
    #table1_wrapper,
    #table1_wrapper .dataTables_scrollBody,
    #table1_wrapper table.dataTable,
    .dt-stack_wrapper,
    .dt-stack_wrapper .dataTables_scrollBody,
    .dt-stack_wrapper table.dataTable {
        width: 100% !important;
        max-width: 100% !important;
    }

    #table1_wrapper table.dataTable,
    #table1_wrapper table.dataTable tbody,
    #table1_wrapper table.dataTable tbody tr,
    #table1_wrapper table.dataTable tbody td,
    .dt-stack_wrapper table.dataTable,
    .dt-stack_wrapper table.dataTable tbody,
    .dt-stack_wrapper table.dataTable tbody tr,
    .dt-stack_wrapper table.dataTable tbody td {
        display: block;
        box-sizing: border-box;
    }

    /* Long job and client names have no spaces to wrap on. */
    #table1_wrapper table.dataTable tbody td,
    .dt-stack_wrapper table.dataTable tbody td {
        overflow-wrap: anywhere;
    }

    #table1_wrapper table.dataTable tbody tr,
    .dt-stack_wrapper table.dataTable tbody tr {
        border: 1px solid rgba(128, 128, 128, 0.3);
        border-radius: 8px;
        margin-bottom: 0.75rem;
        padding: 0.25rem 0.5rem;
    }

    #table1_wrapper table.dataTable tbody td,
    .dt-stack_wrapper table.dataTable tbody td {
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 1rem;
        border: 0;
        border-bottom: 1px solid rgba(128, 128, 128, 0.15);
        padding: 0.4rem 0;
        text-align: right;
    }

    #table1_wrapper table.dataTable tbody td:last-child,
    .dt-stack_wrapper table.dataTable tbody td:last-child {
        border-bottom: 0;
    }

    #table1_wrapper table.dataTable tbody td[data-label]::before,
    .dt-stack_wrapper table.dataTable tbody td[data-label]::before {
        content: attr(data-label);
        font-weight: 600;
        text-align: left;
        opacity: 0.7;
        flex: 0 0 auto;
    }

    /* A stacked cell is a flex row of [label | value]. When the value is itself
       a list -- the date/montant pairs of a payment request, which is the one
       thing that kept the Demandes de paiement panel a card list for so long --
       it has to wrap and stay on the right, or a request with four échéances
       pushes its own card open.

       display/flex-wrap/gap come from Bootstrap's d-flex flex-wrap gap-1 on the
       same element, which are !important utilities and identical at every width;
       only the alignment is width-dependent, so only the alignment is here.
       Class-scoped, so it exists only where a panel asks for it; nothing that
       shipped before carries it. */
    .dt-stack_wrapper table.dataTable tbody td .dt-stack-list {
        justify-content: flex-end;
        min-width: 0;
    }

    /* An EMPTY stacked table still has a header, and §7's :has(td[data-label])
       guard cannot see one -- there are no data cells to carry a label. So a
       search that matches nothing put a full-width 10-column header back above
       a single "aucun résultat" card and the listing scrolled sideways again.
       DataTables marks that placeholder row td.dataTables_empty, which is
       exactly the missing signal.

       Applies to #table1 too: the same defect was measured on every other
       stacked listing when filtered to zero -- /users 1039/341, /finance/tous
       1510/341, /clients 1270/341 at 390px, each a full-width header above one
       "aucun résultat" card. Same cause, same fix, and it can only ever hide a
       header that labels nothing. */
    #table1_wrapper table.dataTable:has(> tbody > tr > td.dataTables_empty) > thead,
    .dt-stack_wrapper table.dataTable:has(> tbody > tr > td.dataTables_empty) > thead {
        display: none;
    }
}


/* ==========================================================================
   Merged from branch worktree-mobile-ux-audit (Step 1 foundation).
   Kept verbatim on merge; overlaps the #main-content padding rule above
   at <=576px with dark-theme paired selectors. Consolidate in a follow-up.
   ========================================================================== */

/* ==========================================================================
   mobile.css — mobile-first corrections for the Genius app
   ==========================================================================

   WHERE THIS FILE SITS IN THE CASCADE -- read this before writing a rule that
   relies on winning a tie. (Corrected by step 20. This header used to say the
   file "loads LAST in welcome.blade.php and auth.blade.php ... so these rules
   win ties". That was true of welcome.blade.php on 2026-08-07, when step 1
   wrote it; the merge 31c6cb7 on 2026-08-21 moved the <link> back above
   @yield('styles') without touching the sentence. Two weeks true, a month
   wrong, and every rule written in between assumed the wrong order.)

   Loaded LAST in auth.blade.php, errors/403.blade.php and
   pages/jobs/satisfaction.blade.php. In those three documents these rules do
   win every specificity tie.

   NOT last in resources/views/welcome.blade.php -- the layout behind every
   authenticated page. There the <link> is near the top of the <head>, ABOVE
   @yield('styles') and above welcome's own <style> block. So on a tie this
   file LOSES to every stylesheet a page emits from @section('styles') -- the
   21 sheets steps 14 and 19 extracted, plus DataTables, FilePond, Toastify,
   selectize, flatpickr, CKEditor and fullcalendar -- and to every remaining
   per-page <style> block (60 views as of step 19).

   Write accordingly, as several sections below already do: element-qualified
   selectors in §4 (ul.nav-tabs, not .nav-tabs), a raised specificity in §11
   (div.selectize-dropdown), !important on exactly two properties in §9.

   Step 20 moved the link below @yield('styles') and reverted. The move is
   desktop-safe -- everything here is inside a max-width query, largest 991.98px
   -- but it flips one live tie: job-edit.css's unscoped `.btn{padding:8px 16px}`
   (0,1,0) against §1's `.btn-xs,.btn-sm,.btn-group-sm>.btn{padding-top/bottom:
   0.5rem}` (0,1,0), which changes 20 buttons on /jobs/update/{id} below 768px
   in light mode. The full write-up is at the end of welcome.blade.php's <head>.

   RULES OF THIS FILE
   1. Vendor app.css / app-dark.css are never edited. Overrides live here.
   2. Every rule is scoped to a max-width media query. Desktop is never
      touched. The largest breakpoint used here is 576px, which sits below
      the 768px tablet breakpoint and the 1200px sidebar breakpoint.
   3. Mazer re-declares many rules in app-dark.css at HIGHER specificity,
      prefixed with html[data-bs-theme="dark"]. A plain override silently
      fails in dark mode, so paired selectors are required. See §1.

   Step 1 (foundation) is implemented below. Later steps append new sections.
   ========================================================================== */


/* --------------------------------------------------------------------------
   §1  Reclaim the padding tax
   --------------------------------------------------------------------------
   app.css       #main #main-content                        { padding: 2rem }
   app-dark.css  html[data-bs-theme="dark"] #main #main-content { padding: 2rem }

   The root font-size is 14px (set inline in both layouts), so 2rem = 28px per
   side. Combined with .card .card-body's 1.5rem, a 360px phone was left with
   only 247px of usable content width — 31% lost to padding before a single
   character is drawn.

   Measured effect of this section:
     360px viewport : 247px -> 299px usable  (+52px, +21%)
     390px viewport : 277px -> 329px usable  (+52px, +19%)

   Both the plain and the dark-scoped selector are listed for each rule. The
   dark selector out-specifies the plain one, so omitting it would leave dark
   mode — the theme actually in use — unfixed.
   -------------------------------------------------------------------------- */

@media (max-width: 576px) {

    #main #main-content,
    html[data-bs-theme="dark"] #main #main-content {
        padding: 1rem 0.75rem;
    }

    .card .card-body,
    html[data-bs-theme="dark"] .card .card-body {
        padding: 0.875rem;
    }

}


/* --------------------------------------------------------------------------
   §2  Readable type floor
   --------------------------------------------------------------------------
   Bootstrap sizes badges at 0.85em and small buttons at 0.875rem. Against the
   14px root that lands at 11.9px and 12.25px, and some pages push .btn-sm
   down to 11.2px. Nudge the two worst offenders to a 12px floor on phones.

   Deliberately NOT touched: SVG chart labels (tspan/title, 10-11px). Those
   belong to ApexCharts' own layout maths — resizing them here would misalign
   the charts. They are handled in Step 6.
   -------------------------------------------------------------------------- */

@media (max-width: 576px) {

    .badge {
        --bs-badge-font-size: 12px;
    }

    .btn-sm,
    .btn-group-sm > .btn {
        --bs-btn-font-size: 12px;
    }

}


/* --------------------------------------------------------------------------
   §3  Horizontal overflow safety net
   --------------------------------------------------------------------------
   No page currently overflows the document horizontally — wide tables are
   held inside DataTables' own scroll containers (that nested-scroll problem
   is Step 4's job, not this file's). This section is a guard so that content
   added later cannot introduce a page-level horizontal scrollbar.

   Scoped to media elements only. Tables are deliberately excluded: forcing
   max-width on a table would collapse its columns and defeat the horizontal
   scrolling that currently makes wide tables reachable at all.
   -------------------------------------------------------------------------- */

@media (max-width: 576px) {

    img,
    svg:not(.apexcharts-svg),
    video,
    pre {
        max-width: 100%;
    }

    img {
        height: auto;
    }

}


/* ==========================================================================
   §4  Shared chrome — touch targets in the navbar and the drawer
   --------------------------------------------------------------------------
   Plan step 2. Measured at 390px, on every page of the app:

     notification bell   20x20      messages icon   20x20
     leave-balance link  20x20      avatar          39x39
     theme switch        30x18      burger          46x46  (already fine)
     ordinary .btn       91x36      bare checkbox   13x13

   §0 above already floors .btn-sm / .btn-xs / .btn-group-sm at 44px. It does
   NOT cover a plain .btn, which is what every form's Confirmer/Annuler pair
   uses, nor the navbar, nor bare inputs. That is what this section adds.
   ========================================================================== */

@media (max-width: 767.98px) {

    /* All four navbar controls are the same shape: a bare <a data-bs-toggle=
       "dropdown"> wrapping a 20px glyph, so the tappable area is the glyph.
       Give each a 44px box and centre its contents. */
    header .navbar-top .dropdown > a[data-bs-toggle="dropdown"] {
        min-width: 44px;
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    /* Those anchors carry a count badge pinned with Bootstrap's .top-0
       .start-100 .translate-middle utilities -- all of which are !important,
       hence the !important here. Anchored to the corner of a box that just grew
       from 20px to 44px, the badge would drift well clear of the glyph it
       counts, so re-pin it over the icon instead. */
    header .navbar-top .dropdown > a[data-bs-toggle="dropdown"] > .badge {
        top: 5px !important;
        left: auto !important;
        right: 3px !important;
        transform: none !important;
    }

    /* An ordinary button. 36px measured; .btn-sm and .btn-xs are handled in §0.
       min-width is deliberately NOT set: these buttons carry words, so they are
       already wide, and forcing 44px would inflate icon-only buttons that sit
       inside table cells. */
    .btn {
        min-height: 44px;
    }

    /* Native, unstyled checkboxes and radios -- 13x13 as the browser draws them.
       24px is the WCAG 2.5.8 (AA) minimum; a literal 44px box looks broken in a
       dense form, so the height goes on the row instead, just below.
       .form-check-input (Bootstrap's own, including the theme switch) and
       .btn-check (visually hidden, its <label class="btn"> is the real target)
       are both excluded -- resizing either would deform a control that is
       already handled. */
    input[type="checkbox"]:not(.form-check-input):not(.btn-check),
    input[type="radio"]:not(.form-check-input):not(.btn-check) {
        width: 24px;
        height: 24px;
    }

    /* Give the row around a check control a full-height target, so the label is
       as tappable as the box. */
    .form-check {
        min-height: 44px;
        display: flex;
        align-items: center;
        flex-wrap: wrap;
    }

    /* Matches a bare <label for="..."> as well as .form-check-label: the login
       page's "Afficher le mot de passe" carries no class, and the label is the
       real target for a Bootstrap check (the 18px box is too small to aim at,
       but clicking the label toggles it). 24px is the WCAG 2.5.8 (AA) floor. */
    .form-check label {
        min-height: 24px;
        display: inline-flex;
        align-items: center;
    }

    /* Step 12. The two v0 job forms put a FilePond uploader inside a
       .form-check -- markup that exists nowhere else in the app (grep: only
       pages/jobs/v0/edit and v0/create, plus their two unreachable .old twins).
       FilePond's root has no in-flow children -- its drop label is
       position:absolute -- so once the rule above turns .form-check into a flex
       container the root's min-content width is 0 and it collapses. Measured on
       /v0/jobs/update/1511 at 360px: .filepond--root 0px wide, and the
       "Drag & Drop le fichier ou Browse" label reflowed to 65px x 128px and
       painted on top of the "Email" radio 7px above it. At >= 768px .form-check
       is display:block again and the widget was always drawn correctly, which
       is why this never showed at desktop. Child selector, (0,2,0), so it beats
       filepond.css's own .filepond--root (0,1,0) whichever loads last; neither
       app.css nor app-dark.css mentions filepond, so no paired dark selector is
       needed. */
    .form-check > .filepond--root {
        flex: 1 1 100%;
        width: 100%;
    }

    /* A one-line textarea is a 36px target. Nothing violates this today; it is
       a floor for the form work in later steps. */
    textarea,
    textarea.form-control {
        min-height: 44px;
    }

    /* The drawer header spent 195px before the first menu item, and 90px of
       that was invisible: the drawer starts at y=0 but the navbar is a static
       90px band painted over it (equal z-index, later in the DOM), so the top
       third of the 200x150 logo was always hidden. Only 90px of it ever showed.
       Measured 90px at 360, 390 and 767px alike.
       Clearing the occluded band and shrinking the logo gives a logo that is
       fully visible for the first time, and still lifts the first menu item
       from y=203 to y=169.
       The logo is sized by a style="" attribute in sidebarr.blade.php, which no
       stylesheet can outrank, hence !important -- the alternative is editing
       markup that desktop shares. */
    #sidebar .sidebar-header {
        padding-top: 90px;
    }

    /* The markup wraps the logo in .d-flex.justify-content-between, which is what
       a header with a logo AND a close button on the right wants. This drawer has
       only the logo, so "space between" left it hard against the left edge with
       the whole rail empty to its right. Centred here rather than in the markup
       because that same markup is what desktop renders.

       Bootstrap's justify-content-between is itself !important, so matching it is
       the only way to win; this is not specificity padding. The sidebar's close
       control is position: absolute and so is unaffected by the flex change. */
    #sidebar .sidebar-header > .d-flex {
        justify-content: center !important;
    }

    /* 56px was chosen when the only goal was clearing the 90px occluded band and
       lifting the first menu item. With the band cleared there is room to give the
       mark back some presence: 72px still leaves the first item above where it
       started, and the logo is the one thing in this drawer that should read
       instantly. */
    #sidebar .sidebar-header .logo img {
        height: 72px !important;
        width: auto !important;
        max-width: 100%;
        object-fit: contain;
    }

}


/* ==========================================================================
   §5  Tab strips — one scrolling row, labels intact
   --------------------------------------------------------------------------
   Plan step 4. Measured at 390px before this section, every tab clicked:

     /leaveRequests           3 tabs on 3 rows, strip 110px tall
     /hr-reports/evaluations  2 tabs on 2 rows, strip  95px tall
     /jobs/clientForm/{id}    4 tabs, one row, every label hidden
     /jobs/update/{id}        7 tabs, every label hidden, strip forced to
                              600px inside a 351px overflow-x:auto parent, so
                              3 tabs sat off-screen behind a 15px desktop
                              scrollbar drawn inside the card
     /evaluations/view/{id}   strip scrollWidth 381 vs clientWidth 368

   Bootstrap's .nav is flex-wrap:wrap and no strip in this app sets overflow-x,
   so a strip that does not fit stacks into rows. One row that scrolls is both
   shorter and predictable: a tab is always in the same place, just further
   along. Hiding the labels instead (what two of these pages did) trades a
   scroll you can read for a scroll you cannot.

   Scoped to 991.98px, not 767.98px, because jobs/edit and jobs/clientForm
   already declare their own strips "mobile" at <= 992px in their page <style>;
   a narrower band here would leave 768-992px half-converted. It is still a
   max-width query, so the 1280/1440px desktop gate cannot see it.

   SELECTORS ARE ELEMENT-QUALIFIED (ul.nav-tabs, not .nav-tabs) ON PURPOSE.
   welcome.blade.php links mobile.css at line 24, ABOVE @yield('styles') at
   line 56 -- the note on line 82 used to deny this and now documents it -- so
   a per-page <style> wins every specificity tie. Three of these five pages style
   `.nav-tabs .nav-link` at exactly (0,2,0); `ul.nav-tabs:not(.flex-column)
   .nav-link` is (0,3,1) and beats them regardless of source order.

   :not(.flex-column) guards a vertical pill nav, which must never be turned
   into a horizontal scroller. Nothing in the app uses one today.
   ========================================================================== */

@media (max-width: 991.98px) {

    ul.nav-tabs:not(.flex-column),
    ul.nav-pills:not(.flex-column) {
        flex-wrap: nowrap;
        overflow-x: auto;
        overflow-y: hidden;
        -webkit-overflow-scrolling: touch;
        /* min-width:auto is the default for a flex item, so a strip that is
           itself a flex item would grow to fit its tabs and never scroll. */
        min-width: 0;
    }

    /* The affordance. The browser draws a ~15px horizontal scrollbar WITH
       arrow buttons inside the card (screenshot-confirmed on /jobs/update);
       4px reads as a hint rather than a control, and unlike a CSS edge-fade it
       appears only when the strip really does overflow -- no stylesheet can
       compare scrollWidth to clientWidth, so a fade would also paint on the
       strips that fit. On iOS/Android this is an overlay scrollbar: zero
       layout cost, visible while scrolling, which is the platform behaviour. */
    /* Chrome/Safari ignore every ::-webkit-scrollbar rule on an element that
       also sets scrollbar-width, and their `thin` is a 10px bar WITH arrow
       buttons (measured: offsetHeight 54 vs clientHeight 44). So hand
       scrollbar-width to the engines that have no ::-webkit-scrollbar at all
       -- Firefox -- and let the pseudo-elements below serve everything else. */
    @supports not selector(::-webkit-scrollbar) {

        ul.nav-tabs:not(.flex-column),
        ul.nav-pills:not(.flex-column) {
            scrollbar-width: thin;
            scrollbar-color: rgba(128, 128, 128, 0.45) transparent;
        }

    }

    ul.nav-tabs:not(.flex-column)::-webkit-scrollbar,
    ul.nav-pills:not(.flex-column)::-webkit-scrollbar {
        height: 4px;
    }

    ul.nav-tabs:not(.flex-column)::-webkit-scrollbar-track,
    ul.nav-pills:not(.flex-column)::-webkit-scrollbar-track {
        background: transparent;
    }

    ul.nav-tabs:not(.flex-column)::-webkit-scrollbar-thumb,
    ul.nav-pills:not(.flex-column)::-webkit-scrollbar-thumb {
        background: rgba(128, 128, 128, 0.45);
        border-radius: 4px;
    }

    /* .nav-justified and .nav-fill give every item flex-basis:0; flex-grow:1,
       which is exactly what squeezes a label until it wraps or truncates
       ("Demandes En Cour", "Détail des deman"). In a scroller each tab should
       be as wide as its own label and no wider. */
    ul.nav-tabs:not(.flex-column) > .nav-item,
    ul.nav-pills:not(.flex-column) > .nav-item {
        flex: 0 0 auto;
    }

    /* nowrap is what actually keeps the label on one line once the item is
       free to size itself; 44px is the touch-target floor from §4. */
    ul.nav-tabs:not(.flex-column) .nav-link,
    ul.nav-pills:not(.flex-column) .nav-link {
        white-space: nowrap;
        min-height: 44px;
    }

}


/* ==========================================================================
   §6  Selectize — stop a selected value escaping its own box
   --------------------------------------------------------------------------
   Plan step 5. The visible defect on /jobs/update/{id} at 360px was the
   Client and Marque values rendering OUTSIDE their control: the text sat on
   top of the neighbouring field and "HENKEL ALKI DISTRIBUTION" crossed the
   right edge of the screen, which was that page's entire 22px of horizontal
   document overflow. Measured chain, 360px, before the grid fix:

     .col-4                 108px
     .selectize-control     85px   -- it is ALSO the .form-control, so it
                                      carries padding 8px 12px 8px 37.5px
                                      (the 37.5px is .has-icon-left)
     .selectize-input       33.5px -- width:100% of an 85px border-box minus
                                      51.5px of padding and 2px of border
     .item                  90px   -- 56px wider than the box it lives in

   So the widget was not "collapsing": it was doing exactly what the box model
   said. The primary fix is in the markup -- those columns now stack below
   768px (col-4 -> col-md-4), which gives the control ~300px and the value
   fits on one line. This section is the backstop for the case markup cannot
   reach: a single value longer than the control.

   A/B verified: with the grid fixed but this section removed, a 51-character
   unbroken value still pushed the document to 523px inside a 360px viewport
   (163px of overflow). Ordinary client names contain spaces and wrap, so this
   guards the long-token case rather than today's data.

   overflow-wrap:anywhere (not break-word) is deliberate -- only `anywhere`
   lowers an element's min-content width, which is what lets the control
   shrink at all. It is applied to every selectize control because the widget
   is loaded globally from welcome.blade.php and the same narrow-column
   arrangement exists on the dashboard, paiments, statements and step forms.

   The ellipsis is restricted to .single, where .item is a bare text node.
   The remove_button plugin makes .item an inline-flex wrapper around a text
   span AND a remove link, so overflow:hidden there would clip the "x" and
   leave the tag impossible to delete. Those are left to wrap instead.
   ========================================================================== */

@media (max-width: 767.98px) {

    .selectize-control .selectize-input {
        overflow-wrap: anywhere;
    }

    .selectize-control.single:not(.plugin-remove_button) .selectize-input > .item {
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

}


/* ==========================================================================
   §7  Card-stacked listings — finish the job §0 started
   --------------------------------------------------------------------------
   Plan step 6. §0 above turns every DataTables #table1 into a stack of
   labelled cards below 768px, and hides the column header because a header
   labels nothing once the cells carry their own data-label. But it hides
   `.dataTables_scrollHead`, and THAT ELEMENT ONLY EXISTS WHEN A PAGE PASSES
   scrollX: true. Five views do (jobs/index, jobs/cloture, jobs/v0/index,
   jobs/v0/cloture, statements/index); the other forty-one #table1 views do
   not, so on those the original <thead> stayed in the document at its full
   natural width, above cards that are 341px wide and do not line up with it.

   Measured at 390px before this section, on the .table-responsive that holds
   the stack (scrollWidth / clientWidth):

     /users          848 / 341     header 119px tall
     /report/job    1982 / 341     header 119px tall
     /finance/tous  1510 / 341     header  97px tall
     /leaveBalances  433 / 341     header  97px tall
     /jobs           934 / 341     header   0px tall  (the scrollX remnant)

   So 29 of the app's listings scrolled sideways to show a header row over
   blank space, and the /jobs figure is the whole of the "dataTables_scrollBody
   computes overflow-x: visible, so wide content is clipped" report: the only
   thing wider than that box was the zero-height <thead> DataTables leaves in
   the body table for column sizing. Nothing visible was ever clipped there,
   and after this section the box measures 341/341 — there is no wide content
   left for `overflow: visible` to fail to scroll.

   THE :has() GUARD IS LOAD-BEARING, NOT DECORATION. Three listings build
   their DataTable with page-local options instead of the shared initialiser
   in datatables.js, so its drawCallback never runs and their cells get no
   data-label: /statements, /clients/rapport and (before this step) the
   tasks/reports table on /report/activity. On those the header is the only
   place the column names appear, so hiding it would make them worse, not
   better. :has(td[data-label]) says exactly "hide the header only once the
   cards carry their own labels", and re-evaluates live when drawCallback
   adds the attributes. A browser without :has() drops the rule and keeps
   today's behaviour, which is the safe direction to fail in.

   TRADEOFF, STATED PLAINLY: the header is also where DataTables puts the
   sort control and where a dozen views append their per-column filter input,
   so a card-stacked listing loses both below 768px. That is already true of
   the five scrollX views and has been since §0 shipped; this makes the other
   24 consistent with them rather than inventing a new rule. Those filter
   inputs measure 25-93px at 390px (one to five characters), and reaching the
   tenth one means scrolling a strip that no longer matches anything below it,
   so what is lost was not usable. The global "Search:" box, the page-length
   menu and paging are untouched. Restoring per-column filtering on a phone
   properly needs a responsive control built in JS, which this round cannot
   touch.
   ========================================================================== */

@media (max-width: 767.98px) {

    #table1_wrapper table.dataTable:has(> tbody > tr > td[data-label]) > thead,
    .dt-stack_wrapper table.dataTable:has(> tbody > tr > td[data-label]) > thead {
        display: none;
    }

    /* A Bootstrap column inside a stacked cell blows the card open.
       §0 makes the <td> a flex row of [::before label | value]; several views
       wrap the value in a .col-12 (the avatar+name cells on /users,
       /leaveBalances, /leaveRequests), and .col-12 is `flex: 0 0 auto;
       width: 100%` — 100% of the CELL, on top of the label beside it. The
       cell therefore asks for label + 100% and the card overflows by exactly
       the label's width: /users 380/341 and /leaveBalances 407/341 once the
       header above was gone. Sizing those columns to their content instead
       takes both to 341/341 — no sideways scroll left anywhere on the stack.

       min-width: 0 does NOT fix this and was measured doing nothing: the
       problem is an explicit width: 100%, not an unshrinkable min-content
       floor, so there is no flex-shrink for it to unlock. */
    #table1_wrapper table.dataTable tbody td > [class*="col-"],
    .dt-stack_wrapper table.dataTable tbody td > [class*="col-"] {
        width: auto;
        max-width: 100%;
    }

    /* [added by step 8] A COLUMN PERCENTAGE SURVIVES INTO THE STACK.
       Eighteen views hand-write `<td style="width: N%">` for their desktop
       column widths -- clients/list, fournisseurs/list, meeting-rooms/list,
       trips/requests, couts, marques, modes, typeTaches, societes, salaries,
       tasks/list, jobs/indexClient and more. §0 makes that <td> a flex row,
       but an inline width still applies to it, so the whole card row was
       pinned to N% of the card: 65px at 390px, 59px at 360px on /clients. With
       §0's `overflow-wrap: anywhere` the min-content width of the value is
       then one character, and /clients rendered "HENKEL TUNISIE" as fourteen
       stacked letters in an 11px column, beside a client logo squeezed to
       2.7px (1px at 360px). Screenshot-confirmed before and after.

       Found while measuring §10's avatars: the 2.7px logo and the vertical
       name are the same defect, and flex-shrink alone only fixes the logo.
       A percentage column width means nothing once the row is a card, so it
       is dropped for the stack only -- !important because it is inline.

       Measured at 390px, every #table1 listing in the app: /clients name
       cell 11x293 -> 118x32 and the page 9050 -> 6648px; /meeting-rooms
       1002 -> 900; /trips/requests 2517 -> 2232. /users, /disabled,
       /users/clients, /leaveBalances, /jobs, /finance/tous, /report/job,
       /report/activity and /products are unchanged to the pixel, and
       horizontal document overflow stays 0 on every one. */
    #table1_wrapper table.dataTable tbody td,
    .dt-stack_wrapper table.dataTable tbody td {
        width: auto !important;
    }

}


/* ==========================================================================
   §8  Demandes de Congés — the filter card and the date cells
   --------------------------------------------------------------------------
   Plan step 6. Both rules are driven by marker classes added in
   pages/leaves/partials/*, and both exist only because the markup they
   correct has to keep its desktop behaviour untouched:

   .leaves-filter-field carries `style="width: 20%"` in _requests_filters,
   which is right for the three-across desktop row and is 20% of a 341px card
   on a phone: 68px. Measured at 390px, that gave two flatpickr date fields
   and the "Type de Congé" select 68px each, with their labels wrapping to two
   lines and the select clipping its options mid-word. An inline style
   outranks every stylesheet, so the override has to be !important; the class
   stays on the element and the inline 20% stays with it, which is what keeps
   the 1280/1440px row identical.

   .leaves-cell-date marks the cells whose whole content is one date. At 66px
   the "Créé" cell rendered "Le 04-08-2026 À 11:16" as four lines broken
   mid-number ("Le 04-08-" / "2026 À" / "11:16"). These tables already scroll
   horizontally inside .table-responsive (1179px in a 341px box on the pending
   tab), so letting the date claim the ~70px it needs costs scroll the reader
   is already paying and buys a date that can be read.
   ========================================================================== */

@media (max-width: 767.98px) {

    .leaves-filter-field {
        width: 100% !important;
        /* .mx-2 puts 0.5rem on each side; at width:100% that would push each
           field 16px past the flex line and give the card a scrollbar. */
        margin-left: 0 !important;
        margin-right: 0 !important;
    }

    .leaves-cell-date {
        white-space: nowrap;
    }

}


/* ==========================================================================
   §9  Text inputs and selects — the touch-target family §4 left out
   --------------------------------------------------------------------------
   Plan step 7. §4's own comment says it covers "the navbar, ... nor bare
   inputs", but what it actually floors is .btn, the native checkbox/radio box
   and textarea. A plain <input class="form-control"> was never touched, and
   Bootstrap gives it no min-height at all -- its height is whatever
   line-height 1.5 + .75rem of padding + 2px of border comes to. Measured at
   390px, on this step's pages and on the ones earlier steps reported:

     /evaluations/self        input.form-control            298.8 x 35.8
     /evaluations/self        select.form-select            298.8 x 35.8
     /evaluations/manager/318 input.form-control            298.8 x 35.8
     /qvt/settings            input.form-control-sm          80.0 x 31.2  (x18)
     /evaluations/competences DataTables search box         181.0 x 29.2
     /evaluations/competences DataTables length menu         63.8 x 29.2
     /leaveRequests           flatpickr date fields                 x 36    <- step 6

   Step 6 reported the flatpickr case and left it as "a global touch-target
   question, not this step's". This is that question, answered: one floor, in
   the section that already floors every other control.

   PAIRED DARK SELECTORS ARE REQUIRED HERE, not decoration. app-dark.css
   declares

     html[data-bs-theme="dark"] .form-control-sm { min-height: calc(1.5em + 0.5rem + 2px) }

   at (0,2,0). `input.form-control` is (0,1,1) and would LOSE to it in dark
   mode -- the theme the app ships -- leaving all 18 /qvt/settings inputs at
   31px while light mode measured clean. The paired selector is (0,2,1) and
   wins. Same trap mobile.css §1 documents and step 6 hit in column-filters.css.

   min-height rather than height: a .form-floating control is 58px and a
   textarea grows, and min-height cannot shrink either.
   ========================================================================== */

@media (max-width: 767.98px) {

    input.form-control,
    select.form-select,
    html[data-bs-theme="dark"] input.form-control,
    html[data-bs-theme="dark"] select.form-select {
        min-height: 44px;
    }

    /* EXCEPTION: a filter input sitting in a table header is not a target, it
       is the geometry column-filters.css measures against. That file reserves
       exactly `.5rem + 1.5em + .75rem + 2px` of cell padding while the focused
       input is out of the flow, which is Bootstrap's own .form-control formula;
       step 6 measured the resulting header jump at 0px and the arithmetic only
       holds while the input really is that tall. Flooring it at 44px would
       under-reserve by ~8px and put back a jump that step 6 removed -- on
       /statements and /clients/rapport, the two listings whose <thead> is still
       visible below 768px because their cells carry no data-label.

       So the two Bootstrap formulas are restated here rather than the floor
       being excluded with :not(), which would take the whole rule with it on a
       browser that does not parse a complex :not(). These inputs are 25-93px
       WIDE at 390px anyway (step 6) -- 44px of height would not have made one
       usable. */
    table thead th input.form-control,
    html[data-bs-theme="dark"] table thead th input.form-control {
        min-height: calc(1.5em + 0.75rem + 2px);
    }

    table thead th input.form-control-sm,
    html[data-bs-theme="dark"] table thead th input.form-control-sm {
        min-height: calc(1.5em + 0.5rem + 2px);
    }

}


/* ==========================================================================
   §10  Avatars and logos — a reserved box that keeps its shape
   --------------------------------------------------------------------------
   Plan step 8. The step was written to add width/height attributes against
   layout shift; measured, the images in this app are NOT a layout-shift
   source (app.css fixes .avatar img at 32x32 on both axes, and the content
   images carry an inline width AND height), but they ARE the wrong SHAPE
   below 768px, which is the other half of finding 3.8. Measured before this
   section, every avatar loaded, at 390px:

     /clients               client logos    2.7 - 8.7 x 32   (1 x 32 at 360px)
     /autorisationRequests  avatars         0 - 28.1  x 32   (two at 0 wide)
     /missionsRequests      avatars         0 - 24.7  x 32
     /attestationRequests   avatars         9.6 - 22.8 x 32
     /leaveRequests         avatars         0 - 19    x 32
     /posts comment avatars                 39 - 45   x 50
     /clients/stats         client logos    85 - 99.8 x 100  (129 of 152)

   TWO DIFFERENT CAUSES, hence two rules.

   1. .avatar is an inline-flex box inside a `d-flex flex-row` cell, so it is
      a flex item with the default flex-shrink:1. Its only content is an <img>
      which §3 above gives `max-width: 100%` -- and that zeroes the image's
      min-content contribution, so the automatic minimum size of .avatar is 0
      and the name beside it can squeeze the avatar to nothing. A/B verified:
      deleting `img { max-width: 100% }` and setting `.avatar { flex-shrink:
      0 }` produce byte-identical avatar geometry (/clients 10 x 32x32, page
      9050px either way), so this is that rule's side effect and not a
      pre-existing bug -- with mobile.css disabled entirely every avatar on
      every one of these pages measures 32x32. §3 stays as it is: it is the
      guard that keeps a wide image from scrolling the document, and it is
      correct for every image that is NOT inside a fixed-size box.

      Scoped to 767.98px because that is where the crushing stops: /clients
      measures 10 x 32x32 at 768, 992 and 1280px already.

   2. .client-item logos on /clients/stats are not flex items at all -- the
      .avatar there sits in a block <a> inside a col-4/col-sm-3/col-md-2 grid
      cell. The image asks for 100x100 inline, .img-fluid caps it at
      max-width:100%, and the column is only 85-99px wide, so the WIDTH
      shrinks while the inline `height: 100px` does not: a .rounded-circle
      rendered as an ellipse, 129 times on one page. aspect-ratio pins the
      second axis to the first, so the box stays square at whatever width the
      column allows AND is fully reserved before the image loads.

      `height` must be !important to beat the inline style; there is no
      markup fix available because that <img> is built by an inline <script>.

      Scoped to 991.98px, not 767.98px, because the distortion is worst at
      768px (92.4 x 100, 127 images) where col-md-2 packs six logos into a
      sidebar-less 768px row. Measured clean at 900px and at every width from
      992 to 1440, where the rule is a no-op anyway: the column is wider than
      100px, so width stays 100px and aspect-ratio 1/1 gives back exactly the
      100px height the inline style already asked for.
   ========================================================================== */

@media (max-width: 767.98px) {

    .avatar {
        flex-shrink: 0;
    }

    /* Same failure, different wrapper: /posts builds its comment rows in an
       inline <script> (news.blade.php:465), so the 50x50 avatar box there is
       not a .avatar and cannot be reached by editing markup. Measured at
       390px: 39 / 43 / 45 x 50; at 360px: 32.3 / 35.6 / 37.3 x 50.
       Keyed off the DOM shape rather than a class, the way column-filters.css
       is, because the JS and the Blade twin agree on the shape and nothing
       else. A browser without :has() drops the rule and keeps today's
       behaviour, which is the safe direction to fail in. */
    .d-flex > .position-relative:has(> img.rounded-circle) {
        flex-shrink: 0;
    }

}

@media (max-width: 991.98px) {

    .client-item .avatar img {
        height: auto !important;
        aspect-ratio: 1 / 1;
    }

}


/* ==========================================================================
   §11  The long tail — a badge that is really a button, paging, breadcrumb,
        and the calendars
   --------------------------------------------------------------------------
   Plan step 11. Measured across the ~45 remaining routes at 360/390px before
   anything was changed. Four defects account for almost every sub-44 target
   left in the app outside the drawer, and all four are shared, so they are
   fixed once here rather than in twenty views.

   1. THE BADGE IS THE APP'S ACTION BUTTON. Eighteen listings render their row
      actions as `<button class="badge bg-warning">Modifier</button>` and
      `<a class="badge bg-success">Jobs</a>` rather than as .btn, so none of
      them is reached by §0's .btn-sm/.btn-xs floor or §4's .btn floor.
      Measured at 390px: Modifier 85.2 x 25.8, Supprimer 97.5 x 25.8,
      Jobs 39.8 x 20.4, Taches 53.7 x 20.4, an icon-only success badge
      27.6 x 21.8 — about 300 targets over /posts/update, /promos, /products,
      /societes, /mode, /marques, /meeting-rooms, /types, /salaires, /users,
      /disabled, /autorisation, /steps, /jobs, /archive/jobs, /cloture/jobs,
      /finance, /finance/tous and /finance/details.

      A <span class="badge"> — a status pill — is deliberately NOT matched:
      it is not a target and inflating it would wreck every status column.
      Only an <a> that has an href and a <button> are touched.

   2. PAGING. .page-link measured 32.4 x 32.9 (32 for a page number, 75 for
      "Précédent") on all 46 listings, and the strip ALREADY overflowed its
      own container: scrollWidth 354 in a clientWidth 341 box at 390px, with
      the last link's right edge at 378.6 — "Suivant" was partly unreachable
      before this step, not because of it. flex-wrap:wrap is what makes a 44px
      grid fit: the strip becomes two rows of 341/341 and nothing is cut.
      Cost is +44px per listing, stated in the plan.

   3. BREADCRUMB. `.breadcrumb-item > a` ("Dashboard") is 73.9 x 20 on every
      page in the app — the last sub-44 target in the shared chrome, reported
      by steps 7 and 10 and owned by nobody. +22px of page height, once.

   4. FULLCALENDAR SIZES ITSELF BY ASPECT RATIO, WHICH IS WRONG ON A PHONE.
      /holidays, /team/calendar and /reservations run FullCalendar 3, whose
      default aspectRatio 1.35 makes the view 341/1.35 = 253px tall at 390px.
      The month grid needs 360px, so .fc-scroller — which the library sizes
      with an inline height — scrolled internally and showed four of six week
      rows, the last one cut mid-cell (screenshot-confirmed). Releasing the
      scroller below 768px takes the view to 386px and the whole month is
      visible on the page's own scroll. Measured identical at 1440px in both
      files (view 772px, scroller 748/748), so the release is genuinely
      mobile-only; `height` must be !important because the library writes it
      inline. Its toolbar buttons are 31.5px tall and are floored too.

   PAIRED DARK SELECTORS ARE REQUIRED, not decoration. app-dark.css declares
   `html[data-bs-theme="dark"] .page-link` and `... .form-control` at (0,2,1),
   which beat `.pagination .page-link` (0,2,0) and `select.form-control`
   (0,1,1) outright, and `... .badge` (0,2,1) beats `button.badge` (0,1,1).
   Same trap §1 documents and steps 6, 7 and 10 all hit.
   ========================================================================== */

@media (max-width: 767.98px) {

    a.badge[href],
    button.badge,
    html[data-bs-theme="dark"] a.badge[href],
    html[data-bs-theme="dark"] button.badge {
        min-height: 44px;
        min-width: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    /* A bare <a> is the whole of the target in a card-stacked cell: /steps
       renders "Voir les types de tâches" as an unclassed anchor (159.3 x 22.5)
       and /societes the company name (71.6 x 22.5). Scoped to the stack so an
       inline link inside a paragraph is left alone. */
    #table1_wrapper table.dataTable tbody td a:not(.badge):not(.btn),
    .dt-stack_wrapper table.dataTable tbody td a:not(.badge):not(.btn) {
        min-height: 44px;
        /* min-width matters for the short ones: /societes links the company
           name, and "3SG" is a 29.1px-wide target even once it is 44px tall. */
        min-width: 44px;
        display: inline-flex;
        align-items: center;
    }

    /* §9 floored input.form-control and select.form-select but not
       select.form-control, which is what the dashboard agency filter, the
       /users/create form (7 of them) and /clients/finances use: 341.3 x 35.8. */
    select.form-control,
    html[data-bs-theme="dark"] select.form-control {
        min-height: 44px;
    }

    .pagination,
    html[data-bs-theme="dark"] .pagination {
        flex-wrap: wrap;
        row-gap: 4px;
    }

    .pagination .page-link,
    html[data-bs-theme="dark"] .pagination .page-link {
        min-width: 44px;
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    /* DataTables 2 (/statements) paginates with <button class="dt-paging-button">
       instead of <a class="page-link"> — 35.9-41 x 39.5 at 390px. Its own sheet
       declares `min-width: 1.5em` (22.5px) and `display: inline-block` at
       div.dt-container div.dt-paging button.dt-paging-button, and it is loaded
       through @section('styles') — i.e. BELOW mobile.css in welcome.blade.php,
       so it wins every specificity tie on source order. Matching its (0,3,3)
       would not be enough; hence !important on exactly the two properties it
       declares. min-height is not one of them, which is why the height above
       took without any help. */
    div.dt-container div.dt-paging button.dt-paging-button,
    html[data-bs-theme="dark"] div.dt-container div.dt-paging button.dt-paging-button {
        min-width: 44px !important;
        min-height: 44px;
        display: inline-flex !important;
        align-items: center;
        justify-content: center;
    }

    .breadcrumb-item > a,
    html[data-bs-theme="dark"] .breadcrumb-item > a {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }

    /* .breadcrumb is a flex row whose default align-items is `stretch`, so once
       the "Dashboard" anchor is 44px tall the sibling "/ <page title>" item
       stretches with it and its text sits at the TOP of the row while the link
       sits in the middle — found by reading the screenshot, not by measuring.
       Centring puts them back on the same line (li 90..134 with its text at
       100.8..123.3, exactly centred). */
    .breadcrumb,
    html[data-bs-theme="dark"] .breadcrumb {
        align-items: center;
    }

    /* The exact twin of §8's .leaves-filter-field, on the two finance listings:
       four (finances/index) and five (finances/all) filter wrappers carry
       `style="width: 20%"`, right for the one-line desktop row and 68px on a
       phone. Measured at 390px before: the selectize control 68px wide with a
       29px .selectize-input inside it, on /finance and /finance/tous. An inline
       style outranks every stylesheet, so this has to be !important; the class
       and the inline 20% both stay on the element, which is what keeps the
       1280/1440px row identical. .mx-2 is 0.5rem a side and at width:100% that
       would push each field 16px past the flex line. */
    .finance-filter-field {
        width: 100% !important;
        margin-left: 0 !important;
        margin-right: 0 !important;
    }

    /* §9 floored input.form-control, select.form-select and (above)
       select.form-control. Selectize replaces the <select> with a DIV that
       carries .form-control, so none of those reach it: the visible, clickable
       box is .selectize-input and it measured 36px on /finance, /finance/tous,
       /tasks, /report/activity, /leaveReports and /leaveBalances. */
    .selectize-control .selectize-input,
    html[data-bs-theme="dark"] .selectize-control .selectize-input {
        min-height: 44px;
    }

    /* DATATABLES 2 RENAMED EVERY CLASS §0 AND §7 TARGET.
       /statements is the app's one DataTables 2 listing: its container is
       .dt-container (still id="table1_wrapper", which is why §0's card stack
       does reach it) but its scrolling chrome is .dt-scroll-head /
       .dt-scroll-body / .dt-scroll-foot, not .dataTables_scrollHead etc. So
       §0 hid nothing there and the page rendered a 1237px-wide header clone
       above 285px cards — 32 sub-44 targets and the sideways scroll step 6
       recorded. Now that this step has given its <td>s a data-label, the same
       :has() guard §7 documents applies: hide the clone only once the cards
       carry their own labels. The guard sits on the wrapper because the clone
       table has no tbody of its own to test.
       Its length/search controls are .dt-input, which is neither .form-control
       nor .form-select, so §9's floor never reached them either (62 x 32 and
       199 x 34.5). */
    #table1_wrapper:has(table.dataTable > tbody > tr > td[data-label]) .dt-scroll-head,
    #table1_wrapper:has(table.dataTable > tbody > tr > td[data-label]) .dt-scroll-foot,
    .dt-stack_wrapper:has(table.dataTable > tbody > tr > td[data-label]) .dt-scroll-head,
    .dt-stack_wrapper:has(table.dataTable > tbody > tr > td[data-label]) .dt-scroll-foot {
        display: none;
    }

    #table1_wrapper .dt-scroll-body,
    .dt-stack_wrapper .dt-scroll-body {
        max-height: none !important;
        overflow: visible !important;
    }

    #table1_wrapper .dt-scroll,
    #table1_wrapper .dt-scroll-body,
    .dt-stack_wrapper .dt-scroll,
    .dt-stack_wrapper .dt-scroll-body {
        width: 100% !important;
        max-width: 100% !important;
    }

    .dt-container select.dt-input,
    .dt-container input.dt-input,
    html[data-bs-theme="dark"] .dt-container select.dt-input,
    html[data-bs-theme="dark"] .dt-container input.dt-input {
        min-height: 44px;
    }

    /* FullCalendar 3. The library writes `height` inline on .fc-scroller and
       on the view table from its aspectRatio maths, so !important is the only
       way to reach it from a stylesheet. app-dark.css declares no .fc rule at
       all, so no paired selector is needed here. */
    .fc-scroller {
        height: auto !important;
        overflow: visible !important;
    }

    .fc-view-container,
    .fc-view,
    .fc-view > table {
        height: auto !important;
    }

    .fc button.fc-button {
        min-height: 44px;
    }

}


/* ==========================================================================
   §12  What only exists while a modal is open
   --------------------------------------------------------------------------
   Plan step 16. A closed modal is display:none, which has zero size, so every
   metric this file was written against silently skipped it. /tasks was
   measured a dozen times across round 2 with four modals on it that nobody
   opened. Each of the seven modal partials was opened one at a time through
   bootstrap.Modal.getOrCreateInstance(el).show() and measured as a page in its
   own right at 360/390/768/1280. What that found:

     modal header .close       32 x 36.5   80 occurrences across 43 views
     modal header .btn-close   30 x 30     48 occurrences across 23 views
     .selectize-dropdown       28 x 13px   every option of every selectize
       .option                             control in the app, 151 of them in
                                           add-task-modal's Client alone

   The two dismiss buttons are the same defect twice: this app writes a modal
   header with Bootstrap 4's `.close` in 43 views and Bootstrap 5's `.btn-close`
   in 23, and neither is a .btn, so §0's .btn-sm floor and §4's .btn floor both
   miss them. The x-glyph is the only way out of a modal that has no Annuler
   button (the deliverables modal is one), so it is a primary target.

   THE SELECTIZE DROPDOWN IS THE FIRST THING ROUND 2 NEVER SAW, BECAUSE IT IS
   ALSO display:none UNTIL OPENED. §6 and §11 both worked on selectize and both
   measured only .selectize-input, the closed control. Opening one shows rows
   of 228.5 x 28 -- the actual target you tap to choose a value, on the 43 views
   that call `.selectize(`.
   Selectize's own sheet declares `padding: 5px 8px` on [data-selectable] and no
   height at all, so min-height does not collide with it; that matters here
   because selectize.default.min.css is linked from cdnjs at
   welcome.blade.php:43, BELOW mobile.css at line 24, and would win any tie.
   [data-selectable] rather than .option so the "add new" row in a create-enabled
   control is floored too. No paired dark selector: neither app.css nor
   app-dark.css contains the string "selectize" (grep: 0 hits in both).

   THE ROW IS GROWN WITH PADDING, NOT WITH FLEX CENTRING, AND THAT IS NOT A
   STYLE PREFERENCE. Selectize marks the matched substring by splitting the
   label: `SIKA <span class="highlight">TUNIS</span>IA`. Under `display: flex`
   each text run becomes an anonymous flex item, and a flex item's leading and
   trailing whitespace is stripped -- so the first version of this rule passed
   every metric (228.5 x 44, zero overflow) while rendering "SIKATUNISIA",
   "ROCHETUNISIE" and "ULYSSETUNISIE" with the space before every match eaten.
   Screenshot-confirmed, then replaced. Padding keeps the label in normal inline
   flow. 13px + the 18px line-height + 13px is exactly 44; a label that wraps to
   two lines lands at 62px, which is taller than it needs to be and is the price
   of not touching the inline formatting.

   `div.selectize-dropdown` (0,2,1) rather than `.selectize-dropdown` (0,2,0),
   because selectize's own `padding: 5px 8px` is declared at (0,2,0) and its
   sheet loads BELOW this file, so an equal-specificity override would lose the
   tie on source order. min-height alone did not need it; padding does.

   TRADEOFF: a 200px dropdown that showed 7 rows now shows 4.5. Selectize's
   max-height is left alone deliberately -- the dropdown is positioned below its
   input, and on a 660px phone viewport a taller list would run off the bottom
   of the screen instead of scrolling inside itself.

   CHECKED WHILE THE MODALS WERE OPEN AND DELIBERATELY NOT CHANGED HERE:

   - flatpickr's calendar, 39 x 39 day cells and 34 x 34 month arrows, 12.6px
     weekday letters. It is the second thing that only exists once opened, and
     it is reached from inside two of these modals -- but it is a global vendor
     widget on every date field in the app, not modal CSS. Its own sheet pins
     `.flatpickr-day { width: 14.2857143%; max-width: 39px; height: 39px;
     line-height: 39px }`, so the cell is ALREADY allotted 43.98px of a 307.9px
     popup and it is the 39px cap that shrinks it -- raising the floor means
     raising max-width, height and line-height together, on every date field in
     the app. That is its own change with its own A/B, the same call round 2
     made about FilePond's internals. Reported, not fixed.

   - `span.badge.bg-info` at 22.8 x 20.4, the link-count chip on a deliverable.
     §11 floors `a.badge[href]` and `button.badge` and deliberately not a bare
     `<span>`, because a span badge is a label and not a control. This one is a
     count, it has no href and no handler, and it stays a label.

   - `.deliverable-meta`, the `•` separators and the link text render at 11.9px
     from 768px UP -- 0.85rem against the 14px desktop root. That is real and it
     is the qvt-dashboard shape §10 fixed, but it is sub-12px only at widths
     this file does not govern, and /jobs/update/1874 is a gate page. It needs a
     deliberate all-widths decision like the one step 18 owes `.step-info`.

   - the four couts/salaries dialogs have NO `modal-fullscreen-sm-down` and a
     duplicated `modal-dialog-centered` class. Measured at 360 and 390 the
     dialog is 345/375 x 785 with 7.5px margins, fits, and scrolls inside
     itself, so the missing class costs nothing and the duplicate class is a
     no-op. Nothing to fix; recorded so the next reader does not "fix" it.
   ========================================================================== */

@media (max-width: 767.98px) {

    /* (0,3,0) against app.css:17156 `.modal .modal-header .close`, which sets
       only padding/border-radius; mobile.css is linked after app.css so the tie
       goes here. The dark pair is (0,4,0) against app-dark.css:9028's (0,3,0). */
    .modal .modal-header .close,
    html[data-bs-theme="dark"] .modal .modal-header .close {
        min-width: 44px;
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    /* app.css:17165 pins every icon in a modal header to 12px, so growing the
       button alone leaves a 12px glyph floating in a 44px box that reads as
       empty -- found by looking at the screenshot, not by measuring. 20px is
       the same glyph size §4 left in place on the navbar controls it grew.
       Scoped to the dismiss button so the other header icons (the warning
       triangle on the delete confirmations, the paperclip on the deliverables
       modal) keep the size the header title was designed around. (0,4,0) beats
       app.css's (0,3,0); the dark twin is (0,5,0) against app-dark's (0,4,0).

       `i[data-feather]` is listed beside `svg` because that is what is in the
       source: 79 of the 80 `.close` buttons contain `<i data-feather="x">`, and
       feather.replace() swaps it for an <svg> only once its script has run. If
       feather fails or has not run yet, the <i> is what is on screen, and it is
       the same 12px glyph in the same button -- so it needs the same floor.
       Still scoped to `.close`, so no `.bi` icon elsewhere in a header moves. */
    .modal .modal-header .close svg,
    .modal .modal-header .close i[data-feather],
    html[data-bs-theme="dark"] .modal .modal-header .close svg,
    html[data-bs-theme="dark"] .modal .modal-header .close i[data-feather] {
        width: 20px;
        height: 20px;
    }

    /* .btn-close is `box-sizing: content-box; width: 1em; height: 1em` with
       0.25em of padding, so a min-width on the content box would land at 44px
       PLUS the padding. border-box makes 44 mean 44. The X is painted as a
       background at `center/1em`, so it stays 16px and centred in the bigger
       box rather than scaling up with it. */
    .modal .modal-header .btn-close,
    html[data-bs-theme="dark"] .modal .modal-header .btn-close {
        box-sizing: border-box;
        min-width: 44px;
        min-height: 44px;
    }

    div.selectize-dropdown [data-selectable] {
        min-height: 44px;
        padding-top: 13px;
        padding-bottom: 13px;
    }

}


/* ==========================================================================
   §11  Navbar panel dropdowns, and the PWA install button
   --------------------------------------------------------------------------
   Measured at 390px on 2026-09-15, before this section existed:

     notifications panel   368px wide, x -247 .. 121   -> 247px off-screen LEFT
     messages panel        320px wide, x -148 .. 172   -> 148px off-screen LEFT

   Both carry .dropdown-menu-end, which right-aligns the panel to its toggle --
   and both toggles sit near the LEFT of the bar, just after the burger. On a
   desktop bar they hang leftwards into empty space; on a phone they hang off
   the edge of the screen. The inline `min-width: min(380px, calc(100vw - 1.5rem))`
   already caps the WIDTH correctly; nothing capped the POSITION.

   Fix: below 768px stop anchoring these panels to the toggle at all and pin them
   to the viewport instead. Bootstrap positions dropdowns with Popper, which
   writes `position`, `inset` and `transform` as INLINE styles, so overriding it
   needs !important -- that is Popper being outranked deliberately, not a
   specificity workaround.

   Only .navbar-panel-dropdown is re-anchored. The leave-balance and avatar menus
   are narrow, already sit fully on screen (14..222 and 199..384), and read better
   as compact menus than as full-width sheets.
   ========================================================================== */

@media (max-width: 767.98px) {

    header .navbar-top .navbar-panel-dropdown {
        position: fixed !important;
        top: 4.25rem !important;          /* clears the 90px bar without touching it */
        left: 0.75rem !important;
        right: 0.75rem !important;
        bottom: auto !important;
        transform: none !important;
        width: auto !important;
        min-width: 0 !important;
        max-width: none !important;
        /* Never taller than the space between the bar and the bottom of the
           screen, so the panel can always be scrolled to its end. */
        max-height: calc(100dvh - 5.5rem) !important;
        overflow-y: auto !important;
        overscroll-behavior: contain;
    }

    /* The panels are wide now, so their rows can breathe and reach 44px. */
    header .navbar-top .navbar-panel-dropdown .dropdown-item {
        min-height: 44px;
        display: flex;
        align-items: center;
        white-space: normal;
    }

    /* Keep the truncation helpers inside the new width rather than the old
       fixed 220px, or long job names still clip. */
    header .navbar-top .navbar-panel-dropdown .text-truncate {
        max-width: 100% !important;
    }

    /* Install button: icon-only under 576px, where the bar is tightest. The
       aria-label on the button carries the name for screen readers. */
    #pwa-install-btn {
        min-height: 44px;
    }
}

@media (max-width: 575.98px) {

    #pwa-install-btn .pwa-install-label {
        display: none;
    }

    #pwa-install-btn {
        min-width: 44px;
        justify-content: center;
    }
}


/* ==========================================================================
   §12  Keep the theme switch to one navbar slot on mobile
   --------------------------------------------------------------------------
   At 390px the navbar carried nine controls: burger, install, bell, messages,
   leave balance, sun icon, switch, moon icon, avatar. The two used rarely and
   reading worst at that width were the theme switch (a 30x18 pill between two
   20px icons) and the install button, so both moved into the sidebar drawer.

   The install button is still there. The theme switch is NOT: it was asked for
   back in the navbar, as a real switch rather than a menu row reading "Thème
   clair / sombre", so it now renders in the navbar at every width and the
   drawer row is gone. sidebar.css no longer hides it.

   #toggle-dark never moved through any of this. It has always been the navbar's
   checkbox and the only element with that id; dark.js binds to it by id and was
   never touched. The drawer row was a <label for> pointing at it, which is why
   removing that row needed no JavaScript change either.
   ========================================================================== */

/* THE THEME SWITCH IS BACK IN THE NAVBAR, and the drawer row that proxied to it
   is gone. What stays true is the measurement above: the theme control occupied
   THREE of the navbar's slots at 390px, not one, because a 30x18 pill sits between
   two 20px SVGs.

   So the switch comes back at every width and the two flanking icons do not. Below
   768px the navbar spends one slot on the theme, not three; from 768px up nothing
   here matches and the sun/moon pair renders exactly as it always has.

   The icons are decoration, not the control: they are aria-hidden, they are not
   labels, and the <input> now carries its own aria-label, so dropping them costs
   no accessible name. Hiding them rather than the switch is the whole point --
   the switch is the part you operate.

   The install button did NOT come back; it is still hidden in the navbar by
   sidebar.css and still lives as a drawer row. Only the theme control moved. */

@media (max-width: 767.98px) {

    header .navbar-top .theme-toggle > svg {
        display: none;
    }
}


/* ==========================================================================
   §13  Let a task's Label chip wrap inside its Kanban card
   --------------------------------------------------------------------------
   The Label chip is new. tasks/partials/kanban-scripts and
   jobs/partials/kanban-scripts both emit the same element beside the task name:

       <div class="task-title">NAME <span class="badge bg-secondary">LABEL</span></div>

   .badge is white-space: nowrap, so a long label cannot break, and the card it
   sits in is a FIXED 196px inside a FIXED 280px .kanban-board at EVERY viewport.
   Nothing about that card responds to width -- but its text does, and in the
   wrong direction. tasks/partials/kanban-scripts carries its own

       @media (max-width: 991.98px) {
           .step-kanban-container .kanban-task-card .badge { font-size: .875rem !important; }
       }

   so the chip gets BIGGER as the screen gets smaller, inside a box that does not.
   Swept /tasks (81 cards, 8 of them labelled) before this rule existed:

       viewport   chip width   card width   cards overflowing   worst clipped
       >= 992          171px        196px                   0             0px
        900            207px        196px                   8            11px
        768            207px        196px                   8            11px
        767            221px        196px                   8            25px
        390            221px        196px                   8            25px
        360            221px        196px                   8            25px

   Clipped, not scrollable: .kanban-board is overflow-x: hidden, so the end of a
   long label is simply not reachable. Document overflow stays 0 throughout --
   this never showed up as a page-level defect, only as lost text.

   THE BREAKPOINT IS 991.98px, NOT THIS FILE'S USUAL 767.98px, and the sweep above
   is why: at 768-991 the chip is already 11px too wide. 991.98 is not a number
   picked to cover it either -- it is the exact breakpoint of the rule that causes
   it, quoted above, so the wrap applies over precisely the band where the chip is
   enlarged. At >= 992px the chip measures 171px in a 196px card with 0 cards
   overflowing, so there is nothing to fix there; a max-width query cannot match
   there in any case, which is the property that makes desktop regression
   impossible. §5 already uses a 992px breakpoint for the same kind of reason.

   The declarations are copied from the task-type chip rendered two lines above it
   in the same card, which already carries `word-break: break-word;
   white-space: normal` inline -- so this is the card's own existing treatment of
   a long chip, applied to the one chip that missed it, not a second way of doing
   it.

   Scoped to .task-title so the status/indicator badges lower down the card,
   which are short by construction and rely on nowrap, are untouched.

   A/B'd by deleting this rule from the CSSOM and re-capturing white-space,
   word-break, overflow-wrap, display, font-size, max-width, width, the bounding
   box and scrollWidth/clientWidth on every .badge / td / th / kanban node:
   /users, /finance/tous, /clients, /leaveBalances and /report/job are
   byte-identical with and without it at both 390px and 1440px, and so is /tasks
   at 1440px. Only /tasks below the breakpoint differs, and only as intended --
   the card goes from scrollWidth 221 / clientWidth 196 to 196 / 196.
   ========================================================================== */

@media (max-width: 991.98px) {

    .kanban-task-card .task-title .badge {
        white-space: normal;
        word-break: break-word;
    }
}


/* ==========================================================================
   §14  44px targets in the Devis désignation editors
   --------------------------------------------------------------------------
   /statement/{id} turned its two désignation <textarea>s into CKEditor 5
   instances (statements/show.blade.php, .statement-rich). Each one brings a
   toolbar of icon-only buttons, and CKEditor sizes them itself:

       measured at 390px, /statement/987   14 buttons, every one 30 x 30

   §4 cannot reach them. It floors `.btn`, and a .ck-button is not a .btn --
   it is CKEditor's own class on a bare <button>, so the app's button floor has
   never applied to it. 30x30 is under the 44x44 this file holds everything
   below 768px to, and these are the controls that put bold, italic and lists
   into a devis that goes on to be rendered as a PDF.

   One declaration pair, and CKEditor does the rest ITSELF. Seven 44px buttons
   plus two separators do not fit on a 290px row, and CKEditor's toolbar watches
   for exactly that: it moves the tail into its own "Show more items" dropdown.
   A first attempt added `flex-wrap: wrap` to .ck-toolbar__items to force a
   second row instead; measured with and without, the result was byte-identical
   at 360, 390, 767, 768 and 1440 -- the grouping runs first and there is never
   anything left to wrap -- so the declaration was dead and was removed.

   Measured on /statement/987 with the rule applied:

       width   buttons     under 44px   toolbar h   toolbar sw/cw   doc sw/cw
         360   all 44x44            0        52px         268/268     360/360
         390   all 44x44            0        52px         298/298     390/390
         767   all 44x44            0        52px         656/656     767/767
         768   all 30x30           10        38px         140/140     768/768
        1440   all 30x30           13        38px         233/233   1440/1440

   768 and 1440 are the proof of no desktop regression: above the query nothing
   moves at all, down to the pixel.

   WHAT THE USER SEES AT 390px, and the trade this makes. Visible in the bar:
   Undo, Redo, Bold, Italic, "Show more items" -- all 44x44. Behind that button:
   Underline, Bulleted List, Numbered List -- also 44x44 once the panel is open.
   So three of the seven controls cost one extra tap on a phone, and in exchange
   none of the seven is a 30px target any more. The toolbar grows 14px, not the
   66px a wrapped second row would have cost.

   Scoped to .statement-rich so it reaches only these two editors and not any
   other CKEditor in the app (posts, promos, notes internes), which are not part
   of this round and were not measured.
   ========================================================================== */

@media (max-width: 767.98px) {

    .statement-rich .ck.ck-toolbar .ck-button {
        min-width: 44px;
        min-height: 44px;
    }
}
