/*!
 * ScoutMagic — Copyright (C) 2026 Xavier Dubois and contributors
 * Licensed under AGPL-3.0-or-later. See LICENSE and NOTICE.
 */

:root {
    --page-max-width: 1200px;
    --page-medium-width: 720px;
    --page-wide-width: 960px;
}

/* Unify the main content container at a single width on large screens,
   instead of Bootstrap's 1140/1320 tiers. */
@media (min-width: 1200px) {
    .container { max-width: var(--page-max-width); }
}

/* A centered column inside the shared container, in two tiers
   (design.md §7.6): medium for form, detail and configuration pages, wide
   for dense management screens. Views use one of these two classes instead
   of nesting a second .container or writing an inline max-width. There is
   no narrower tier: the unit asked for none (issue #471). */
.page-medium {
    max-width: var(--page-medium-width);
    margin-inline: auto;
}
.page-wide {
    max-width: var(--page-wide-width);
    margin-inline: auto;
}

/* Stepped workflow (e.g. "Changer d'année"): numbered circles with a connector. */
.step-circle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 50%;
    font-weight: 600;
    line-height: 1;
    color: #fff;
}
.step-circle--pending {
    background-color: var(--bs-primary);
}
.step-circle--done {
    background-color: var(--bs-success);
}
/* Vertical connector linking each step's circle to the next. */
.step-item__marker {
    position: relative;
}
.step-item:not(:last-child) .step-item__marker::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 2.25rem;
    bottom: -1rem;
    width: 2px;
    transform: translateX(-50%);
    background-color: var(--bs-border-color);
}
/* Dim steps that are not yet reachable. */
.step-item--upcoming {
    opacity: 0.65;
}

/* ============================================================
   MOBILE TOUCH-TARGET BASELINE
   Enforces the design.md §7.2 44px comfort goal on coarse-pointer
   devices. Restored to compact under `pointer: fine` (a real mouse/
   trackpad) — NOT at a width breakpoint: a 10" touch tablet is wider
   than 992px yet still needs its touch targets, and conversely a
   narrow desktop window still has a mouse.
   ============================================================ */

@media (pointer: coarse) {
    /* Buttons — standard .btn included, not just .btn-sm: covering only
       the -sm variants made a "small" control render TALLER than a
       normal one (44px vs Bootstrap's 38px), which is what bred the
       inline min-height:44px patches in templates. The bump on .btn is
       modest (38 → 44px) and only affects height, never padding. */
    .btn {
        min-height: 44px;
    }
    .btn-sm {
        min-height: 44px;
        min-width: 44px;
        padding-top: 0.5rem;
        padding-bottom: 0.5rem;
    }

    /* Form controls — min-height only on the standard sizes (38 → 44px);
       deliberately no line-height/padding inflation beyond 44px
       (design.md §7.2: never lengthen forms for zero gain). */
    .form-control,
    .form-select {
        min-height: 44px;
    }
    .form-control-sm,
    .form-select-sm {
        min-height: 44px;
        padding-top: 0.5rem;
        padding-bottom: 0.5rem;
    }

    /* Pagination links */
    .pagination .page-link {
        min-height: 44px;
        min-width: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    /* Bootstrap switches & checkboxes: AGENTS.md's 44px rule is about the
       tappable ZONE, not the drawn control. This block used to grow the
       input itself to 44x44 (min-width/min-height directly on
       .form-check-input) — that turned a checkbox into an oversized square
       next to ~14px of label text, and a switch's 3rem/44px box into an
       almost-square lozenge instead of Bootstrap's pill. That was the
       actual reported bug. The control now keeps its native Bootstrap size
       (~1em checkbox/radio, 2em-wide switch pill); the 44px zone moves to
       .form-check-label instead — a real <label for="...">, which the
       browser already activates its control from anywhere inside its box,
       so growing the label (not just decorative padding around it) is what
       makes the extra height actually tappable, not just visually taller.
       inline-flex + align-items:center keeps the label an inline-level box
       (still wraps next to the float-positioned .form-check-input the way
       Bootstrap lays it out) while vertically centering its text inside
       the taller box — the checkbox/label pair's own relative alignment
       (.form-check-input's margin-top:.25em against the label's first
       line) is untouched by this, only the row's overall height changes.
       flex-wrap: wrap matters once the label's content isn't a single text
       node — a multi-sentence label with a link in the middle (e.g. the
       registration form's RGPD consent text) splits into several flex
       items (the text before the link, the <a> itself, the text after);
       flex's default nowrap then keeps every item on one row instead of
       letting text flow onto the next line the way a normal paragraph
       would, which reads as the sentence being torn into disconnected
       column-like fragments rather than wrapping naturally. */
    .form-check {
        min-height: 44px;
    }
    .form-check-label {
        display: inline-flex;
        flex-wrap: wrap;
        align-items: center;
        min-height: 44px;
    }

    /* List-group interactive items */
    .list-group-item-action {
        min-height: 44px;
    }

    /* Generic tappable zone for interactive elements that are neither
       .btn, form controls, nor .list-group-item-action (e.g. a plain
       <a> row, a dropdown item, a clickable area). Replaces the former
       template-level style="min-height:44px" patches — inline styles
       beat every stylesheet rule, including the pointer:fine restore
       below, which is why they are banned (design.md §7.2). */
    .tap-target {
        min-height: 44px;
    }

    /* Icon-only buttons (ensure square minimum) */
    .btn-sm:has(> i:only-child),
    .btn-sm:has(> .bi:only-child) {
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
}

/* Restore compact sizes where a real mouse/trackpad is the primary
   pointer. Keyed on `pointer: fine`, not a width breakpoint: the old
   `min-width: 992px` restore stripped the 44px targets from wide TOUCH
   devices (a 10" tablet in landscape), while this leaves them intact
   there and still compacts any desktop, whatever its window width. */
@media (pointer: fine) {
    .btn,
    .btn-sm {
        min-height: revert;
        min-width: revert;
    }
    .form-control,
    .form-select,
    .form-control-sm,
    .form-select-sm {
        min-height: revert;
    }
    .pagination .page-link {
        min-height: revert;
        min-width: revert;
    }
    .form-check {
        min-height: revert;
    }
    .form-check-label {
        display: revert;
        align-items: revert;
        min-height: revert;
    }
    .tap-target {
        min-height: revert;
    }
}

/* Code blocks in tables/cards: prevent blow-out */
td code,
.card code {
    overflow-wrap: anywhere;
    word-break: break-all;
}

/* ============================================================
   MODAL MOBILE ENHANCEMENTS
   ============================================================ */
@media (max-width: 575.98px) {
    /* Stack modal footer buttons vertically on very narrow screens */
    .modal-footer {
        flex-wrap: wrap;
        gap: 0.5rem;
    }
    .modal-footer > .btn {
        flex: 1 1 100%;
    }
    /* Destructive buttons stand alone at full width */
    .modal-footer > .btn-outline-danger {
        order: 10;
    }
}

/* ============================================================
   SAFE-AREA INSETS
   For fixed-bottom elements (cookie banner, sticky footers)
   ============================================================ */
.fixed-bottom {
    padding-bottom: env(safe-area-inset-bottom, 0);
}

/* ============================================================
   COOKIE BANNER HEIGHT COMPENSATION
   The consent banner (partials/cookie_banner.html.twig) is fixed-bottom
   and overlays the page; without compensation the end of every page
   (footer, last form buttons) sits underneath it and can be unreachable,
   especially on mobile where the banner is ~300px tall.
   Chosen approach: pure CSS via body:has(), rather than a body class set
   by the template or a JS height measurement, because
   - the padding appears with the banner and disappears the instant
     cookie-consent.js removes it from the DOM — no stale body class to
     clean up and no change needed in cookie-consent.js;
   - :has() is supported by every browser we target (all evergreen
     browsers since 2023); where it is not, the fallback is exactly
     today's behavior (no padding), never something worse.
   The paddings bracket the banner's real rendered height at the banner's
   own breakpoints (checked at 360, 768 and 1280px wide), slightly
   oversized so the bottom of the page always clears it.
   ============================================================ */
body:has(> #cookie-banner) {
    /* <576px: text block + three stacked full-width actions (~280px) */
    padding-bottom: 20rem;
}
@media (min-width: 576px) {
    body:has(> #cookie-banner) {
        /* 576-991px: text block above a single row of actions (~150px) */
        padding-bottom: 12rem;
    }
}
@media (min-width: 992px) {
    body:has(> #cookie-banner) {
        /* >=992px: text and actions side by side (~100px) */
        padding-bottom: 8rem;
    }
}

/* ============================================================
   UTILITY: responsive width auto (Bootstrap has w-100 but no w-sm-auto)
   ============================================================ */
@media (min-width: 576px) {
    .w-sm-auto { width: auto !important; }
}

/* ============================================================
   Badges configuration row (admin/badges/configuration.html.twig):
   below md the name takes the whole line and the switch and the bin
   wrap under it; from md up it keeps the width it always had.
   ============================================================ */
.badge-config-name {
    flex: 1 1 100%;
    min-width: 120px;
}
@media (min-width: 768px) {
    .badge-config-name {
        flex: 1 1 auto;
        max-width: 220px;
    }
}

/* ============================================================
   UTILITY: dashed border for drop zones (upload, receipts, gallery).
   Bootstrap 5 has border-* color/width utilities but no border-style
   one — the templates already used this class name, which silently
   rendered as a SOLID border until it was defined here. !important
   matches Bootstrap's own border-utility convention so it wins over
   .border's shorthand wherever the two are combined.
   ============================================================ */
.border-dashed {
    border-style: dashed !important;
}

/* ============================================================
   Site-wide: highlight any HTML5-invalid field (wrong type/pattern, or a
   missing required value) once the user has interacted with it or tried
   to submit — native browser constraint validation already blocks the
   submit itself, this just makes the faulty field visually obvious.
   :user-invalid only matches after interaction/a submit attempt (unlike
   plain :invalid, which would highlight an empty required field before
   the user has even touched it); .is-invalid is the fallback for any JS
   that sets it explicitly, and for browsers without :user-invalid.
   ============================================================ */
.form-control:user-invalid,
.form-select:user-invalid,
.form-check-input:user-invalid,
.form-control.is-invalid,
.form-select.is-invalid {
    border-color: var(--bs-danger);
}
.form-control:user-invalid:focus,
.form-select:user-invalid:focus,
.form-control.is-invalid:focus,
.form-select.is-invalid:focus {
    border-color: var(--bs-danger);
    box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);
}

/* Offline navigation (Lot 4, public/assets/js/offline-nav.js) — a link to
   a page outside the offline whitelist, while offline. Greyed out only —
   deliberately NOT pointer-events:none and no forced tabindex, so the
   link stays reachable and operable by keyboard/assistive tech and can
   announce its own aria-disabled state; a click is caught by the script's
   own capture-phase listener instead, which opens the "unavailable
   offline" dialog rather than silently swallowing the click. */
.offline-link-disabled {
    opacity: .5;
    cursor: not-allowed;
}

/* Every cached page is read-only while offline (offline-nav.js's own
   applyFormState() — the visible counterpart of the submit interception
   that already refuses every form unconditionally). Only the submit
   controls are actually disabled; the fields stay editable on purpose, so
   a draft can still be written and sent once the connection is back. */
.offline-form-disabled {
    opacity: .75;
}
.offline-form-disabled button[type="submit"],
.offline-form-disabled input[type="submit"] {
    cursor: not-allowed;
}

/* The one-line "read-only, you are offline" strip offline-nav.js reveals
   at the top of the page. Server-rendered hidden in base.html.twig, so
   there is no flash of it on a normal online load. */
#offline-readonly-banner {
    position: sticky;
    top: 0;
    z-index: 1030;
}

/* Breadcrumb bar (partials/breadcrumb_bar.html.twig) — visible at every
   width, in an ordinary browser tab as much as in an installed PWA.

   It used to be hidden at lg and up unless the site ran standalone, on
   the reasoning that the desktop nav's own permanent sub-menu row already
   showed where you were and reached every ancestor a `parents` entry
   names. That row is gone: the desktop menus are now panels that open on
   click and close again (.desktop-megamenu below), so nothing on a
   desktop screen states the current page's ancestry any more. The
   breadcrumb is what states it, and it is the same one crumb-height bar
   mobile has always carried.

   (.breadcrumb-bar--has-trail is still emitted by the template — a
   `breadcrumb_trail` drives real ancestor-page links and stays meaningful
   — but it no longer needs a rule of its own to force the bar visible.) */
.breadcrumb-bar {
    display: flex;
}

/* The separator belongs to the text it separates, not to the tallest
   crumb in the row.

   Bootstrap renders it as a floated `::before` on each crumb after the
   first. A float is placed at the top of its containing block, which is
   invisible as long as every crumb is the same height — plain text. One
   crumb here is not: a `parents` entry that matches a menu renders as a
   real <button> (partials/breadcrumb_bar.html.twig), and the touch
   baseline above gives every .btn `min-height: 44px` on a coarse
   pointer. On a phone that crumb's <li> is therefore 44px tall while its
   neighbours are 24px, and the « / » in front of it floats to the top of
   those 44px — roughly ten pixels above the words it sits between.
   Reported from a real phone; a mouse-driven browser never shows it,
   which is why it survived the review.

   float: none puts the separator back in the crumb's own line box, on
   the same baseline as the text, whatever height the crumb ends up
   with. The tap target stays 44px.

   The zeroed padding is a separate matter, and about width rather than
   height: Bootstrap puts 0.5rem on each side of every « / » — a full rem
   of empty space per separator, which on a 375px screen costs a
   three-crumb trail some thirty pixels it has nothing to spend them on.
   The separator glyph already carries its own visual gap. Scoped to this
   bar so any other breadcrumb on the site keeps Bootstrap's spacing. */
.breadcrumb-bar .breadcrumb-item + .breadcrumb-item {
    padding-left: 0;
}
.breadcrumb-bar .breadcrumb-item + .breadcrumb-item::before {
    padding-right: 0;
    float: none;
    /* And a little air back around it, reported from a phone once the
       zeroing above shipped: with both of Bootstrap's paddings gone, the
       crumbs touched the « / » on both sides.

       Four pixels each side rather than Bootstrap's eight — half the
       width the rule above just saved, given back where it is legible,
       and a separator reads as separating at 4px. Margins rather than
       restoring the padding, so the two decisions stay readable as two:
       the zeroing says "not Bootstrap's spacing", this says "this much
       instead". */
    margin-left: 0.25rem;
    margin-right: 0.25rem;
}

/* A toast announces something; it must never intercept a click meant for
   the page.

   public/assets/js/toast.js parks every message in a
   `.toast-container.position-fixed.bottom-0.end-0` — the bottom-right
   corner, which is exactly where a modal's footer buttons and most
   primary actions sit. For the four seconds a success toast lives (eight
   for an error), that corner was dead: the visitor pressed « Envoyer un
   test », then pressed « Lancer l'envoi » and nothing happened, because
   the toast was on top of it.

   Bootstrap already sets `pointer-events: none` on the CONTAINER — it
   knows the problem. What it then does is set `pointer-events: auto`
   back on `.toast` itself, so the box the visitor can actually see is
   the box that swallows the click. That is the declaration overridden
   here; the close button gets it back, so a toast can still be dismissed
   by hand.

   Caught by the end-to-end suite, whose click on that very button was
   refused with « <div class="toast-body"> … subtree intercepts pointer
   events ». It had passed locally for weeks: a faster machine let the
   toast expire before the next click. Nothing changes visually — the
   toast simply stops being in the way. No caller treats one as
   clickable: show() returns the element, and none of the 61 call sites
   binds anything to it. */
.toast-container .toast {
    pointer-events: none;
}
.toast-container .toast .btn-close {
    pointer-events: auto;
}

/* Core\Security\HumanCheck — partials/human_check_fields.html.twig's
   honeypot trap field. Global (app.css, loaded on every page by
   base.html.twig) rather than components.css (opt-in per template) since
   any future public form anywhere can include the partial and must get
   this styling for free. Off-screen rather than display:none/visibility:
   hidden: a real, visible-to-the-DOM input a robot's form-filler still
   finds and fills, which is the entire point (a CSP-forbidden inline
   style, or a display:none some scrapers special-case and skip, would
   both defeat it). tabindex="-1"/aria-hidden on the field itself keep a
   human on keyboard/screen reader from ever reaching it. */
.hc-trap {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* iOS Safari auto-zooms the page on focus for any text input/select/
   textarea whose computed font-size is under 16px — Bootstrap's -sm form
   control variants are 0.875rem (14px), so every compact table (Passage,
   Départs, the config screens) triggered it. Fixed at the font-size
   level, never via the viewport meta tag's user-scalable/maximum-scale
   (that would disable pinch-zoom site-wide, a real accessibility
   regression — WCAG 1.4.4 — for a cosmetic annoyance). 16px is the exact
   threshold, not a rounder "safe" number, so this is the smallest change
   that stops the zoom. Padding/border-radius/etc. from .form-control-sm/
   .form-select-sm are left alone — only the font-size that actually
   triggers the browser behavior is overridden. */
.form-control-sm,
.form-select-sm {
    font-size: 16px;
}

/* The shared person avatar (Core\View\PersonAvatar, person_avatar() in
   Core\View\TwigFactory): one circle, holding a photo when one is known
   and initials when not, wherever this site shows a person — the member
   entries in the mobile menu, the connected person in the header and in
   that menu, the author of a message in a discussion group, "Mon compte".

   The colours are the ones the mobile menu already used for a member
   (`bg-primary-subtle` / `text-primary`): an identity rather than a
   missing image, and quiet enough to sit in a nav bar. Size comes from
   the call site as inline width/height (the component computes the font
   size from it), so one rule serves a 24px row and a 96px page header. */
.person-avatar {
    flex-shrink: 0;
}
.person-avatar-initials {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background-color: var(--bs-primary-bg-subtle, #cfe2ff);
    color: var(--bs-primary, #0d6efd);
    font-weight: 600;
    line-height: 1;
    /* Two letters in a circle: never let a long-ish rendering push the
       box out of round. */
    overflow: hidden;
    white-space: nowrap;
}

/* Core\View\templates\partials\list_editor.html.twig / list-editor.js —
   generic reusable list editor (add/reorder/activate/delete).

   In app.css rather than components.css because this partial is chrome
   several pages reuse and only SOME of them load components.css: the
   banner configuration page never did, so these rules — and, worse, the
   phone layout below — silently did not apply on one of the three pages
   built out of this partial.

   An item is [poignée][texte][actions] on one line. On a phone those
   actions are four controls wide (edit, visibility, activate, delete) and
   they were taking the row from the text they act on, which was left with
   a couple of words per line. Below `sm` the block takes a full line of
   its own, under the text, which is where a thumb expects it anyway. */
.list-editor-item {
    background-color: var(--bs-body-bg);
}
.list-editor-item--dragging {
    opacity: 0.4;
}
.list-editor-drag-handle:active {
    cursor: grabbing;
}
.list-editor-item-content {
    /* Basis 0, not auto: with `auto` the item's own text length decides
       whether the actions fit beside it, so a long banner wrapped them
       onto their own line on a DESKTOP too. At 0 the text takes whatever
       the actions leave, and the wrap below is the only thing that ever
       moves them. */
    flex: 1 1 0;
    min-width: 0;
}
.list-editor-item-actions {
    /* Hard right whenever it shares the line, whatever the text's width. */
    margin-left: auto;
}
@media (max-width: 575.98px) {
    .list-editor-item-actions {
        width: 100%;
        justify-content: flex-end;
    }
}

/* ============================================================
   DESKTOP NAV — level one reads as tabs, level two as pills.
   The two rows used to be identical grey outline pills: two
   different hierarchy levels (menus, then the active menu's pages)
   with zero visual difference between them. The menu row is now
   quiet text with a primary underline on the active menu — the
   page row below keeps the pill buttons, so "where am I" reads
   in one glance: underlined section, filled page.
   ============================================================ */
.desktop-menu-btn {
    border: 0;
    border-radius: 0;
    color: var(--bs-secondary-color);
    /* The underline slot is always reserved so activating a menu
       never shifts the row's height. */
    border-bottom: 2px solid transparent;
    padding-top: 0.65rem;
    padding-bottom: 0.65rem;
}
.desktop-menu-btn:hover {
    color: var(--bs-body-color);
}
.desktop-menu-btn.active {
    color: var(--bs-primary);
    border-bottom-color: var(--bs-primary);
    font-weight: 600;
}
/* The tab whose panel is currently open, which is NOT the same thing as
   `.active` above (the menu the current page lives in). Both can be true
   at once, and either can be true alone. */
.desktop-menu-btn[aria-expanded="true"] {
    background-color: var(--bs-tertiary-bg);
    color: var(--bs-body-color);
}

/* Desktop mega-menu panel (partials/nav.html.twig, opened by
   public/assets/js/nav.js).

   Custom CSS rather than a Bootstrap component because Bootstrap 5 has no
   multi-column mega-menu: `.dropdown-menu` is a single column sized to its
   content, and forcing nineteen entries into four titled columns inside one
   would be fighting it the whole way. This duplicates nothing (AGENTS.md,
   § CSS / frontend).

   #desktopNav is the positioning context (position: relative below), so
   the panel hangs under the menu bar across the full width without
   pushing the page down — the permanent sub-menu row this replaced did
   push it down, by a height that changed with the active menu.

   z-index 1030 sits above page content and below Bootstrap's offcanvas
   backdrop (1040) and the toast container: a panel must never cover a
   message about what just happened, or survive over an opening
   offcanvas. */
#desktopNav {
    position: relative;
}
.desktop-megamenu {
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    z-index: 1030;
    box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
.desktop-megamenu-grid {
    display: grid;
    /* auto-fit collapses the tracks a menu has no group for, so the real
       column count is the number of groups — at most six, the widest
       Core\View\MenuBuilder::MENU_GROUPS declares. That number is a
       tripwire, not a decoration: Tests\Core\View\MegaMenuColumnCeilingTest
       fails when it stops matching, because the last time it drifted
       nothing said so.

       Six is a count auto-fit handles rather than a limit it enforces:
       below roughly 1360px the tracks wrap to a second row instead of
       shrinking under the minimum, which is the intended degradation.
       200px is the point below which a two-word French label starts
       wrapping. */
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 0.25rem 2rem;
    align-items: start;
}
.desktop-megamenu-title {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding-bottom: 0.25rem;
    margin-bottom: 0.25rem;
    border-bottom: 1px solid var(--bs-border-color);
}

/* Contextual help panel (partials/help_panel.html.twig, ARCHITECTURE.md
   §8.64) — one offcanvas element, two placements: a bottom sheet below
   lg (the chip picker's sheet precedent), a right-hand drawer at lg and
   up. Bootstrap 5 has no responsive offcanvas placement of its own, so
   the element keeps .offcanvas-bottom for its mobile default and this
   block re-shapes it into an .offcanvas-end-alike on desktop — Bootstrap's
   generic `.offcanvas.show { transform: none }` still lands it on screen
   whichever initial transform applies. This duplicates no Bootstrap
   component (AGENTS.md § CSS / frontend): it parameterizes one. */
.help-offcanvas {
    /* Bootstrap's --bs-offcanvas-height drives .offcanvas-bottom; the
       default 30vh fits a toolbar, not a help text. */
    --bs-offcanvas-height: 75vh;
}
@media (min-width: 992px) {
    .help-offcanvas.offcanvas-bottom {
        top: 0;
        right: 0;
        left: auto;
        bottom: 0;
        width: 420px;
        max-width: 100%;
        height: 100%;
        max-height: none;
        border-top: 0;
        border-left: var(--bs-border-width) solid var(--bs-border-color-translucent);
        transform: translateX(100%);
    }
}

/* The help search field carries its own clear button (the × before the
   search icon), because the native one that `type=search` gets exists in
   WebKit and Chromium and nowhere else — a Firefox reader would have no
   way to empty a long question but the keyboard. Suppressing the native
   decoration leaves exactly one cross, in the same place, everywhere. */
[data-help-search-input]::-webkit-search-cancel-button,
[data-help-search-input]::-webkit-search-decoration {
    -webkit-appearance: none;
    appearance: none;
}

/* Help topic content — the Markdown body of a topic, rendered by
   Core\View\MarkdownRenderer with heading_base_level 2 (the page's real
   <h1> is the topic title). Scoped sizes keep those <h2>/<h3> reading as
   section titles inside a narrow column or the panel, not competing with
   the page header; semantic levels stay intact for assistive tech. */
.help-content h2 {
    font-size: 1.15rem;
}
.help-content h3 {
    font-size: 1rem;
}
.help-content h4,
.help-content h5,
.help-content h6 {
    font-size: 0.95rem;
}
/* The charter's single warning callout per topic (design.md §7.11) —
   `> ` in the topic source, rendered as a <blockquote>. Theme-safe
   colours only (design.md §7.8). */
.help-content blockquote {
    border-left: 4px solid var(--bs-warning);
    background-color: var(--bs-warning-bg-subtle);
    padding: 0.5rem 0.75rem;
    border-radius: var(--bs-border-radius);
    margin: 0.75rem 0;
}

/* ============================================================
   Rich text rendered with |raw — the one rule that bounds an image
   nobody sized
   ============================================================
   Everything a chief writes through a rich-text editor (a news
   article's body, the site's editable blocks, a help topic, the RGPD
   page, a rental's conditions, a camp's note) is stored as HTML and
   printed with `|raw`. An <img> in it carries whatever width the
   source file had: a 4000px photo pasted into an article came out at
   4000px and pushed the whole page sideways, because nothing in this
   stylesheet or in Bootstrap constrains it — `.img-fluid` is opt-in,
   and no editor here adds it.

   So the containers carry `.rich-text` (alongside whatever class they
   already had) and every image inside one is bounded here, once.

   The boxes that HTML is WRITTEN in carry it too, and that half was
   missed at first: every display container had the class and neither
   editor did, so a published article bounded its images while the
   contenteditable its author was typing into let a 4000px photo run off
   a phone screen (#181). A contenteditable is a rendering like any
   other — the browser lays the markup out inside it exactly the same
   way — so it takes the same class. Tests\Core\View\RichTextImageRuleTest
   holds both halves.

   The desktop cap is the same 420px as `.groups-media-grid`
   (components.css): on a wide screen a photo that fills the text
   column reads as a banner the author never asked for, and an image
   slightly too small is a much smaller problem than one too large.
   Below 992px the image simply fits its column, which is what a phone
   wants and what the reported case never got wrong.

   `height: auto` is what keeps a constrained image in proportion —
   without it an `<img width height>` pair (which editors do emit)
   keeps its stated height while the width shrinks. */
.rich-text img {
    max-width: 100%;
    height: auto;
}

@media (min-width: 992px) {
    .rich-text img {
        max-width: 420px;
    }
}

/* Passage (modules/registration, views/passage.html.twig) — the column
   both of that page's tables end with: a section picker and its
   « Enregistrer » button, side by side in one flex row.

   The button already carried .text-nowrap, so the defect was never a
   wrapped label: with nothing stopping the flex item from shrinking, the
   button was squeezed BELOW the width of its own text and the nowrap made
   that text overflow the cell. .flex-shrink-0 on the button and
   .flex-grow-1 on the select fix the pair; this class gives the column the
   room the two of them actually need — a realistic section name plus the
   button — rather than the 200/240px it had been guessed at.

   A class rather than the three style="min-width:…" attributes it
   replaces: SECURITY.md §34 asks for exactly this conversion whenever a
   template carrying inline geometry is touched anyway, and these were
   static values, not computed ones. It lives here rather than in
   components.css because base.html.twig loads app.css for every page and
   components.css only on the pages that opt in — one min-width rule does
   not justify pulling a thousand lines of unrelated component CSS into
   this one page.

   The table stays inside .table-responsive: horizontal scrolling on a
   phone is the accepted outcome here (spec §12), not a defect to design
   around. The goal is a legible column, not a table that fits 375px. */
.passage-assign-col {
    min-width: 17.5rem;
}

/* The planning block under a Passage line (roadmap IT-17,
   @registration/_passage_planning.html.twig).

   It sits in a `colspan` cell of a table that scrolls horizontally at
   375px — deliberately, per the rule just above — so without this it
   would be as wide as the TABLE and a chief would have to scroll right to
   read a note or tick a checkbox. Two rules fix that:

   - `max-width` caps it at the screen rather than at the table;
   - `position: sticky; left: 0` pins it to the left edge of the scroll
     viewport, so it stays put while the row above it scrolls.

   The 4rem is the page padding plus the card's own, either side, so the
   block ends where the card does instead of a hair past it. Erring
   narrow is deliberate: a few pixels of slack cost nothing, and a few
   pixels of overflow cost the end of every sentence. */
.passage-planning-inner {
    position: sticky;
    left: 0;
    max-width: calc(100vw - 4rem);
}

/* Configuration > E-mails (core/View/templates/config/emails.html.twig,
   config/email_edit.html.twig).

   Two rules, both about a column that must not collapse:

   - .email-state-col is the inventory's « État » column. Its content is a
     single badge, so the browser is happy to squeeze it to the width of
     the longest word and wrap « Non modifiable (email d'authentification) »
     over four lines. The state is what the page is read for; it gets the
     room to say itself on one line and the table scrolls instead
     (.table-responsive, spec §12).
   - .email-body-editor is the preview the shared rich-text modal reads
     from and writes back to. Empty — a template whose content block did
     not render — it would be a zero-height box with no hint that the
     « Modifier » button opens anything, so it keeps a minimum height.

   Classes rather than inline style attributes: SECURITY.md §34, and these
   are static values. In app.css rather than components.css for the same
   reason as .passage-assign-col above — base.html.twig loads app.css on
   every page, components.css only where a page opts in. */
.email-state-col {
    white-space: nowrap;
}

.email-body-editor {
    min-height: 6rem;
}

/* Passage statistics box (modules/registration/views/
   _passage_statistics.html.twig, spec §8).

   Two track heights, because the two bars answer different questions and
   should not read as one control: the total is a comparison BETWEEN the
   sections of a branch and gets the taller track; the gender split is a
   100% bar whose numbers are written out beside it, so the bar itself is
   a glance rather than a measurement.

   Classes rather than inline style attributes for the static half
   (SECURITY.md §34); the widths and the section colour stay inline
   because they are computed per section and there is nothing to hoist.
   In app.css for the same reason as .passage-assign-col above —
   base.html.twig loads it on every page, components.css only on the pages
   that opt in. */
.passage-stats-track {
    height: 0.5rem;
    background-color: var(--bs-secondary-bg);
    overflow: hidden;
}

.passage-stats-fill {
    height: 100%;
}

.passage-stats-gender {
    height: 0.375rem;
}

/* ============================================================
   Bornage des blocs qui grandissent tout seuls (§7.6)
   ============================================================ */

/* Notes de version : hauteur, pas nombre de lignes. Le contenu est du
   Markdown rendu (titres, listes, blocs de code ont chacun leur propre
   boîte de ligne), donc découper 20 lignes de source côté serveur
   couperait au milieu d'un élément. 30em ≈ 20 lignes à la hauteur de
   ligne Bootstrap (1.5). Bootstrap n'a pas d'équivalent multiligne de
   .text-truncate : c'est la raison d'être de ces quelques lignes.
   Le dégradé signale qu'il reste du texte ; public/assets/js/notes-clamp.js
   retire la classe quand le contenu tient déjà. */
.notes-clamp:not(.is-expanded) {
    max-height: 30em;
    overflow: hidden;
    -webkit-mask-image: linear-gradient(to bottom, #000 82%, transparent 100%);
            mask-image: linear-gradient(to bottom, #000 82%, transparent 100%);
}

[data-bs-toggle="collapse"] .bi-chevron-down,
[data-notes-clamp-toggle] .bi-chevron-down {
    transition: transform 0.2s ease;
}

[data-bs-toggle="collapse"]:not(.collapsed) .bi-chevron-down,
[data-notes-clamp-toggle][aria-expanded="true"] .bi-chevron-down {
    transform: rotate(180deg);
}

/* Navigation progress bar (assets/js/navigation-feedback.js): a 3 px line
   at the very top of the viewport, shown a moment after a link is
   followed and gone the instant the next page (or a bfcache restore)
   shows. The only feedback the installed app has that a tap registered:
   there is no address bar there, no browser spinner, nothing. Indeterminate
   on purpose — the page never knows how long the server will take. */
#navigation-progress {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    z-index: 1090;
    background: linear-gradient(90deg, transparent 0%, var(--bs-primary) 40%, var(--bs-primary) 60%, transparent 100%);
    background-size: 50% 100%;
    background-repeat: no-repeat;
    pointer-events: none;
}
#navigation-progress.is-active {
    animation: navigation-progress-slide 1.1s ease-in-out infinite;
}
@keyframes navigation-progress-slide {
    from { background-position: -50% 0; }
    to { background-position: 150% 0; }
}
@media (prefers-reduced-motion: reduce) {
    #navigation-progress.is-active {
        animation: none;
        background: var(--bs-primary);
    }
}

/* .min-w-0 — the one utility a flex row needs and Bootstrap 5.3 does not
   ship.

   A flex item's default `min-width: auto` refuses to shrink below its
   content, so a long word — a place name, a document title, a sender's
   address — pushes its row wider than the viewport and the whole page
   scrolls sideways on a phone. `min-width: 0` is what lets `text-truncate`
   and `text-break` inside it actually work.

   It was already being written in two modules on the assumption that
   Bootstrap provided it; it did not, so every one of those was an inert
   class name and the overflow they were meant to prevent was not
   prevented. It then lived in components.css, which base.html.twig does
   not load, and was inert again on fifteen pages (issue #602): here, in
   the stylesheet every page gets, is the only place a utility belongs. */
.min-w-0 {
    min-width: 0;
}
