/* =====================================================================
   Prosyjob — application layer
   =====================================================================
   Loaded LAST, after both prosyjob-components.css and whichever vendor theme
   AssetStacks has activated. This is the only stylesheet the application
   itself owns; everything under assets/user-template and assets/admin-template
   is generated and must not be edited (see assets/VENDORING.md).

   It holds three kinds of rule and nothing else:
     1. contracts the JS depends on and no vendor sheet may break;
     2. the few shell variants the template did not ship;
     3. the dark theme.

   RULE FOR EVERY WAVE: no raw hex below this line — always a var(--pj-*).
   That discipline is what keeps the dark block at the bottom to ~50 lines
   instead of a second pass over 97 templates.
   ===================================================================== */


/* ── 0. The token scale, on every stack ────────────────────────────────
   The `--pj-*` scale is declared in `user-template/css/prosyjob.css` — and
   AssetStacks switches that sheet OFF for the admin console, because the two
   vendor themes both define `.card` / `.btn` / `.header` and only one may
   apply at a time.

   So every `var(--pj-…)` written below resolved to *nothing* on the admin
   console in light mode. That is not a fallback to a default: a custom
   property with no value makes the whole declaration invalid at computed-value
   time, so `border: 1px solid var(--pj-line)` becomes `border-color: inherit`.
   Measured on /admin/villes before this block existed: the header's theme
   toggle came out a 20x40 oval with a black border, and the language pill
   inherited plain black — the two controls E7-13 had just "fixed" in dark,
   where the block at the bottom of this file happens to define the same names.

   The scale is therefore restated here, in the one sheet every stack loads.
   It is a MIRROR of prosyjob.css, not a second source of truth:
   `tools/check-assets.mjs` compares the two and fails the build if they drift.
   The dark block at the end of this file overrides these at the same `:root`
   level and wins on order, so dark keeps working on all three shells. */
:root {
    --pj-orange: #EF5A28;
    --pj-orange-dark: #D8481A;
    --pj-orange-soft: #FEEDE5;
    /* E8-03: the surface pair. White on the brand orange is 3,41:1 — fine for
       an accent, short of AA under a 15px button label. Splitting the fill off
       leaves `--pj-orange` the brand colour everywhere it is *text* or a
       border, exactly as `--primary-fill` did for the blue in E7-15. The hover
       has to be darker than the resting fill; the old hover
       (`--pj-orange-dark`) is lighter than it, so the button would have
       brightened on hover. Mirrored from prosyjob.css — check-assets.mjs
       fails if the two drift. */
    --pj-orange-fill: #CE4416;
    --pj-orange-fill-hover: #B03A12;
    /* E10-02: the brand as *text*. `--pj-orange-dark` measured 3,80:1 on the
       pale orange chip behind an active menu entry (29 of them across 13
       screens) and `--pj-orange` 3,41:1 on white for outline-button labels.
       Third cut of the same pattern after --pj-orange-fill: the brand keeps
       `--pj-orange` for borders, icons and decoration, and darkens only where
       it is read. The dark block below overrides this to the lifted orange —
       dark already measured 6,23:1 and must not move. */
    --pj-orange-text: #C04017;
    --pj-navy: #34506E;
    --pj-navy-dark: #263D55;
    --pj-blue: #3B5BDB;

    --pj-ink: #1C2A3A;
    /* E8-02: was #7A8699 — 3,69:1 on white and 3,41:1 on the page background.
       Measuring where muted text actually lands put every occurrence on the
       page background (#F4F6FA), never on a card, so that is the surface the
       value has to clear: #657085 gives 4,61:1 there, 4,99:1 on white. */
    --pj-muted: #657085;
    --pj-line: #E9EDF3;
    --pj-bg: #F4F6FA;
    --pj-card: #FFFFFF;

    --pj-radius: 16px;
    --pj-radius-sm: 12px;
    --pj-radius-pill: 999px;
    --pj-shadow: 0 1px 2px rgba(16, 24, 40, .04), 0 8px 24px rgba(16, 24, 40, .06);
    --pj-shadow-sm: 0 1px 2px rgba(16, 24, 40, .06);

    --pj-sidebar-w: 264px;
    --pj-rail-w: 300px;
    --pj-topbar-h: 72px;
    --pj-gap: 24px;
    --pj-max: 1560px;
}

/* ── The type scale (2026-09-29) ───────────────────────────────────────
   Not in prosyjob.css, which writes its sizes as literals: these are those
   same literals, named. The shell's page title is 24px, a hero title 30px
   (24px on a phone), a card title 20px, body text 14px, secondary 13px,
   small labels 12px, uppercase eyebrows 11px.

   Why it exists: the maquette redesigns of 2026-09-27..29 were sized from
   enlarged images, in rem and clamp(), and came out one to two steps above
   the rest of the application — titles up to 42px, body text at 17px.
   `tools/check-type-scale.mjs` now refuses a new literal font-size in our
   two sheets: a text size is one of these, and an icon or a glyph that
   genuinely needs its own says so with a `fs-ok` comment. */
:root {
    --pj-fs-2xs: 11px;
    --pj-fs-xs: 12px;
    --pj-fs-sm: 13px;
    --pj-fs-body: 14px;
    --pj-fs-md: 15px;
    --pj-fs-lg: 17px;
    --pj-fs-card: 20px;
    --pj-fs-title: 24px;
    --pj-fs-display: 30px;
}

@media (max-width: 767px) {
    :root { --pj-fs-display: 24px; }
}

/* Not in prosyjob.css — the raised surface the dark block needs for inputs,
   hovered rows and the admin console's inner panels. Declared here in both
   themes so a rule may use it without asking which one is active.

   This block is also where a token WE own alone belongs. `check-assets.mjs`
   walks the tokens of prosyjob.css and asserts each one is mirrored here with
   the same value; it never walks the other way, and it reads only the FIRST
   `:root {` of each file. So a name declared here and nowhere else is outside
   the comparison — which is what makes `--pj-card-2` legal, and the two
   semantic colours below with it. */
:root {
    --pj-card-2: #EEF2F7;

    /* LES CINQ METAUX DU GRADE PARTENAIRE (PO, 2026-10-04).

       Une teinte par palier, et elles doivent se distinguer DEUX A DEUX :
       c est la couleur qui porte le metal, l icone etant la meme medaille
       pour les trois premiers. Un degrade de gris n aurait rien dit.

       L explorateur est en BRONZE et non en argent pale : il est le
       plancher du bareme, pas un metal precieux de plus, et le bronze est
       exactement ce que dit une troisieme place. */
    --pj-metal-explorer: #9C5A2A;
    --pj-metal-silver: #79818C;
    --pj-metal-gold: #B8860B;
    --pj-metal-platinum: #3E6E7E;
    --pj-metal-diamond: #6D4AC8;

    /* The two signal colours the scale never had. Every red and green in the
       button set came from a vendor theme instead: `.btn-danger` was #FF0100
       (a pure sRGB red nobody chose), `.btn-success` #FF3115 — an ORANGE, so
       the success button and the primary were the same hue — and
       `.btn-outline-success` #00E65B, which measures 1,56:1 on white and is
       simply unreadable.

       Each comes in the same two cuts as the orange, and for the same reason:
       a value that reads as TEXT or a BORDER has to lift on a dark card, while
       a value used as a SURFACE under a white label must not — lifting it
       would put white on a pale red. That is the split `--pj-orange-fill`
       already makes, and the one `--primary-fill` is documented as refusing to
       follow into dark.

         --pj-danger        #CC3038   5,19:1 on white, 4,80:1 on the page
         --pj-danger-fill   #CC3038   white on it: 5,19:1 — stable in both themes
         --pj-success       #157347   5,87:1 on white, 5,43:1 on the page
         --pj-success-fill  #157347   white on it: 5,87:1 — stable in both themes

       The two pairs are equal in light and part company in dark, where only
       the first of each lifts.

       #CC3038 is a deliberate near-miss of `--color-error` (#D1373F) in
       prosyjob-components.css, which measures 4,48:1 on the page background —
       just under AA. Close enough that nothing visibly moves, correct enough
       that the token can be used anywhere. */
    --pj-danger: #CC3038;
    --pj-danger-fill: #CC3038;
    --pj-success: #157347;
    --pj-success-fill: #157347;

    /* ── The status tints (2026-09-10) ─────────────────────────────────
       A third cut, after "text/border" and "surface under a white label":
       the pale chip. `.jo-status--pending_payment`, `.ap-status--*`,
       `.pay-*`, `.mt-*` — a saturated ink on a tint of its own hue.

       It needed its own pair because the two existing cuts answer a
       different question. `--pj-warning-ink` is NOT `--pj-warning` with a
       suffix: the unsuffixed tokens mean "signal colour on a card", these
       mean "signal colour on a tint", and the tint is lighter than the card
       in light and darker in dark. Six units of luminance apart, and not
       interchangeable — the same one-colour-two-jobs trap `--primary-fill`
       and `--pj-orange-fill` already document. Nothing needs a bare
       `--pj-warning`, so there isn't one.

       Success and danger reuse the inks above: both already clear AA on
       these tints in both themes, so only the tint half is new.

       Measured, ink on its own tint, light / dark:
         success  5,19 / 6,64      info     6,00 / 6,38
         danger   4,64 / 5,45      neutral  5,70 / 6,71
         warning  5,41 / 8,64      accent   7,81 / 6,12

       Six of these seven pairs failed AA in the LIGHT theme before this —
       `--ap-interview` measured 3,33:1 — so this is not a dark-mode patch.
       The light tints keep the values the chips already had; it is the inks
       that move, and only far enough to clear 4,5:1 on the tint they sit on
       rather than on the white they were judged against. */
    --pj-success-soft: #E7F4ED;
    --pj-danger-soft: #FDEFF0;
    --pj-warning-soft: #FDF4E3;
    --pj-warning-ink: #8A5A12;
    --pj-info-soft: #E8F0FD;
    --pj-info-ink: #1558B0;
    --pj-neutral-soft: #F1F2F4;
    --pj-neutral-ink: #55606F;
    --pj-accent-soft: #F3ECFF;
    --pj-accent-ink: #5B21B6;
}


/* ── 1. Contracts ──────────────────────────────────────────────────────
   The visibility guard. 154 places across 57 files set `element.hidden =
   true` and expect it to hold, and 47 templates ship the bare attribute.
   Bootstrap's reboot provides this too, but a vendor theme that sets
   `display` on `.card` / `.btn` / `.row` / `.form-group` would beat it — so
   it is restated here, at the end of the cascade, where nothing can.
   AppShell.assertHiddenGuard() probes this on every render in dev.
   Moved here from the component sheet, which is no longer the last one loaded. */
[hidden] {
    display: none !important;
}

.hidden {
    display: none !important;
}

/* The shell toggles `is-active`, the template's own CSS keys off `active`.
   Aliasing costs one rule; renaming would mean touching six JS files and
   would still break `.pj-shortcut.is-active`, which prosyjob.css spells with
   the `is-` prefix. So the vocabulary stays as the JS already writes it. */
.pj-nav a.is-active,
.pj-quicknav a.is-active {
    background: var(--pj-orange-soft);
    color: var(--pj-orange-text);
    font-weight: 700;
}

.pj-nav a.is-active i {
    color: var(--pj-orange);
}

.pj-quicknav a.is-active {
    background: transparent;
    border-bottom: 2px solid var(--pj-orange);
    border-radius: 0;
}


/* LE LIEN ACCENTUE DU PIED DE PAGE.

   Un seul, et c est ce qui le fait marcher : l Academie doit se remarquer
   dans une rangee de mentions legales (PO, 2026-09-29). Deux liens colores
   n en distingueraient aucun.

   `--pj-orange` est la couleur de marque, et elle est definie dans les deux
   themes -- une couleur ecrite en dur ici serait illisible en sombre. */
.pj-footer__links a.pj-footer__accent {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--pj-orange);
  font-weight: 600;
}

.pj-footer__links a.pj-footer__accent i { font-size: 1.05em; } /* fs-ok: icône du lien, relative à son texte */

.pj-footer__links a.pj-footer__accent:hover { text-decoration: underline; }


/* ── 2. Shell geometry ─────────────────────────────────────────────────
   The shell must present the same three columns on every page at a given
   viewport. It did not: `/tableau-de-bord` came out 1016px wide with a 356px
   centre column where `/profil` filled 1560px with 900px, at the same 1920px
   viewport.

   `#app` is `display: flex; flex-direction: column` (prosyjob-components.css),
   so `.pj-shell` and `.pj-footer` are flex items, and both carry
   `margin: 0 auto` from prosyjob.css to centre themselves under `--pj-max`.
   An auto margin on a flex item's *cross* axis cancels the default `stretch`,
   and the item is then sized to its own max-content instead of to the line —
   so the `minmax(0, 1fr)` centre track was measuring the page's content, not
   the space available to it. Every page got its own layout, and the footer
   (415px, centred on itself) never lined up with the shell above it.

   `width: 100%` restores the stretch. `max-width: var(--pj-max)` still caps
   it and the auto margins still centre it, so the intended geometry is
   unchanged — it is now simply derived from the viewport at every breakpoint
   rather than from whatever the page happened to render. */
.pj-shell,
.pj-footer {
    width: 100%;
}

/* 16px on a phone, not 24 (2026-10-13). The template pads the shell with
   `--pj-gap` at every width, so on a 360-390px screen a card lost 48px of
   width to its margins — an eighth of the viewport — where the maquettes
   (responsabilite.png and the others) put the cards 16px from the edge. Only
   the sides change: the vertical rhythm stays the template's. The footer
   follows the shell so their edges keep lining up (see above). */
@media (max-width: 560px) {
    .pj-shell,
    .pj-footer {
        padding-left: 16px;
        padding-right: 16px;
    }

}

/* The guest shell sat inside a SECOND margin (2026-10-13). `.layout-guest`
   keeps the 20px frame (14 and 12 on smaller screens) of the guest layout
   that predates the pj shell, and the shell pads itself on top of it: a
   visitor's cards started 44px from the edge where a signed-in reader's
   start at 24 (16 on a phone), and the sticky top bar floated 20px below the
   top of the window with the page scrolling through the gap above it. The
   connected shell has no frame; the guest one now matches it, so the two
   layouts — the same pages, logged out and in — have the same geometry.
   Not the chromeless pages (login, signup…): they have no shell to carry a
   margin, the frame IS their margin, and without it the form touched the
   edges of a phone. */
.pj-app.layout-guest:not(.is-chromeless) {
    padding: 0;
}

/* One flat background for the whole shell, in both themes.
   `.layout-auth` and `.layout-guest` in prosyjob-components.css paint a blue
   radial tint over `linear-gradient(180deg, var(--surface-sunken), #eff4fb)`.
   That second stop is a raw light hex, so under `[data-theme="dark"]` the
   page started at #1e2733 and ended at #eff4fb — scrolling a connected page
   faded the background from dark to near-white behind dark cards. Repointing
   the hex at a token would only make the gradient theme-aware, and the pj
   shell does not want a gradient at all: prosyjob.css already states the
   intent on `body.pj-body`, a single `--pj-bg`. The shells take that and the
   gradient goes, which is also what keeps the two shells identical.
   Scoped to `.pj-app` — the guest and connected shells — so the admin
   console (`.layout-admin`, mutually exclusive with it) is untouched. */
.pj-app {
    background: var(--pj-bg);
}

/* The admin console's content column, spaced like the other two shells.

   `auth-layout.html` and `guest-layout.html` both mount `<main id="content"
   class="pj-main">`, and `.pj-main` (prosyjob.css) is what stacks a page's
   blocks with `--pj-gap`. `admin-layout.html` mounts a bare `<main
   id="content">`: the 27 administration screens therefore had NO spacing at
   all between their top-level blocks — measured at 0px, on screens stacking
   up to eight of them.

   The rule cannot live in prosyjob.css, which AssetStacks switches off on
   this stack; hence here.

   20px, the same figure as `.connected-page`, so a page reads with the same
   rhythm whichever console it belongs to. `--pj-gap` (24px) is deliberately
   not reused: that token is the gap BETWEEN the shell's own regions, not
   inside a page. */
.layout-admin #content {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

/* The gap alone did not give 20px: Bootstrap puts `margin-bottom:
   var(--bs-card-group-margin)` (0.75rem) on every `.card`, and the admin
   theme puts its own on `.page-header`. Those margins ADD to the gap —
   measured 36px and 40px between blocks where 20 was intended. Spacing is
   now carried by the gap and nothing else, which is the point of having one.

   Scoped to the direct children of the content column: margins inside a card
   are none of this rule's business. */
.layout-admin #content > * {
    margin-bottom: 0;
}

/* Le même oubli, une strate plus bas : la cible de rendu qui avale le `gap`.

   `.pj-card` ne porte aucune marge — tout l'espacement d'une page vient du
   `gap` de son conteneur. Neuf pages écrivent pourtant leurs cartes dans un
   `<div id="…-body">` intermédiaire, sans classe, posé entre la section et
   elles :

       <section class="jo-public">       <- display: grid; gap: 16px
           <div id="jo-public-body">     <- l'unique enfant, donc le seul
               <article class="pj-card">    élément que ce gap sépare de…
               <article class="pj-card">    personne
           </div>
       </section>

   Le gap de la section s'applique à ce div-là et jamais aux cartes : elles se
   touchaient bord à bord, réduites au filet d'1px de leur bordure là où 16
   étaient prévus (/emplois/offre, la fiche formation, les fiches entreprise
   et organisme, la fiche candidat, les deux pages d'invitation, l'aperçu de
   CV, les statistiques organisme).

   Le div est une cible de rendu, pas une boîte de mise en page. On lui donne
   donc le rythme de la page plutôt que de le rendre transparent avec
   `display: contents` : `querySelector('#…-body').innerHTML = …` reste alors
   ce qu'il paraît être, une écriture dans un conteneur qui sait empiler.
   16px, la valeur des sections qui le contiennent, et `align-content: start`
   pour qu'une page courte n'étale pas ses cartes sur la hauteur disponible.

   Ici plutôt que dans la feuille des composants : huit modules s'en servent,
   et la règle doit valoir sur les trois coquilles. */
.pj-stack {
    display: grid;
    gap: 16px;
    align-content: start;
    /* Une pile est à une colonne, et cette colonne ne doit jamais s'élargir
       pour un contenu trop large : sans `minmax(0, 1fr)`, sa piste hérite du
       minimum `auto` et une carte plus large que la colonne centrale la fait
       grandir au lieu de déborder d'elle-même. C'est par ce chemin que la
       carte de « Mon site web » est passée sous le rail (2026-08-30) : la
       piste de `#wbs-body` mesurait 871 px dans un élément de 765. Le contenu
       trop large doit désormais déborder de sa propre pile — visible, donc
       corrigeable — plutôt que déformer la page autour de lui. */
    grid-template-columns: minmax(0, 1fr);
}

/* ── 2b. Shell variants the template did not ship ──────────────────────
   prosyjob.css defines the 3-column shell and a 2-column `--norail`, but
   nothing for a logged-out visitor: the template's own pages are all
   connected. 16 of our page classes serve `auth: "public"` routes and so
   render in the guest layout too, where a user sidebar would be nonsense. */
.pj-shell--guest {
    grid-template-columns: minmax(0, 1fr);
}

/* Guest topbar: no avatar or bell, a pair of CTAs instead. */
.pj-topbar__cta {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    white-space: nowrap;
}

/* The compact forms exist in the markup and show only on a small phone. */
.pj-topbar__cta-icon,
.pj-topbar__cta-short {
    display: none;
}

/* The logo never yields its width: an `img` whose max-width is a percentage
   of a shrinking flex item can reach 0px, which it did at 390px. */
.pj-app .pj-topbar__brand img {
    max-width: none;
}

/* Under 480px the guest pair did not fit (2026-10-13): actions 401px wide in
   a 375px bar. Login becomes a 44px icon button, register its short label. */
@media (max-width: 480px) {
    .pj-topbar__cta-label {
        display: none;
    }

    .pj-topbar__cta-icon,
    .pj-topbar__cta-short {
        display: inline;
    }

    .pj-topbar__cta--login {
        width: 44px;
        min-width: 44px;
        padding-left: 0;
        padding-right: 0;
        justify-content: center;
    }
}

/* Every bare <button> in the connected shell must opt out of the component
   sheet's full-width contract. Scoped to the shell so it cannot leak into a
   page. 
.pj-topbar button,
.pj-side button,
.pj-rail button {
    width: auto;
}*/

/* Tap targets. The project rule is >= 44px and it is measured, not eyeballed
   (`.btn--sm` sat at 38px for five milestones because nobody checked). The
   template's sidebar links come out at 43px from padding alone, so the floor
   is set explicitly rather than tuned via padding. */
.pj-nav a,
.pj-shortcut,
.pj-lang {
    min-height: 44px;
}

.pj-nav a {
    box-sizing: border-box;
}

/* ── L'accordeon de la barre laterale ──────────────────────────────────
   Demande en recette : cinq rubriques, repliees par defaut, « pour eviter
   que le menu ne paraisse surcharge ».

   Le bouton PORTE deja `.pj-side__label` (prosyjob.css) et lui emprunte donc
   sa typographie ; ces regles ne font que le rendre cliquable sur toute la
   largeur et lui donner sa hauteur de cible. `all: unset` n'est pas utilise :
   il effacerait aussi le libelle herite. On defait a la main les trois
   proprietes que l'agent utilisateur pose sur un <button> et rien d'autre.

   La fleche tourne au lieu de changer d'icone : deux glyphes different
   demanderaient un aller-retour dans le DOM a chaque bascule, et une
   transition sur `transform` est la seule chose ici que le compositeur peut
   animer sans reflow. `prefers-reduced-motion` la coupe — une fleche qui
   pivote n'apprend rien de plus qu'une fleche deja pivotee. */
.pj-side__group {
    display: block;
}

.pj-side__toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    min-height: 44px;
    background: none;
    border: 0;
    text-align: left;
    cursor: pointer;
    /* `.pj-side__label` (prosyjob.css) fournit deja taille, graisse, casse,
       interlettrage et couleur : ce sont des regles d'auteur, elles battent
       l'agent utilisateur sans qu'on ait a les repeter. Seule la FAMILLE doit
       etre reprise — c'est la seule que `.pj-side__label` ne declare pas, donc
       la seule ou le `font: 400 13.33px Arial` du <button> passerait.

       Ne pas ecrire `font: inherit` ici : la forme courte reinitialise aussi
       taille et graisse, et le libelle est ressorti en 16px maigre sans
       capitales — mesure, pas suppose. */
    font-family: inherit;
}

.pj-side__toggle:hover,
.pj-side__toggle:focus-visible {
    color: var(--pj-ink);
}

.pj-side__chev {
    font-size: 10px;
    flex: none;
    transition: transform .18s ease;
}

/* Ferme = fleche vers la droite ; ouvert = vers le bas (l'orientation du
   glyphe au repos). L'etat est lu sur `aria-expanded`, qui est la meme source
   que celle annoncee aux lecteurs d'ecran : impossible que le dessin et
   l'annonce divergent. */
.pj-side__toggle[aria-expanded="false"] .pj-side__chev {
    transform: rotate(-90deg);
}

@media (prefers-reduced-motion: reduce) {
    .pj-side__chev {
        transition: none;
    }
}

/* Bootstrap buttons come out at 41px here, `.btn-lg` included — under the
   floor, and it is the primary action on most screens. The component sheet
   used to set this on `.btn` behind the E7 gate; the gate went in E7-14, so
   this is now the only place the floor is stated for the pj dialect.
   Scoped to `.pj-app`, which is the guest and connected shells only: the
   admin console's table-row icon buttons are `.btn-sm` by design and its
   layout does not carry this class.
   Inline text links are deliberately not included — WCAG 2.5.5 exempts a
   link inside a block of text, and padding them out would wreck the copy. */
.pj-app .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
}

/* prosyjob.css drops the round buttons to 40px below 420px to fit the bar.
   That is exactly the width where tap accuracy matters most, and 44px still
   fits: four of them plus the burger and the language pill come to ~266px of
   a 360px viewport. Measured at a real 357px viewport, not eyeballed. */
@media (max-width: 420px) {
    .pj-iconbtn,
    .pj-burger {
        width: 44px;
        height: 44px;
    }
}

/* The legal links (E14-04). They used to sit at the bottom of the connected
   sidebar, which the guest shell does not have — so a logged-out visitor, the
   one most likely to be looking for a privacy policy, never saw them. They are
   in both footers now, and the sidebar's "À propos" block is gone.

   Five links have to wrap at 360px, so this is a wrapping flex row with a gap
   rather than the `·`-separated span it replaces: a hard separator between
   items that wrap leaves an orphaned dot at the end of a line. */
.pj-footer__links {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    column-gap: 16px;
    row-gap: 0;
}

/* The five authentication screens paint their own header; BasePage sets this
   from `get chrome()` so the shell's bar and footer step aside rather than
   duplicating theirs. (The guest home does NOT — since E7-02 the shell emits
   the same bar it used to draw itself.) */
.is-chromeless .pj-topbar,
.is-chromeless .pj-footer,
.is-chromeless #code-resume {
    display: none;
}

/* Those pages are `body.account-page`, whose own `.content` supplies the
   vertical rhythm of the centred card. The shell grid's padding would stack
   on top of it and push the card off-centre. */
.is-chromeless .pj-shell {
    padding: 0;
    max-width: none;
}

/* The template ships one card width. Registration collects a whole identity
   and verification carries two independent forms side by side, so both need
   more room than a two-field sign-in. */
.login-right--wide {
    max-width: 720px;
}

/* …and on its own it has never widened anything (found E50, 2026-09-14).
   The vendor theme caps the OUTER box — `.account-page .account-content`,
   `width: 480px; max-width: 480px` at (0,2,0) — so a max-width set on an inner
   element cannot make the card bigger; it can only make it smaller. Both
   screens have been living in 418px of usable width, which is why the verify
   page's two `col-md-6` cards sat at ~200px each and nobody could tell.

   The cap has to be lifted where it is set, at the same specificity, from the
   sheet that loads last. Two modifiers because the two screens want different
   things: a roomier column of fields, and a row of cards.

   A measured fault, invisible in review: `.login-right` reported `max-width:
   720px` and a `width` of 418px in the same computed style. */
.account-page .account-content--wide {
    width: auto;
    max-width: 720px;
}

.account-page .account-content--cards {
    width: auto;
    max-width: 1040px;
}

/* Et son padding sur téléphone, ici pour la même raison de feuille : le
   vendeur pose `padding: 30px` à (0,2,0) et `prosyjob-components.css`
   charge AVANT lui, donc la famille `.vf-` peut vivre là-bas mais pas
   cette ligne-ci. Sans elle les six cases du code tombent à 37,8px,
   sous la cible tactile — la règle avait été écrite dans l'autre
   feuille et ne s'appliquait pas, ce que seule la mesure a dit. */
@media (max-width: 575px) {
    .account-page .account-content--cards {
        padding: 20px 10px;
    }
}

/* Le bouton principal de l'écran mérite d'être plus haut que le plancher.
   `.pj-app .btn` pose déjà `min-height: 44px` pour toute la coquille — ce
   qui est pourquoi chaque bouton mesuré ici tombe pile sur la cible
   tactile, par construction et non par chance. Il faut donc deux classes
   pour le dépasser : à une seule, la règle existe et ne s'applique pas. */
.pj-app .vf-continue {
    min-height: 48px;
}

/* `.pj-stats` is a fixed three columns, which orphans the fourth card when a
   grid holds four — the dashboard has exactly four profiles. Auto-fit keeps
   the tiles even at any count and needs no breakpoint of its own. */
.pj-stats--auto {
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

/* A `.pj-stat` shows one number. The dashboard's profile tiles carry two or
   three counters plus an action, so they lay them out in a row and the card
   becomes a column with the button pinned to the bottom — otherwise tiles with
   different counter counts end up with buttons at different heights. */
.pj-stat {
    display: flex;
    flex-direction: column;
}

.pj-stat__row {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
    margin: 4px 0 18px;
}

.pj-stat__row .pj-stat__num {
    font-size: 26px;
}

.pj-stat .btn {
    margin-top: auto;
}

/* Not-yet-activated profiles read as an invitation, not as data. */
.pj-stat--muted .pj-stat__ico {
    background: color-mix(in srgb, var(--pj-muted) 14%, transparent);
    color: var(--pj-muted);
}

.pj-stat--muted .pj-stat__desc {
    margin-bottom: 18px;
}

/* ── Controls that must work on every stack ────────────────────────────
   The theme toggle and the language pill appear in all three shells, but the
   classes the template gives them (`.pj-iconbtn`, `.pj-lang`) live in
   prosyjob.css — the *front* theme, which AssetStacks switches off for the
   admin console. Their baseline therefore has to live here, in the one sheet
   that is always applied. The front theme still layers its own look on top
   where it is active. */
.pj-theme-toggle {
    width: 40px;
    height: 40px;
    min-height: 40px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--pj-line);
    border-radius: 50%;
    background: transparent;
    color: inherit;
    font-size: 16px;
    cursor: pointer;
}

.pj-theme-toggle:hover {
    background: var(--pj-orange-soft);
    color: var(--pj-orange);
}

.pj-lang-trigger {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    white-space: nowrap;
    cursor: pointer;
}

/* In the admin header the trigger is a <button> inside a `.dropdown`, so the
   theme's `.pj-flag-nav > .nav-link` rule (a direct-child selector) never
   matches it. Give it the same shape here rather than reshuffling the markup
   to satisfy a selector. */
.layout-admin .pj-lang-trigger {
    background: transparent;
    border: 0;
    color: var(--pj-ink);
    padding: 0 6px;
}

/* Both controls are bare <button>s, and prosyjob-components.css neutralises those for the
   admin console with `.layout-admin button { width: auto; min-height: auto }`
   — one class plus one type, which outranks the plain `.pj-theme-toggle`
   above and flattened the toggle to 20x40 and the language pill to 85x24.
   Restated at two classes so it wins, and at 44px because that is the tap
   target this project holds itself to and these sit in the header of a
   console that is used on a phone like everything else.
   Those neutralisations are still there; the 44px floor has to outrank them. */
.layout-admin .pj-theme-toggle,
.layout-admin .pj-lang-trigger {
    min-height: 44px;
}

.layout-admin .pj-theme-toggle {
    width: 44px;
    height: 44px;
}

/* Tap targets in the console.
   E7-04 exempted the admin buttons from the 44px floor — "the admin console's
   table-row icon buttons are `.btn-sm` by design" — but the result was never
   measured. At a real 356px viewport they come out 31px tall, and they are the
   edit and delete controls of every list in the console; the sidebar links
   land at 43 and the header's "+" at 42. The floor is a project rule, not a
   preference, and an administrator holds the same phone as everyone else.
   Raised here rather than by un-sizing `.btn-sm`, so the buttons keep their
   compact type and padding and only the hit area grows. Re-measured at 356px:
   no row wraps, nothing overflows. */
.layout-admin .btn {
    min-height: 44px;
}

.layout-admin .add-button {
    width: 44px;
    height: 44px;
}

.layout-admin .sidebar-menu li a {
    min-height: 44px;
}

/* The console menu has to be scrollable, because it is taller than any screen.
   The vendor sets `.sidebar { position: fixed; top: 60px; bottom: 0;
   overflow-y: hidden }` and hands the scrolling to the jQuery slimScroll
   plugin — which was never vendored (`$.fn.slimScroll` is undefined, and the
   `.slimScrollDiv` it would build is nowhere in the page). So the overflow
   stayed hidden with nothing taking over, and the menu was simply cut off with
   no way to reach the rest.

   Nothing failed loudly, which is why it lasted: the clipped rubrics are still
   reachable by typing their URL, so whoever knows the way never notices. The
   25 entries measure 1495px, needing a 1555px viewport — at 1366x768, fifteen
   of them were unreachable, Référentiels, Pays & tarifs and Traductions among
   them.

   The browser does this better than the plugin would. `overscroll-behavior`
   keeps the wheel from scrolling the page behind once the menu bottoms out. */
.layout-admin .sidebar {
    overflow-y: auto;
    overscroll-behavior: contain;
    /* The platform scrollbar is 15px of grey against a soft `#FFF8F7` panel,
       which reads as a seam down the menu. Thin, in the token line colour, and
       it stays a real scrollbar — visible, draggable, and there before the user
       hovers, which an overlay one would not be. No label is truncated by the
       width it takes: measured at 240px box / 225px usable. */
    scrollbar-width: thin;
    scrollbar-color: var(--pj-line) transparent;
}

.layout-admin .user-menu .nav-item {
    display: flex;
    align-items: center;
}

/* The vendor theme's custom checkbox paints its checked state with the theme's
   *own* orange `#E65425`, not the Prosyjob `#EF5A28`. prosyjob.css patches
   `.btn-primary` and friends but never got to `.checkmark`, and these appear
   on every filter panel — the one place two nearly-identical oranges sit next
   to each other and read as a mistake. */
.pj-app .custom_check input:checked ~ .checkmark {
    /* A filled box carrying a white tick — a surface, so it takes the E8-03
       fill like every other one. */
    background-color: var(--pj-orange-fill);
    border-color: var(--pj-orange-fill);
}

/* The template's bell indicator is a bare 8px dot. The app has always shown
   an unread *count*, which is strictly more information, so the dot grows a
   pill form when NavComponent gives it text.

   `.pj-iconbtn` is load-bearing, not decoration. The vendored rule is
   `.pj-iconbtn .pj-dot` — (0,2,0) — and this class alone is (0,1,0), so every
   one of the five properties below that the vendor also sets (top, right,
   width, height, border-radius) was LOSING, whatever the sheet order. The
   count pill rendered 18×8 with a 50% radius: a squashed orange ellipse with
   the number clipped out of it. Measured, then compared side by side against
   the vendored rule suppressed.

   Fixed here rather than by editing the generated theme: `vendor-template.mjs`
   rewrites that tree from upstream, so a hand-edit there is reverted by the
   next run. Raising our own selector keeps the bare 8px dot intact for any
   future use and leaves the vendored file untouched. */
.pj-iconbtn .pj-dot--count {
    width: auto;
    min-width: 18px;
    height: 18px;
    top: 2px;
    right: 2px;
    padding: 0 5px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
    color: #fff;
    border-radius: var(--pj-radius-pill);
}


/* ── 2b. Shared referential controls ───────────────────────────────────
   ReferenceSelect, CityAutocomplete and SkillPicker are imported by 15, 8 and
   4 modules respectively, so they are re-skinned once here and every module
   inherits it. Moved out of the component sheet because they are shared
   infrastructure that every stack needs, including the admin console.

   The markup stays bespoke on purpose. Remapping the autocomplete onto
   Bootstrap's `.dropdown-menu` would buy nothing and would put the
   `is-active` class the keyboard handler toggles at risk. Only the colours
   move to tokens, so dark mode comes for free. */
.rf-autocomplete-wrap {
    position: relative;
}

.rf-autocomplete {
    position: absolute;
    z-index: 20;
    top: calc(100% + 2px);
    left: 0;
    right: 0;
    margin: 0;
    padding: 4px;
    list-style: none;
    max-height: 260px;
    overflow-y: auto;
    background: var(--pj-card);
    border: 1px solid var(--pj-line);
    border-radius: var(--pj-radius-sm);
    box-shadow: var(--pj-shadow);
}

.rf-autocomplete__item {
    display: flex;
    align-items: baseline;
    gap: 8px;
    /* 44px: the minimum tap target this project holds itself to. */
    min-height: 44px;
    padding: 10px 12px;
    border-radius: 8px;
    cursor: pointer;
}

.rf-autocomplete__item:hover,
.rf-autocomplete__item.is-active {
    background: var(--pj-orange-soft);
}

.rf-autocomplete__item small {
    color: var(--pj-muted);
    font-size: 0.8rem;
}

.rf-autocomplete__item em {
    margin-left: auto;
    font-style: normal;
    font-size: 0.72rem;
    color: var(--pj-orange-text);
}

.rf-autocomplete__item--new {
    color: var(--pj-orange);
}

.rf-autocomplete__empty {
    padding: 12px;
    color: var(--pj-muted);
    font-size: 0.85rem;
}

/* LocationSelect (E56) — « ville » and « quartier » as a list with a search.
   The trigger is the form's own `.form-control`, dressed by `.select-caret`
   (§ the select chevron, below), so it is the same height, border and caret
   as the country `<select>` beside it. Only the panel is new, and it borrows
   the autocomplete's surface so the two never look like different products. */
.rf-lsel {
    position: relative;
}

/* The trigger is `readonly` so that tapping it never raises the keyboard, but
   it is a select, not a locked field: it keeps the look of the `<select>`
   beside it. Only the cursor is set here; the dark theme's repaint of
   readonly fields is answered below, next to the rule it answers. */
.rf-lsel > .form-control[readonly]:not(:disabled) {
    cursor: pointer;
}

.rf-lsel__panel {
    position: absolute;
    z-index: 30;
    top: calc(100% + 2px);
    left: 0;
    right: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 6px;
    background: var(--pj-card);
    border: 1px solid var(--pj-line);
    border-radius: var(--pj-radius-sm);
    box-shadow: var(--pj-shadow);
}

.rf-lsel__search.form-control {
    min-height: 44px;
}

.rf-lsel__list {
    margin: 0;
    padding: 0;
    list-style: none;
    /* Half the screen at most: on a phone the field above must stay visible,
       so the person still sees what they are choosing for. */
    max-height: min(300px, 50vh);
    overflow-y: auto;
    overscroll-behavior: contain;
}

.rf-lsel__item {
    display: flex;
    align-items: baseline;
    gap: 8px;
    min-height: 44px;
    padding: 11px 12px;
    border-radius: 8px;
    color: var(--pj-ink);
    font-size: var(--pj-fs-body);
    cursor: pointer;
}

.rf-lsel__item:hover,
.rf-lsel__item.is-active {
    background: var(--pj-orange-soft);
}

.rf-lsel__item.is-selected {
    font-weight: 600;
}

/* Legacy names come with no spaces at all (« Yaound01001100160… »): in a
   narrow filter column one of them would scroll the whole list sideways. */
.rf-lsel__label {
    min-width: 0;
    overflow-wrap: anywhere;
}

.rf-lsel__item small {
    color: var(--pj-muted);
    font-size: var(--pj-fs-xs);
}

.rf-lsel__item em {
    margin-left: auto;
    font-style: normal;
    font-size: var(--pj-fs-2xs);
    color: var(--pj-muted);
    white-space: nowrap;
}

.rf-lsel__item--none {
    color: var(--pj-muted);
}

/* « Autre (préciser) » closes the list, apart from it: it is not a place,
   it is the way out when the place is missing. */
.rf-lsel__item--other {
    align-items: center;
    margin-top: 4px;
    border-top: 1px solid var(--pj-line);
    border-radius: 0 0 8px 8px;
    color: var(--pj-orange-text);
    font-weight: 600;
}

.rf-lsel__note {
    padding: 10px 12px;
    color: var(--pj-muted);
    font-size: var(--pj-fs-sm);
}

/* « Choisir dans la liste », under a field switched to « Autre ». A text
   link sized as a tap target, not a second button competing with Save. */
.rf-lsel__back {
    display: inline-flex;
    align-items: center;
    width: auto;
    min-height: 44px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--pj-orange-text);
    font-size: var(--pj-fs-sm);
    text-decoration: underline;
    cursor: pointer;
}

/* Chips (skills, aliases, interests) */
.rf-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin: 0 6px 6px 0;
    padding: 6px 8px 6px 12px;
    background: color-mix(in srgb, var(--pj-navy) 8%, transparent);
    border: 1px solid color-mix(in srgb, var(--pj-navy) 18%, transparent);
    border-radius: var(--pj-radius-pill);
    font-size: 0.85rem;
    color: var(--pj-ink);
}

/* A skill the user typed that does not exist yet — pending curation. */
.rf-chip--new {
    background: var(--pj-orange-soft);
    border-color: color-mix(in srgb, var(--pj-orange) 35%, transparent);
}

/* Read-only chip: same shape, no remove button. */
.rf-chip--static {
    background: color-mix(in srgb, var(--pj-navy) 8%, transparent);
    border-color: color-mix(in srgb, var(--pj-navy) 18%, transparent);
}

.rf-chip__remove {
    width: 24px;
    height: 24px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: color-mix(in srgb, var(--pj-ink) 8%, transparent);
    color: inherit;
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
}

.rf-chip__remove:hover {
    background: color-mix(in srgb, var(--pj-ink) 18%, transparent);
}

.rf-skills__chips {
    margin-bottom: 8px;
}

.rf-skills__hint,
.rf-skills__empty {
    display: block;
    margin-top: 6px;
    font-size: 0.8rem;
    color: var(--pj-muted);
}

/* BaseForm emits Bootstrap's `.invalid-feedback` since E7, but templates that
   have not been converted yet still carry static `<span class="field-error">`
   placeholders they fill themselves. Aliasing the two keeps a half-converted
   form from showing two different error styles. Deleted in Wave 9 with the
   last `.field-error` in the templates. */
.field-error {
    color: var(--bs-form-invalid-color, #dc3545);
    font-size: 0.875em;
}

/* BaseForm marks the offending control `is-invalid`, but Bootstrap only paints
   a border for `.form-control.is-invalid`. Legacy templates use bare inputs
   inside `.field`, so on those the flag would set the message and leave the
   field looking fine — the one place a user actually looks. Matching the
   element directly covers both dialects. */
input.is-invalid,
select.is-invalid,
textarea.is-invalid {
    border-color: var(--bs-form-invalid-border-color, #dc3545);
}


/* ── 3. Loading state ──────────────────────────────────────────────────
   The template shipped prosyjob-loader.js for this, but it intercepts every
   link click in the capture phase and only hides on `window.load` — which
   never fires again in an SPA, so it would lock the app on first navigation.
   Its look is kept; the trigger stays our own ref-counted [activity_loader],
   driven by the axios interceptors in framework/core/ApiClient.js. */
.global-loader {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, var(--pj-bg) 82%, transparent);
}

.loader-spinner {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    border: 4px solid var(--pj-line);
    border-top-color: var(--pj-orange);
    border-right-color: var(--pj-orange);
    animation: pj-spin 0.7s linear infinite;
}

@keyframes pj-spin {
    to {
        transform: rotate(360deg);
    }
}

/* Opt-in per call site via BasePage.busy(); never a global listener. */
.pj-btnloading {
    position: relative !important;
    pointer-events: none !important;
    color: transparent !important;
}

.pj-btnloading > * {
    visibility: hidden !important;
}

.pj-btnloading::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 18px;
    height: 18px;
    margin: -9px 0 0 -9px;
    border-radius: 50%;
    border: 2px solid rgb(255 255 255 / 55%);
    border-top-color: #fff;
    animation: pj-spin 0.6s linear infinite;
    visibility: visible !important;
}

@media (prefers-reduced-motion: reduce) {
    .loader-spinner,
    .pj-btnloading::after {
        animation-duration: 2s;
    }
}


/* ── LA MARQUE DE L'ACADÉMIE ───────────────────────────────────────────────

   Posé par `AppShell._applyShellClasses()` sur <html>, et non sur la racine de
   l'application : six boîtes de dialogue s'ajoutent directement au <body>, et
   posée plus bas la marque ne les atteindrait pas — une modale garderait
   l'ancienne palette au milieu d'une section qui en a changé.

   ## Il ne change RIEN pour l'instant, et c'est voulu

   Toutes les valeurs ci-dessous sont celles de la marque Prosyjob, recopiées.
   Le bloc existe pour que le jour où le designer livre la palette de
   l'Académie, ce soit une liste de valeurs à remplir et rien d'autre : pas un
   sélecteur à inventer, pas une cascade à démêler, pas un composant à
   retoucher. Mesuré avant de l'écrire — sur trois écrans de l'Académie, tout
   ce qui se peint suit déjà un jeton, à une exception près traitée plus bas.


   ## Le THÈME l'emporte sur la MARQUE, et c'est pour cela que ce bloc est ici

   Il est déclaré AVANT les blocs de thème. À spécificité égale — les deux
   pèsent (0,1,1) — c'est le dernier qui gagne : le mode sombre redéclare
   `--pj-orange`, `--color-primary` et quatre autres, et il doit continuer à
   gagner. Placé après, ce bloc rendait à l'Académie ses couleurs claires en
   plein thème sombre. Mesuré en l'écrivant, pas déduit.

   Le jour où l'Académie voudra sa propre déclinaison sombre, elle s'écrira
   `:root[data-theme="dark"][data-brand="academy"]` — (0,2,1), donc gagnante
   sur les deux — et se placera APRÈS les thèmes. L'ordre du fichier dit la
   règle : marque, puis thème, puis marque-dans-un-thème.

   ## Les DEUX familles, parce qu'il y en a deux

   `--pj-*` vit ici et habille la coquille ; `--color-*` et `--surface-*`
   vivent dans la feuille des composants et habillent les cartes, les champs,
   les liens. Une marque qui n'en redéclarerait qu'une donnerait une Académie à
   moitié repeinte, et c'est le genre de moitié qu'on ne voit qu'en production.

   N'y mettre que ce qui PORTE la marque. Les gris de texte, les lignes et les
   fonds neutres restent communs : une académie n'a pas besoin de son propre
   gris, et chaque valeur recopiée ici est une valeur à maintenir deux fois. */
:root[data-brand="academy"] {
    /* La couleur d'accent : boutons, icônes, états actifs. */
    --pj-orange: #EF5A28;
    --pj-orange-dark: #D8481A;
    --pj-orange-soft: #FEEDE5;
    --pj-orange-fill: #CE4416;
    --pj-orange-fill-hover: #B03A12;
    --pj-orange-text: #C04017;

    /* La couleur d'appui : barres, aplats profonds. */
    --pj-navy: #34506E;
    --pj-navy-dark: #263D55;

    /* Les liens et les actions primaires des composants. */
    --color-primary: #1a73e8;
    --color-primary-dark: #1558b0;
}

/* ── 4. Dark theme ─────────────────────────────────────────────────────
   Neither vendor theme ships one: zero prefers-color-scheme rules in either.
   Three layers make it up, and only the middle one is ours to write:

     Bootstrap primitives  →  free, via [data-bs-theme="dark"] (5.3.8)
     the .pj-* shell       →  this block, because prosyjob.css is tokenised
     Kofejob overrides     →  Wave 10, over whatever survives the purge

   `data-theme` is set on <html> by the inline script in index.html before
   first paint, so there is no flash. */
:root[data-theme="dark"] {
    --pj-bg: #0f141b;
    --pj-card: #171e28;
    --pj-card-2: #1e2733;

    /* Les metaux, releves pour tenir sur un fond sombre. Chacun garde sa
       teinte -- un partenaire qui change de theme ne change pas de grade. */
    --pj-metal-explorer: #D08A4E;
    --pj-metal-silver: #AEB8C4;
    --pj-metal-gold: #E8B429;
    --pj-metal-platinum: #6FA8BD;
    --pj-metal-diamond: #A68CF0;
    --pj-line: #2a3441;

    --pj-ink: #e8edf4;
    --pj-muted: #94a3b5;

    /* Lifted from #EF5A28: the light-theme orange clears 4.5:1 on a dark
       surface only just, and this keeps a margin for small text. */
    --pj-orange: #ff6b3d;
    --pj-orange-dark: #ff8355;
    --pj-orange-soft: #3a1f14;
    /* E10-02 is a LIGHT-theme correction. Dark already measured 6,23:1 for the
       same elements, and darkening the token here would have taken it to 2,88 —
       the same trap the pale chips sprang in E8-02. So the text token simply
       keeps the value those elements already resolved to. */
    --pj-orange-text: #ff8355;

    /* Lifted for the same reason as the orange: as text or as a border these
       sit on a #171e28 card, where the light values measure 2,3:1 and 2,6:1 —
       below the 3:1 that WCAG 1.4.11 asks of a control's own outline, let alone
       the 4,5:1 of its label. Measured against that card: #F1707A gives 5,86:1
       and #42C87A 7,80:1.

       Their `-fill` halves are deliberately NOT lifted, exactly like
       `--primary-fill` below: those carry a white label, white on #CC3038 is
       already 5,19:1, and a solid signal colour must not change meaning with
       the theme. */
    --pj-danger: #f1707a;
    --pj-success: #42c87a;

    /* The status tints. A tint is "lighter than the card" in light and
       "darker than the card" in dark — inverted, not lightened, which is why
       these are values and not a filter. Each sits 1,04-1,19:1 off
       `--pj-card`, enough to still read as a chip. */
    --pj-success-soft: #14301F;
    --pj-danger-soft: #3A1A1E;
    --pj-warning-soft: #33260F;
    --pj-warning-ink: #F0BF5C;
    --pj-info-soft: #16263F;
    --pj-info-ink: #7FA8F5;
    --pj-neutral-soft: #232C38;
    --pj-neutral-ink: #A8B4C4;
    --pj-accent-soft: #261A3D;
    --pj-accent-ink: #B18CF5;

    /* Navy is the light theme's structural colour; on dark it has to come up
       to stay visible as anything other than background. */
    --pj-navy: #4e7099;
    --pj-navy-dark: #3d5a7d;
    --pj-blue: #6d87e8;

    /* Shadows do not read on dark surfaces — borders carry the elevation. */
    --pj-shadow: 0 0 0 1px rgb(255 255 255 / 4%);
    --pj-shadow-sm: 0 0 0 1px rgb(255 255 255 / 3%);

    color-scheme: dark;
}

/* Legacy tokens, pointed at their dark equivalents.
   =================================================
   prosyjob-components.css defines its own palette in five `:root` blocks and 6 700
   lines hang off it. Repainting those rules one by one would be a second full
   pass over the app; repointing the eleven structural tokens does the whole
   thing at once — and it works on pages that have not been converted yet,
   which is what keeps the app usable in dark mode all the way through E7
   rather than only at the end.

   This remap is what makes that whole sheet follow the theme; it stays as long
   as the component sheet writes `--color-*` rather than `--pj-*`.

   The status palettes (`--ap-*` applications, `--mt-*` matching, `--pay-*`
   payments) are left alone: they are saturated signal colours that read on
   either background, and their meaning must not shift with the theme. */
:root[data-theme="dark"] {
    --color-bg: var(--pj-bg);
    --color-surface: var(--pj-card);
    --color-text: var(--pj-ink);
    --color-muted: var(--pj-muted);
    --color-border: var(--pj-line);

    --connected-bg: var(--pj-bg);
    --connected-surface: var(--pj-card);
    --connected-text: var(--pj-ink);
    --connected-muted: var(--pj-muted);
    --connected-border: var(--pj-line);
    --connected-shadow: var(--pj-shadow-sm);

    /* E7-15: the accent pair was missed the first time round, and it is the
       colour of every "read more", every recommendation score and every active
       filter chip. `#2563eb` on a `#171e28` card measures 1.9:1 — the link that
       tells you where to go next was the least readable text on the page. */
    --color-primary: var(--pj-blue);
    --color-primary-dark: var(--pj-navy);
    --connected-primary: var(--pj-blue);
    --connected-primary-deep: var(--pj-navy);
    /* `--primary-fill` is deliberately NOT lifted — it is the same blue used
       as a solid background under a white label (the active tab of every
       pipeline, the active CMS filter). Lifting it there costs contrast
       instead of buying it: white on #264A78 is 9:1, white on the lifted
       #6d87e8 is 3.3:1. One colour, two jobs, opposite directions — the
       component sheet now names them separately. */
    --connected-accent: var(--pj-orange);
    --connected-accent-soft: var(--pj-orange-soft);

    /* Pale panels the component sheet writes as raw hex. These are surfaces
       carrying theme-following text (`--connected-text` / `--connected-muted`),
       so unlike the status chips they cannot stay light: the background would
       hold still while the text above it turned pale. Named here rather than
       repainted rule by rule, and given their current light values in the
       component sheet's own `:root` so light is unchanged. */
    /* The three signal inks prosyjob-components.css declares and this block
       never remapped. They are the `color` half of the status chips whose
       tint moved to `--pj-*-soft` above: leaving them behind put a dark green
       ink (#17724a) on a dark green tint — `.jo-badge--verified` measured
       2,41:1, worse than before the tint was fixed. A tint and its ink move
       together or not at all. */
    --color-success: var(--pj-success);
    --color-error: var(--pj-danger);
    --color-warning: var(--pj-warning-ink);

    --surface-sunken: var(--pj-card-2);
    --surface-chip: var(--pj-card-2);
    --surface-track: var(--pj-line);
    --chip-ink: var(--pj-ink);
}

/* Bootstrap's dark mode covers `.form-control`, but a legacy template's bare
   `<input>` inside `.field` never matches it, and the component sheet paints those with
   an explicit light background. Same reasoning as the token remap: fix the
   dialect, not the individual pages. */
:root[data-theme="dark"] .pj-app input:not([type="checkbox"]):not([type="radio"]),
:root[data-theme="dark"] .pj-app select,
:root[data-theme="dark"] .pj-app textarea,
/* La richtext box est un champ de saisie en tout point SAUF par sa balise :
   c'est une `<div contenteditable>` qui porte `.form-control` pour sa bordure
   et son focus. Les trois sélecteurs ci-dessus nomment des ÉLÉMENTS, donc elle
   n'en matchait aucun et gardait les couleurs du thème clair. Mesuré le
   2026-10-06 sur l'étape 1 d'un vote, en sombre : texte `#353535` sur fond
   `#212529`, soit un contraste de 1,26 — le champ de texte ordinaire juste
   au-dessus était à 12,81. Le texte saisi était donc invisible, sur TOUS les
   écrans qui offrent cette boîte, pas seulement sur les votes. */
:root[data-theme="dark"] .pj-app .rte__editor {
    background-color: var(--pj-card-2);
    border-color: var(--pj-line);
    color: var(--pj-ink);
}

/* …and the same shape again for the ones that cannot be typed in (E12-04).
   The rule above matches `:disabled` too, so a locked field came out with the
   same background, text and border as an editable one — measured on /profil in
   edit mode, where email and phone are shown but not editable: identical
   computed styles, the cursor the only difference, and a cursor is invisible
   until you hover.

   The selector repeats that shape rather than reading `.form-control:disabled`
   because the rule above is (0,5,1) and would otherwise win on specificity —
   an `!important` here would be papering over the cascade, not using it.
   Repainting a control that is already non-interactive cannot break a working
   screen; it only stops one from lying. */
:root[data-theme="dark"] .pj-app input:not([type="checkbox"]):not([type="radio"]):disabled,
:root[data-theme="dark"] .pj-app input:not([type="checkbox"]):not([type="radio"])[readonly],
:root[data-theme="dark"] .pj-app select:disabled,
:root[data-theme="dark"] .pj-app textarea:disabled,
:root[data-theme="dark"] .pj-app textarea[readonly] {
    background-color: var(--pj-bg);
    color: var(--pj-muted);
    cursor: not-allowed;
}

/* …except the trigger of a LocationSelect (E56), which is `readonly` only so
   that a tap opens its list instead of the keyboard. It is a select, and it
   must paint exactly like the country `<select>` above it — measured, the
   rule above left it at --pj-muted on --pj-bg, the look of a locked field.
   (0,8,1) against the rule's (0,6,1), so no `!important`. */
:root[data-theme="dark"] .pj-app .rf-lsel > input.form-control[readonly][role="combobox"]:not(:disabled) {
    background-color: var(--pj-card-2);
    color: var(--pj-ink);
    cursor: pointer;
}

/* ── Le remplissage automatique de Chrome ──────────────────────────────
   Signale en recette : en thème sombre, le champ « Email » de /connexion
   reste BLANC pendant que le champ mot de passe juste dessous est correct.

   Ce n'est pas une règle qui manque chez nous, c'en est une qui vient du
   thème. `user-template/css/style.css` (et son jumeau côté console,
   `admin-template/css/style.css`) posent :

       input:-webkit-autofill { -webkit-box-shadow: 0 0 0 1000px white inset !important }

   — un blanc en dur, en `!important`, sans thème. Chrome peint ce fond par
   une ombre interne, donc `background-color` ne le recouvre pas : la règle
   sombre plus haut repeint bien l'input, et l'ombre repasse par-dessus.
   Le texte, lui, garde `--pj-ink` (clair) : clair sur blanc, illisible.
   D'où l'asymétrie observée — seul l'identifiant est rempli au chargement
   (`autocomplete="username"`), le mot de passe attend un geste.

   Trois conséquences pour la forme de ce correctif :
   - `!important` est nécessaire, pas décoratif : il faut battre celui du
     thème, et la spécificité seule n'y suffit pas.
   - `-webkit-text-fill-color` double `color`, que Chrome ignore ici.
   - la règle n'est PAS limitée à `.pj-app` : les deux thèmes portent la
     même ligne, et les jetons `--pj-*` sont miroités sur `:root` (§ en tête
     de ce fichier) précisément pour qu'une règle partagée par les trois
     coquilles puisse s'écrire une seule fois.

   Elle vit ici, dans le bloc sombre, parce que c'est là qu'on la cherchera —
   mais la première moitié est bien commune aux deux thèmes. Se vérifie avec
   un identifiant *enregistré* dans le navigateur : taper la valeur à la main
   ne déclenche pas `:-webkit-autofill`. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active,
textarea:-webkit-autofill,
select:-webkit-autofill {
    -webkit-box-shadow: 0 0 0 1000px var(--pj-card) inset !important;
    -webkit-text-fill-color: var(--pj-ink) !important;
    caret-color: var(--pj-ink);
}

/* En sombre, l'input plein est `--pj-card-2` (règle plus haut) et non
   `--pj-card` : sans ce second passage, un champ rempli automatiquement
   serait d'un ton plus foncé que son voisin vide. */
:root[data-theme="dark"] input:-webkit-autofill,
:root[data-theme="dark"] input:-webkit-autofill:hover,
:root[data-theme="dark"] input:-webkit-autofill:focus,
:root[data-theme="dark"] input:-webkit-autofill:active,
:root[data-theme="dark"] textarea:-webkit-autofill,
:root[data-theme="dark"] select:-webkit-autofill {
    -webkit-box-shadow: 0 0 0 1000px var(--pj-card-2) inset !important;
}

:root[data-theme="dark"] .pj-app label,
:root[data-theme="dark"] .pj-app h1,
:root[data-theme="dark"] .pj-app h2,
:root[data-theme="dark"] .pj-app h3,
:root[data-theme="dark"] .pj-app h4 {
    color: var(--pj-ink);
}

/* The vendor themes hardcode white backgrounds on the shell chrome, so the
   handful of places prosyjob.css did not tokenise are corrected here. */
:root[data-theme="dark"] .pj-burger,
:root[data-theme="dark"] .pj-iconbtn,
:root[data-theme="dark"] .pj-lang {
    background: var(--pj-card-2);
    color: var(--pj-ink);
}

:root[data-theme="dark"] .pj-search input {
    background: var(--pj-card-2);
    color: var(--pj-ink);
}

:root[data-theme="dark"] .pj-nav a:hover {
    background: var(--pj-card-2);
}

/* The brand mark is navy + orange on transparent and disappears against a
   dark surface. Until the designer supplies a light variant, lift it just
   enough to read. See the "Known gap" note in assets/VENDORING.md.
   The admin header carries the same mark (measured on a 1300px viewport: the
   navy "Prosy" and "ob" were all but gone, leaving a floating orange J), so
   it takes the same treatment — the two shells must not show two different
   logos to the same person switching between them. */
:root[data-theme="dark"] .pj-topbar__brand img,
:root[data-theme="dark"] .layout-admin .header .logo img {
    filter: brightness(0) invert(1) sepia(1) saturate(6) hue-rotate(346deg);
}

/* Authentication screens.
   The vendor theme hardcodes this whole surface light — `.account-page` is
   `#fff`, the card is `#fff`, and `.focus-label` is `#131135`. Converted in
   Wave 1, so they get their dark treatment here rather than waiting for
   Wave 10: a page that ships in a broken state in one theme is not converted,
   it is half-converted. */
:root[data-theme="dark"] body.account-page {
    background-color: var(--pj-bg);
}

:root[data-theme="dark"] .account-page .content {
    background: transparent;
}

:root[data-theme="dark"] .account-content {
    background-color: var(--pj-card);
    border: 1px solid var(--pj-line);
}

:root[data-theme="dark"] .account-content .focus-label,
:root[data-theme="dark"] .account-page .login-header h3 {
    color: var(--pj-ink);
}

:root[data-theme="dark"] .account-page .form-control {
    background-color: var(--pj-card-2);
    border-color: var(--pj-line);
    color: var(--pj-ink);
}

:root[data-theme="dark"] .account-page .form-control::placeholder {
    color: var(--pj-muted);
}

:root[data-theme="dark"] .account-page .dont-have,
:root[data-theme="dark"] .account-page .text-muted,
:root[data-theme="dark"] .account-page .toggle-password {
    color: var(--pj-muted) !important;
}

/* The auth screens' fixed top-right slot holds the theme toggle beside the
   language pill; prosyjob.css sizes the container for one control only. */
.pj-auth-lang {
    display: flex;
    align-items: center;
    gap: 10px;
}

/* ── The avatar placeholder ────────────────────────────────────────────
   `is-default-avatar` was set on the top bar's and the sidebar's <img> and
   styled NOWHERE: the only rule naming it targets
   `.connected-profile-menu__trigger`, a different element that paints its
   picture as a background. So the placeholder fell through to the vendored
   `.pj-avatar-btn img` / `.pj-side__user img`, which are `object-fit: cover` —
   and the asset was a 76x50 LANDSCAPE thumbnail, so roughly a third of its
   width was cropped away inside a round slot.

   `img.` for the same reason as the terms link and the bell badge: those two
   vendored rules are (0,1,1), a bare class is (0,1,0) and loses however late
   it is declared. Qualifying by tag ties and wins on order.

   `contain` because a placeholder must never be cropped, and the surface is a
   token so it follows the theme — the asset is transparent precisely so this
   rule owns the colour.

   No padding here on purpose. A percentage padding resolves against the
   PARENT's width, not the image's, which measured 6px in the top bar and 32px
   in the sidebar — and 32px of padding on a 46px `box-sizing: border-box` box
   pushed the element out to 64px. The breathing room belongs inside the SVG's
   own viewBox, where it is the same at every size. */
img.is-default-avatar {
    object-fit: contain;
    object-position: center;
    background-color: var(--pj-card-2);
}


/* ── A select that looks like one ──────────────────────────────────────
   `select.form-control` is `appearance: none` with nothing put back, so every
   select in the app painted exactly like a text input. This was opt-in
   (`.select-caret`, three screens) while the gap was logged in the plan as
   too wide for one task to verify; the PO asked for it everywhere
   (2026-09-29), so it is now the default for every single-choice select,
   whatever its class — `.form-control`, `.form-select`, `.field__control` or
   a bespoke one. A list box (`multiple`, `size`) shows its options and needs
   no caret; the language switcher draws its own. `.select-caret` stays as an
   alias so the three screens that name it keep working.

   `:not()` three times puts the rule at (0,3,1): above `.pj-app .form-control`
   (0,2,0) and anything else in the vendored sheets that might repaint the
   background, without an `!important`. Only the `background-image` family is
   set, never the `background` shorthand, so a page's own background colour —
   and the dark theme's below — are left alone.

   Lives here rather than in the component sheet because this file is on all
   three stacks (the admin console switches the front theme off).

   A `mask` would let the colour be a token, but it needs a wrapper element to
   hang off — the data URI carries the colour instead, which is why the dark
   theme gets its own copy. That is the exception the no-raw-hex rule is worth
   making, not a licence to spread it: the two strokes are `--pj-muted` in each
   theme (#657085 / #94a3b5) and must be changed with it, since no checker can
   see inside a URL. */
select:not([multiple]):not([size]):not(.lang-switcher),
.select-caret {
    /* The native arrow goes, or a select that was not already
       `appearance: none` — a bare `.field select`, most of the connected
       screens — showed two: the browser's and this one (2026-10-05). */
    -webkit-appearance: none;
    appearance: none;
    padding-right: 34px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23657085' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 14px 14px;
}

:root[data-theme="dark"] select:not([multiple]):not([size]):not(.lang-switcher),
:root[data-theme="dark"] .select-caret {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2394a3b5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
}


/* ── Registration: dialling code + local number (E12-02) ───────────────
   One field to the eye, two controls to the DOM.

   The select is sized by its content, and the pair is allowed to WRAP. Both
   were measured, not assumed: `.form-control` spends 30px on padding and a
   native select ~20px on its arrow, so a select given 45% of a 360px card has
   69px left for a label that needs 107 — every country came out truncated,
   and "+225 Côte d'Ivoire" was still cut at 768. Splitting the row differently
   only moves the problem onto the number input.

   So on a phone they stack (each control full width, nothing clipped) and
   from ~330px of available row they sit side by side. `flex-basis: 14ch` on
   the number is what decides that: it is the width below which a 9-digit
   number stops being readable, and the point at which the row breaks. */
.pj-phone {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: stretch;
}

.pj-phone__code {
    width: auto;
    flex: 0 0 auto;
}

.pj-phone__number {
    flex: 1 1 14ch;
    min-width: 0;
}

/* The terms row is a `.pj-consent` (§2bis) since 2026-10-13 — the layout that
   lived here is that one now. Only the link's colour stays below. */

/* `a.` — not a stylistic flourish. prosyjob.css carries `.pj-app a { color:
   inherit }`, which is specificity (0,1,1); a bare `.pj-terms__link` is
   (0,1,0) and loses however late it is declared, so the link painted as body
   text and stopped looking like a link at all. Qualifying by tag ties the
   specificity and wins on order, and unlike `.pj-app .pj-terms__link` it still
   holds if this control is ever used outside the pj shell. */
a.pj-terms__link {
    color: var(--pj-orange-text);
    text-decoration: underline;
}


/* ── 4b. Dark theme — the guest and connected shells ───────────────────
   prosyjob.css is tokenised and follows the theme; `user-template/style.css`,
   the Kofejob sheet underneath it, is not — and it owns `.card`, which is the
   frame of nearly every screen in the user space. Measured across 25 user
   screens before this block: `.card` stayed `#fff` while Bootstrap's dark mode
   lightened the text inside it, so headings came out at 1.2:1 and body copy at
   1.3:1 — the same failure the admin console had, one shell over.

   Same method as the console (E7-13): a luminance probe for surfaces that stay
   light, a contrast pass for text under 4.5:1, both re-run in light so that
   what fails in *both* themes is not counted as a dark regression. */

:root[data-theme="dark"] .pj-app .card,
:root[data-theme="dark"] .pj-app .card-header,
:root[data-theme="dark"] .pj-app .card-footer {
    background-color: var(--pj-card);
    border-color: var(--pj-line);
}

/* Bootstrap's muted grey is `#6c757d` whatever the theme: 3.5:1 on the dark
   card, and it is the colour of every hint, count and secondary line. */
:root[data-theme="dark"] .pj-app .text-muted {
    color: var(--pj-muted) !important;
}


/* La règle qui rattrapait ici le `#6c757d` de Bootstrap en sombre a été
   retirée avec l'harmonisation des boutons (§5) : la classe y est maintenant
   définie dans les deux thèmes, et ce correctif la surchargeait — il gagnait
   par sa spécificité (0,4,0 contre 0,1,0) et remettait un texte gris sur le
   fond orange doux. Le contraste restait bon (5,89:1), donc rien n'aurait
   alerté ; le secondaire aurait simplement été d'une autre couleur en sombre
   qu'en clair, c'est-à-dire exactement la divergence que §5 supprime.
   Un correctif survit à ce qu'il corrigeait : quand la cause disparaît, le
   correctif devient la cause. */

/* The primary button is white on brand orange: 3.4:1 in light, which is a
   brand decision this wave does not touch. In dark it had drifted to 2.8:1,
   and that part *is* ours — `--pj-orange` is lifted to #ff6b3d so orange text
   stays legible on a dark page, which makes it a worse background for white
   text. The fill goes back to the brand orange, so the button reads the same
   in both themes. Getting it past 4.5:1 needs a darker brand orange or a
   non-white label, and that is a call for the designer.

   The fill is restored by redefining the two tokens *on the button*, not by
   another `background-color`: prosyjob.css paints it
   `background-color: var(--pj-orange) !important`, which no ordinary
   declaration can outrank, but a custom property redefined on the element
   changes what that `var()` resolves to. */
:root[data-theme="dark"] .pj-app .btn-primary {
    --pj-orange: #EF5A28;
    --pj-orange-dark: #D8481A;
}

/* `.pj-btn--outline` avait ici un rattrapage de thème sombre, le vendor y
   codant un fond blanc en dur. Il est parti avec §5bis, qui donne à cette
   classe le rôle « secondaire » dans les deux thèmes — un correctif qui
   survit à ce qu'il corrigeait redevient la cause, comme pour
   `.btn-outline-secondary` plus haut. `.pj-eyebrow` et `.pj-shortcut__ico`,
   que cette note mentionnait aussi, gardent le leur : ce ne sont pas des
   boutons. */

:root[data-theme="dark"] .pj-app .pj-eyebrow,
:root[data-theme="dark"] .pj-app .pj-shortcut__ico {
    background: var(--pj-card-2);
    color: var(--pj-ink);
}

:root[data-theme="dark"] .pj-app .pj-shortcut:hover {
    background: var(--pj-card-2);
}

/* Same treatment, three rules prosyjob.css paints with a pale plate and this
   file had not covered: the sidebar's avatar fallback (`#EDF0F5`) and the two
   tinted stat icons on the dashboard (`#EAF0FF`, `#E9EEF5`). They are vendored,
   so they are overridden from here rather than edited. The icon glyph keeps its
   own colour — the vendor sets it from `--pj-blue` / `--pj-navy`, both of which
   are already lifted for dark, so only the plate underneath has to move. */
:root[data-theme="dark"] .pj-app .pj-side__user img,
:root[data-theme="dark"] .pj-app .pj-stat__ico.is-blue,
:root[data-theme="dark"] .pj-app .pj-stat__ico.is-navy {
    background: var(--pj-card-2);
}

/* Navy is the light theme's structural colour and is lifted only a little in
   dark, which is enough behind a border but not enough for a line of text. */
:root[data-theme="dark"] .pj-app .pj-auth-tagline {
    color: var(--pj-muted);
}

/* A *category* chip painted with the "hired" green tint, which no longer works
   once its label lifts. On dark it becomes the neutral raised chip the rest of
   the app uses. */
:root[data-theme="dark"] .pj-app .cms-card__cat {
    background: var(--pj-card-2);
}

/* `/gagner` (2026-09-23) : les deux récompenses se distinguent par la couleur
   de leur titre, pêche pour l'argent, bleu pour les Miles. La règle sombre qui
   remet tous les h3 à `--pj-ink` (0,3,1) l'effaçait ; même spécificité plus
   une classe, avec les jetons que le thème sombre éclaircit déjà. */
:root[data-theme="dark"] .pj-app .co-reward--cash h3 {
    color: var(--pj-orange-text);
}

:root[data-theme="dark"] .pj-app .co-reward--miles h3 {
    color: var(--pj-blue);
}

/* The shared Pagination component renders the template's `.paginations` list,
   whose links are `#161c2d` — near-black, and Bootstrap's `#6c757d` where the
   theme does not reach. 3.9:1 on the dark page, on a control that appears on
   22 screens. */
:root[data-theme="dark"] .pj-app .paginations li a {
    color: var(--pj-ink);
}

:root[data-theme="dark"] .pj-app .paginations li a:hover {
    color: var(--pj-orange);
}


/* ── 5. Dark theme — the admin console ─────────────────────────────────
   The user surfaces get dark almost for free: prosyjob.css is tokenised, so
   the block above repaints it. The Kofejob admin theme is not — it writes
   #fff, #FFF8F7, #131523 and #333 literally — and it is the one place where
   "no dark theme" is worse than it sounds: the console ships the theme toggle
   in its header, and Bootstrap 5.3's `[data-bs-theme="dark"]` obeys it and
   lightens the *text* while the vendor sheet keeps the card backgrounds
   white. Measured before this block: `.card` text came out #dee2e6 on #fff
   (1.3:1) and the active breadcrumb 1.3:1. A working-looking button that
   makes the console unreadable.

   Wave 10 (E7-15) was scheduled to do this. Doing it in the admin wave
   instead, for the same reason Wave 1 took the auth screens: a surface that
   ships broken in one theme is not converted, it is half-converted.

   THE LIST IS MEASURED, NOT TRANSCRIBED. The vendor sheet has 61 rules that
   paint something white; the console only ever shows the handful below. It
   was collected by walking all 27 screens with a probe that reports every
   element whose computed background is lighter than 0.55 luminance, plus a
   contrast pass that reports every text node under 4.5:1 — then re-run in
   light so that what fails in *both* themes is not counted as a dark-mode
   regression. Two known survivors, both pre-existing and both unchanged by
   the theme: white on the brand orange `.btn-primary` is 3.4:1 (a brand
   decision, and it is the same in light), and the pale status chips
   (`.mt-badge`, `.pc-state`, `.sa-state`, `.wd-badge`) stay light on purpose —
   they are signal tints chosen in E7-04 to hold their meaning in both
   themes, and their own text colour keeps them legible. */

:root[data-theme="dark"] body {
    background-color: var(--pj-bg);
    color: var(--pj-ink);
}

:root[data-theme="dark"] .layout-admin .header,
:root[data-theme="dark"] .layout-admin .header .header-left {
    background: var(--pj-card);
    border-bottom-color: var(--pj-line);
}

:root[data-theme="dark"] .layout-admin .sidebar {
    background-color: var(--pj-card);
    border-right: 1px solid var(--pj-line);
}

:root[data-theme="dark"] .layout-admin .sidebar-menu li a {
    color: var(--pj-ink);
}

:root[data-theme="dark"] .layout-admin .sidebar-menu > ul > li > a:hover {
    background-color: var(--pj-card-2);
}

/* The vendor paints the active entry white — the light theme's "raised" cue,
   which on a dark sidebar reads as a hole. Raised means lighter *relative to
   the surface*, so it becomes card-2. */
:root[data-theme="dark"] .layout-admin .sidebar-menu li.active > a {
    background: var(--pj-card-2);
    color: var(--pj-orange);
}

/* The seven section headers, and the active entry, are the vendor's #EF5A28.
   On the dark sidebar that clears 4.4:1 — just under the floor for 14px
   semibold — so they take the lifted dark-theme orange like everything else. */
:root[data-theme="dark"] .layout-admin .menu-title,
:root[data-theme="dark"] .layout-admin .sidebar-menu li.menu-title a {
    color: var(--pj-orange);
}

:root[data-theme="dark"] .layout-admin .card,
:root[data-theme="dark"] .layout-admin .card .card-header,
:root[data-theme="dark"] .layout-admin .card-footer {
    background: var(--pj-card);
    border-color: var(--pj-line);
}

:root[data-theme="dark"] .layout-admin h1,
:root[data-theme="dark"] .layout-admin h2,
:root[data-theme="dark"] .layout-admin h3,
:root[data-theme="dark"] .layout-admin h4,
:root[data-theme="dark"] .layout-admin h5,
:root[data-theme="dark"] .layout-admin h6,
:root[data-theme="dark"] .layout-admin .page-title,
:root[data-theme="dark"] .layout-admin .card .card-header .card-title,
:root[data-theme="dark"] .layout-admin .breadcrumb a {
    color: var(--pj-ink);
}

:root[data-theme="dark"] .layout-admin .text-muted,
:root[data-theme="dark"] .layout-admin .page-header .breadcrumb,
:root[data-theme="dark"] .layout-admin .breadcrumb-item.active,
:root[data-theme="dark"] .layout-admin .dropdown-header {
    color: var(--pj-muted) !important;
}

:root[data-theme="dark"] .layout-admin .table {
    color: var(--pj-ink);
}

:root[data-theme="dark"] .layout-admin .table thead th,
:root[data-theme="dark"] .layout-admin .table tbody tr,
:root[data-theme="dark"] .layout-admin .table td,
:root[data-theme="dark"] .layout-admin .table th {
    border-color: var(--pj-line);
}

:root[data-theme="dark"] .layout-admin .table-hover tbody tr:hover {
    background-color: var(--pj-card-2);
}

:root[data-theme="dark"] .layout-admin .table-hover tbody tr:hover td {
    color: var(--pj-ink);
}

:root[data-theme="dark"] .layout-admin .table-striped > tbody > tr:nth-of-type(2n+1),
:root[data-theme="dark"] .layout-admin .table-striped tbody tr:nth-of-type(odd) {
    background-color: var(--pj-card-2);
}

:root[data-theme="dark"] .layout-admin .table-striped tbody tr:nth-of-type(even) {
    background-color: transparent;
}

:root[data-theme="dark"] .layout-admin .form-control,
:root[data-theme="dark"] .layout-admin .form-select {
    background-color: var(--pj-card-2);
    border-color: var(--pj-line);
    color: var(--pj-ink);
}

:root[data-theme="dark"] .layout-admin .form-control::placeholder {
    color: var(--pj-muted);
}

:root[data-theme="dark"] .layout-admin .modal-content {
    background: var(--pj-card);
    border-color: var(--pj-line);
}

:root[data-theme="dark"] .layout-admin .modal-header,
:root[data-theme="dark"] .layout-admin .modal-footer {
    border-color: var(--pj-line);
}

:root[data-theme="dark"] .layout-admin .dropdown-menu {
    background-color: var(--pj-card);
    border-color: var(--pj-line);
}

:root[data-theme="dark"] .layout-admin .dropdown-item {
    color: var(--pj-ink);
}

:root[data-theme="dark"] .layout-admin .dropdown-item:hover,
:root[data-theme="dark"] .layout-admin .dropdown-item:focus {
    background-color: var(--pj-card-2);
    color: var(--pj-ink);
}

/* Le jumeau console de la règle retirée plus haut : même raison, même sort.
   Voir §5. */

/* The console's referential tabs are the one admin control painted with
   `--color-primary` as a solid fill under a white label, so lifting that token
   for text (E7-15) cost them: 3.6:1 became 3.3:1. Same split as the user side,
   applied here rather than in the component sheet so the light theme keeps the
   brighter blue it has always had on this screen. */
:root[data-theme="dark"] .layout-admin .rf-tab.is-active {
    background: var(--primary-fill);
    border-color: var(--primary-fill);
}

/* The "+" in a page header is a white square with a near-black border — the
   inversion of what it should be on a dark page. Its hover swaps the two, so
   the swap is mirrored too. */
:root[data-theme="dark"] .layout-admin .add-button {
    background: var(--pj-card-2);
    color: var(--pj-ink);
    border-color: var(--pj-line);
}

:root[data-theme="dark"] .layout-admin .add-button:hover {
    background: var(--pj-ink);
    color: var(--pj-bg);
    border-color: var(--pj-ink);
}

:root[data-theme="dark"] .layout-admin .user-menu > li > a,
:root[data-theme="dark"] .layout-admin .user-menu .nav-link {
    color: var(--pj-ink);
}

/* The one status token used as a *surface* rather than as a chip: the active
   row of the matching weight history is a pale green card carrying inherited
   body text, so dark mode lit the text and left the card pale — 1.2:1, the
   single dark-only regression the sweep found across the 27 screens. The
   signal moves to the border, which is what the row already used to say
   "this is the version in force". The other `--*-soft` tokens stay as they
   are: they are chip backgrounds paired with their own saturated text. */
:root[data-theme="dark"] .mt-history__item--active {
    background: var(--pj-card-2);
    border-color: var(--mt-very);
}

/* E8-02 — the pale chips, in dark.
   `.bg-*-light` is a 12% tint of its own hue carrying that hue as text, and the
   tint composites against whatever is behind it. In light that lands on a pale
   surface, so the text has to darken (done in the vendored sheet, via
   vendor-template.mjs). In dark the very same tint composites to a *dark*
   surface, where the original saturated hue already measures 5,1:1 to 7,0:1 —
   darkening it there took it the wrong way, down to 2,7:1.

   So the darkening is a light-theme correction only, and dark restores the
   original hue. Measured against the real composited surface, not against
   white: `.bg-danger-light` is the one whose original was itself short in dark
   (4,17:1) and is lightened instead. */
:root[data-theme="dark"] .bg-info-light    { color: #1db9aa !important; }
:root[data-theme="dark"] .bg-primary-light { color: #2196f3 !important; }
:root[data-theme="dark"] .bg-warning-light { color: #f39c12 !important; }
:root[data-theme="dark"] .bg-success-light { color: #26af48 !important; }
:root[data-theme="dark"] .bg-purple-light  { color: #c580ff !important; }
:root[data-theme="dark"] .bg-danger-light  { color: #e84e4e !important; }

/* ── La colonne de droite s'empile au lieu de disparaître (2026-09-12) ──

   `prosyjob.css` porte `@media (max-width: 1200px) { .pj-rail { display:
   none } }` : sous 1201px, tout ce que porte la colonne — le menu contextuel
   de la rubrique, l'encart promo, les deux emplacements publicitaires —
   n'était peint nulle part. Mesuré le 2026-09-11 : `.pj-promo` à 0×0 de 360px
   à 1200px, sur une plateforme dont ~90% de l'audience arrive sur un
   téléphone.

   Elle descend désormais sous la colonne centrale, pleine largeur, au lieu
   d'être masquée. C'est une surcharge et non une correction du thème :
   `user-template/` est régénéré par `tools/vendor-*.mjs` et ne se modifie
   jamais à la main (VENDORING.md).

   Trois règles, et chacune répare une chose que la grille ferait de travers :

   - `grid-column` est ce qui décide **sous quoi** elle tombe. Entre 861 et
     1200px la grille est `menu | central` ; laissée à son placement
     automatique, la colonne se glisserait en deuxième rangée **première
     colonne**, c'est-à-dire sous le menu de gauche. `2` la met sous le
     contenu, où on l'attend. Sous 861px la grille n'a plus qu'une colonne —
     et un `grid-column: 2` y créerait une colonne implicite — d'où le retour
     à `1`.
   - `position: static` : le rail porte `.pj-sticky`. Collant en tête d'une
     colonne qu'on regarde, c'est utile ; collant au pied d'une page qu'on
     fait défiler, c'est un bloc qui ne part jamais.
   - `display: flex` reprend simplement ce que `.pj-rail` déclare au large,
     que le `display: none` du thème écrase.

   Mesuré après coup, sur `/cvtheque` : 360px → rail à y=1058, 297 de large,
   aligné sur le contenu ; 768px → y=979, 705 ; 1000px → x=312 comme le
   contenu, et non x=24 comme le menu. Aucun débordement horizontal.

   **La publicité, elle, ne suit pas.** `AdSlotComponent.railIsVisible()`
   garde son `(min-width: 1201px)` et continue de ne rien demander au moteur
   sous cette largeur. Le moteur compte l'impression au moment où il choisit,
   donc l'élargir ferait passer une campagne en cours de ~10% à ~100% des
   vues — son budget et son plafond avec. C'est une décision commerciale, pas
   une conséquence d'une règle de mise en page : sous 1201px la colonne porte
   le menu contextuel et la promo maison, et l'hôte publicitaire reste
   `hidden`. Le jour où cette décision est prise, c'est `railIsVisible()` qu'on
   change, et `ads_slot_where_rail` / `ads_slot_where_side` avec, qui
   promettent aujourd'hui « Ordinateur uniquement ». */
@media (max-width: 1200px) {
    .pj-app .pj-rail {
        display: flex;
        grid-column: 2;
    }

    .pj-app .pj-rail.pj-sticky {
        position: static;
    }
}

@media (max-width: 860px) {
    .pj-app .pj-rail {
        grid-column: 1;
    }
}

/* Empilée, la colonne n'a plus 300px mais toute la largeur du contenu — 705px
   à 768px de fenêtre, 849px à 1200. Sa pile verticale d'origine y étire chaque
   carte sur toute la ligne : le menu contextuel devient une liste de tuiles de
   705px pour un libellé de deux mots, et l'encart promo un bandeau.

   Elle passe donc en grille auto-ajustée pendant qu'elle est empilée, et
   seulement là : à 360px la piste minimale ne tient qu'une fois et le résultat
   est exactement la pile d'avant ; à partir de ~600px les cartes se rangent à
   deux. `auto-fit` plutôt qu'un nombre de colonnes évite d'écrire un point de
   rupture de plus — c'est le même geste que `.dash-tiles`.

   `align-items: start` : sans lui la grille étire les cartes d'une rangée à la
   hauteur de la plus haute, et le menu contextuel prendrait la hauteur de
   l'encart promo avec du vide dessous. */
@media (max-width: 1200px) {
    .pj-app .pj-rail {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
        align-items: start;
    }
}

/* ── Tap targets, the rest of them (E10-01) ────────────────────────────
   E7-13 raised the admin console to 44px and `.pj-app .btn` covers buttons,
   but an audit of 10 screens at 390px still found 53 interactive elements
   under the floor once WCAG 2.5.5's exemption (a link inside a block of text)
   is honoured. They fall into six families, so this is six rules rather than
   fifty-three patches.

   Scoped to `.pj-app` — the guest and connected shells, which is where the
   phone audience is — exactly like the `.btn` rule above. `.layout-admin` is a
   separate stack and had its own pass in E7-13. */

/* Form fields sat at 40px: the single largest family, and the one a user has
   to hit repeatedly rather than once. */
.pj-app .form-control,
.pj-app select.form-control,
.pj-app textarea.form-control {
    min-height: 44px;
}

/* The rich-text editor's own `.rte__editor` (prosyjob-components.css) also
   carries `.form-control` for its border/padding/focus styling, so the
   44px rule above — same specificity, later file — was quietly winning
   over its 70px and collapsing a six-button toolbar (bold/italic/underline/
   lists/link/video) onto a field that read as one text line (2026-09-05,
   measured at 44px on a live page before this fix). `.rte .rte__editor`
   matches the same two classes the element already carries, so it ties on
   specificity and wins on being the later rule — it exists to win, not to
   set its own number, which is why it echoes 70px rather than choosing it. */
.rte .rte__editor {
    min-height: 70px;
}

/* Round icon buttons in the top bar — the theme toggle is the visible one. */
.pj-app .pj-iconbtn {
    min-width: 44px;
    min-height: 44px;
}

/* The brand mark is the "go home" link on every screen, at 30px. */
.pj-app .pj-topbar__brand {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
}

/* Footer links measured 16px. They are navigation — a `<nav>` since E14-04 —
   not prose in a paragraph, so 2.5.5 does not exempt them. */
.pj-app .pj-footer a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
}

/* The account menu, 28px per item since E8-01. */
.pj-app .dropdown-item {
    display: flex;
    align-items: center;
    min-height: 44px;
}

/* A checkbox is the one control that must NOT simply grow to 44px — a 44px
   box looks broken. The box grows a little and its row carries the rest of
   the target; the label is what the finger actually lands on. Pseudo-elements
   are not an option here: `input` is a replaced element and does not render
   ::before/::after reliably. */
.pj-app .form-check-input {
    width: 22px;
    height: 22px;
}

.pj-app .form-check,
.pj-app .custom_check {
    /* display: inline-block; */
    align-items: center;
    min-height: 44px;
}

/* The search page's filter accordion toggle measured 19px — it is the control
   that opens every filter on the job and training searches, on the screen size
   where filters matter most. */
.pj-app .filter-title > a {
    display: flex;
    align-items: center;
    min-height: 44px;
}

/* The minted Miles QR (E16-04). Capped because a code the width of a desktop
   card is harder to photograph, not easier, and it is scanned from a phone.

   The cap needs BOTH declarations below, and the first one alone never worked:
   it was written for a theme rule assumed to be `<container> img` at 0,1,1 and
   the real one is `.pj-app .pj-main img, .pj-app .pj-maincol img` at **0,2,1**,
   which beats a bare `.cm-hold__qr` (0,1,0) whatever the sheet order — order
   only settles a tie. So the QR rendered at the full card width, 637px on this
   viewport, for as long as the feature has existed, with a comment above it
   explaining why it could not. Measure the element; a rule that is present and
   matching can still be losing.

   The second selector mirrors the offender's shape to clear it at 0,3,0. The
   bare class stays for anywhere the QR is drawn outside the main column. */
.cm-hold__qr {
  display: block;
  width: 100%;
  max-width: 220px;
  height: auto;
  margin: 4px 0;
}

.pj-app .pj-main .cm-hold__qr,
.pj-app .pj-maincol .cm-hold__qr {
  max-width: 220px;
}

/* =====================================================================
   Confirmation par mot de passe (2026-08-08)
   =====================================================================
   The dialog in front of the ten actions that hand power to somebody else.
   It lives here, unscoped, and not in the component sheet, because it is
   raised from three shells: the admin console (`.layout-admin`, where
   `prosyjob.css` and every `--pj-*` token it declares are switched off), the
   connected user shell and — through a deep link — the guest one. A rule that
   must hold regardless of the vendor stack belongs in the last sheet loaded.
   Only tokens mirrored at `:root` in this same file are used below. */
/* Le fond et le panneau sont la chrome d'une modale posée à la main, pas une
   propriété du dialogue de mot de passe : la coquille utilisateur n'a pas
   Bootstrap, donc chaque modale y est un élément sur `document.body`. E44-07 en
   ajoute une seconde — le QR d'un blocage — et la nomme ici plutôt que de
   recopier la règle ailleurs. */
.cpd-backdrop,
.qrd-backdrop,
.mhs-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1080; /* above the Bootstrap modals the member screens already open */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(9, 14, 20, 0.55);
}

.cpd-panel,
.qrd-panel,
.mhs-panel {
  width: 100%;
  max-width: 420px;
  max-height: 90vh;
  overflow-y: auto;
  padding: 22px;
  border-radius: 10px;
  background: var(--pj-card);
  color: var(--pj-ink);
  box-shadow: 0 18px 48px rgba(9, 14, 20, 0.32);
}

.cpd-title {
  margin: 0 0 8px;
  font-size: 1.15rem;
}

/* The subject of the action — a name, an email. Wrapped rather than truncated:
   confirming a transfer to the wrong person is the mistake this dialog exists
   to prevent, so the name must be readable in full. */
.cpd-detail {
  margin: 0 0 10px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.cpd-why {
  margin: 0 0 16px;
  font-size: 0.88rem;
  color: var(--pj-muted);
}

.cpd-panel .form-control {
  width: 100%;
  min-height: 44px; /* tap target floor, measured — see CLAUDE.md §9 */
}

/* `--pj-danger` exists since the button harmonisation (§5) and this now uses
   it, an error being a better thing to paint red than brand-orange.

   The comment that stood here said inventing the token would have had to be
   mirrored in prosyjob.css or `check-assets.mjs` would fail. That was wrong:
   the check walks the tokens found in prosyjob.css and asserts each is
   mirrored here — never the other way — and it reads only the first `:root {`
   of each file. `--pj-card-2` had been declared here alone since E7 and the
   build passed the whole time. Read the gate before assuming what it forbids. */
.cpd-error {
  margin: 8px 0 0;
  font-size: 0.85rem;
  color: var(--pj-danger);
}

.cpd-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 18px;
}

/* Every bare `<button>` in the connected shell is width:100% (see CLAUDE.md
   §9), so a row of two needs both `width: auto` and something to divide the
   space — otherwise they stack at any width. */
.cpd-actions .btn {
  flex: 1 1 140px;
  width: auto;
  min-height: 44px;
}

/* "Voir mes demandes en attente" (Task 12) — a request nobody made becomes
   visible on `/validation/en-attente`, and this is the door to it from right
   here, where the person is already looking at one. */
.cpd-hint {
  margin: 14px 0 0;
  text-align: center;
  font-size: 0.85rem;
}

/* CVthèque profile modal (`.cvtm-`, 2026-09) — "view profile" now opens here
   instead of navigating, so closing it (✕, backdrop, Escape) leaves the
   search/unlocks/favorites list exactly as it was. Same z-index as `.cpd-*`
   and the same reason: above any Bootstrap modal the connected shell still
   opens elsewhere. Full-bleed under 576px (a phone gets the whole screen for
   a CV, not a postage stamp), a centred card above it. */
.cvtm-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1080;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(9, 14, 20, 0.55);
}

.cvtm-panel {
  position: relative;
  width: 100%;
  max-width: 640px;
  max-height: 90vh;
  overflow-y: auto;
  padding: 24px;
  border-radius: 10px;
  background: var(--pj-card);
  color: var(--pj-ink);
  box-shadow: 0 18px 48px rgba(9, 14, 20, 0.32);
}

@media (max-width: 576px) {
  .cvtm-backdrop {
    padding: 0;
  }

  .cvtm-panel {
    max-width: none;
    max-height: 100vh;
    height: 100vh;
    border-radius: 0;
  }
}

/* 44px square, same tap-target floor as every other icon-only control here. */
.cvtm-close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 44px;
  height: 44px;
  min-height: 44px;
  border: 0;
  background: transparent;
  color: var(--pj-muted);
  font-size: 1.6rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.cvtm-body {
  padding-right: 40px; /* clears the close button on the title row */
}

.cvtm-back {
  width: auto;
  margin-bottom: 16px;
}

.cvtm-cv-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
}

.cvtm-cv-bar .btn {
  width: auto;
  margin-bottom: 0;
}

/* =====================================================================
   Enregistrement : la barre qui ne quitte pas l'écran (`.pj-savebar`,
   `.pj-draft`, 2026-09-16)
   =====================================================================
   Retour PO : sur téléphone, « Enregistrer » se trouve au fond d'un long
   formulaire, donc hors écran — les gens saisissent, changent d'écran et
   perdent leur texte. Posé ici et non dans la feuille des composants parce
   que {@see FormAutosave} l'applique sur cinq écrans de quatre modules :
   fiche entreprise, espace organisme, éditeur d'offre, éditeur de formation,
   profil candidat.

   `fixed` et non `sticky`, et ce n'est pas un choix de style : `#app` porte
   `overflow-y: auto` alors que c'est `body` qui défile réellement. Une boîte
   `sticky` se positionne par rapport au premier ancêtre qui défile — ici
   `#app`, qui ne défile jamais puisque sa hauteur suit son contenu (mesuré :
   3432px pour 736px de fenêtre). La barre restait à 1777px du haut,
   parfaitement immobile et parfaitement invisible : le défaut qu'elle devait
   corriger, en croyant l'avoir corrigé. `fixed` se moque de cette chaîne
   d'ancêtres. `.pj-has-savebar` rend au formulaire la hauteur recouverte,
   sans quoi son dernier champ resterait sous la barre. z-index sous 1040 :
   le tiroir de navigation passe devant, comme il doit. */
@media (max-width: 576px) {
  .pj-savebar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 30;
    margin: 0;
    padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
    background: var(--pj-card);
    border-top: 1px solid var(--pj-line);
    box-shadow: 0 -10px 24px -18px rgba(9, 14, 20, 0.7);
  }

  .pj-has-savebar {
    padding-bottom: 104px;
  }
}

.pj-savebar__dirty {
  flex: 1 1 100%;
  align-self: center;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--pj-orange);
}

/* Le brouillon retrouvé, en tête du formulaire : proposé, jamais appliqué
   d'office — voir `FormAutosave._offerDraft()`. */
.pj-draft {
  margin-bottom: 16px;
  padding: 14px;
  border: 1px solid var(--pj-orange);
  border-radius: 8px;
  background: var(--pj-orange-soft);
}

.pj-draft__text {
  margin: 0 0 10px;
  font-size: 0.9rem;
  color: var(--pj-ink);
}

.pj-draft__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.pj-draft__actions .btn {
  width: auto;
  min-height: 44px;
}

/* =====================================================================
   Logo/banner designer (`.lgd-`, 2026-09-14)
   =====================================================================
   Same hand-rolled backdrop chrome as `.qrd-*`/`.cvtm-*` (no Bootstrap on
   the connected shell), on `.cvtm-*`'s standalone family rather than the
   shared `.cpd-*`/`.qrd-*` 420px cap — this one needs real width for a
   canvas preview plus a palette row. Mobile-first: full-bleed under 576px,
   a centred card above it. */
.lgd-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1080;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(9, 14, 20, 0.55);
}

.lgd-panel {
  position: relative;
  width: 100%;
  max-width: 580px;
  max-height: 90vh;
  overflow-y: auto;
  padding: 24px;
  border-radius: 10px;
  background: var(--pj-card);
  color: var(--pj-ink);
  box-shadow: 0 18px 48px rgba(9, 14, 20, 0.32);
}

@media (max-width: 576px) {
  .lgd-backdrop {
    padding: 0;
  }

  .lgd-panel {
    max-width: none;
    max-height: 100vh;
    height: 100vh;
    /* dvh follows the address bar as it hides and shows; 100vh above is the
       fallback for a browser without it, and must stay first. */
    max-height: 100dvh;
    height: 100dvh;
    border-radius: 0;
    padding-bottom: 0;
  }
}

.lgd-close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 44px;
  height: 44px;
  min-height: 44px;
  border: 0;
  background: transparent;
  color: var(--pj-muted);
  font-size: 1.6rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.lgd-title {
  margin: 0 0 4px;
  padding-right: 40px; /* clears the close button */
  font-size: 1.15rem;
}

.lgd-sub {
  margin: 0 0 16px;
  color: var(--pj-muted);
  font-size: 0.9rem;
}

/* The canvas itself is drawn at a fixed pixel size (600x600 / 1200x400) and
   scaled down visually — CSS width alone, never the canvas attributes,
   which would blur or distort the draw. */
.lgd-canvas-wrap {
  width: 100%;
  border-radius: 8px;
  overflow: hidden;
  background: var(--pj-card-2);
}

.lgd-canvas-wrap--square {
  max-width: 220px;
  margin: 0 auto;
  aspect-ratio: 1 / 1;
}

.lgd-canvas-wrap--wide {
  aspect-ratio: 3 / 1;
}

/* Pro mode keeps the preview — it is the colour and style the paid prompt
   is built from — but gives the height back to the step being read. */
.lgd-canvas-wrap--mini {
  max-width: 120px;
  margin: 0 auto;
}

.lgd-canvas {
  width: 100%;
  height: 100%;
  display: block;
}

/* One row per choice (colour, shape/layout, font, symbol, pattern) — same
   flex+gap+centered layout for all five, `.lgd-option-group`/`-label`
   giving each its own small uppercase caption above. */
.lgd-option-group {
  margin-bottom: 14px;
}

.lgd-option-label {
  margin: 0 0 8px;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--pj-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.lgd-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
}

.lgd-color-input {
  width: 40px;
  height: 40px;
  min-height: 40px;
  padding: 0;
  border: 2px solid var(--pj-card-2);
  border-radius: 8px;
  background: none;
  cursor: pointer;
}

.lgd-color-label {
  align-self: center;
  font-size: 0.85rem;
  color: var(--pj-muted);
}

/* Shape/layout/font: a labelled pill (the font row sets font-family inline
   per button, so each pill previews its own typeface). */
.lgd-choice {
  min-height: 40px;
  padding: 0 14px;
  border-radius: 20px;
  border: 2px solid var(--pj-card-2);
  background: var(--pj-card-2);
  color: var(--pj-ink);
  font-size: 0.85rem;
  width: auto;
}

.lgd-choice.is-active {
  border-color: var(--pj-orange);
  background: var(--pj-card);
}

/* Symbol/pattern: a small canvas thumbnail drawn by the same functions the
   main preview uses, so the choice is never a guess from a label alone. */
.lgd-choice-icon {
  width: 44px;
  height: 44px;
  min-height: 44px;
  padding: 4px;
  border-radius: 10px;
  border: 2px solid var(--pj-card-2);
  background: var(--pj-card-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.lgd-choice-icon.is-active {
  border-color: var(--pj-orange);
}

.lgd-choice-icon canvas {
  width: 100%;
  height: 100%;
  display: block;
}

.lgd-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 12px;
}

.lgd-actions .btn {
  flex: 1 1 180px;
  width: auto;
  min-height: 44px;
}

/* The panel is its own scroll container, so the actions of the current
   mode/step stay on screen instead of waiting at the end of the scroll —
   the whole reason a click on "Rendre professionnel ✨" used to look like
   nothing happening on a phone. Sticky only where the panel is the full
   viewport; on a desktop card the buttons are in view anyway. */
@media (max-width: 576px) {
  .lgd-actions--sticky {
    position: sticky;
    bottom: 0;
    z-index: 1;
    margin-top: 16px;
    padding: 10px 0 calc(10px + env(safe-area-inset-bottom, 0px));
    background: var(--pj-card);
    box-shadow: 0 -8px 16px -12px rgba(9, 14, 20, 0.5);
  }
}

/* The two exclusive faces (`template`/`pro`) and the three steps inside the
   second one. Focus lands here when one replaces the other — programmatic,
   so no ring. */
.lgd-mode:focus,
.lgd-mode:focus-visible {
  outline: none;
}

/* Back out of the paid face, or back a step inside it — a bare <button> is
   full width by default in this shell (see `button, .btn` in
   prosyjob-components.css), which is not what a back link is. */
.lgd-back {
  width: auto;
  min-height: 44px;
  margin: 0 0 4px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--pj-muted);
  font-size: 0.9rem;
  text-align: left;
}

.lgd-step-indicator {
  margin: 0 0 12px;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--pj-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.lgd-tier-row {
  margin-bottom: 8px;
}

.lgd-tier-choice {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.lgd-tier-hint {
  margin: 0 0 10px;
  font-size: 0.8rem;
  color: var(--pj-muted);
}

.lgd-pro-color-hint {
  margin: 0 0 10px;
  font-size: 0.8rem;
  color: var(--pj-muted);
}

/* The row itself is a `.pj-consent` (§2bis); only its spacing is local. */
.lgd-consent {
  margin-bottom: 12px;
  font-size: 0.85rem;
}

.lgd-activity-input {
  resize: vertical;
  min-height: 44px;
}

.lgd-prompt-text {
  resize: vertical;
  min-height: 90px;
}

.lgd-prompt-charcount {
  margin: 6px 0 12px;
  font-size: 0.78rem;
  color: var(--pj-muted);
  text-align: right;
}

.lgd-status {
  margin: 10px 0 0;
  font-size: 0.85rem;
  color: var(--pj-muted);
}

.lgd-pro-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(90px, 1fr));
  gap: 10px;
}

.lgd-pro-thumb {
  position: relative;
  display: block;
  border-radius: 8px;
  overflow: hidden;
  border: 3px solid transparent;
  cursor: pointer;
}

.lgd-pro-thumb.is-active {
  border-color: var(--pj-orange);
}

.lgd-pro-thumb input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.lgd-pro-thumb img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  display: block;
}

.lgd-pro-cost {
  margin: 10px 0;
  font-weight: 600;
}

/* =====================================================================
   Professionnaliser l'ensemble (`.gpd-`, 2026-09-15)
   =====================================================================
   Same hand-rolled backdrop chrome as `.lgd-*`, without the canvas: this
   dialog only ever shows a cost/consent step then a before/after field
   list, so `.lgd-panel`'s narrower cousin `.cpd-*`/`.qrd-*` 480px cap fits
   better than `.lgd-*`'s canvas-sized one. */
.gpd-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1080;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(9, 14, 20, 0.55);
}

.gpd-panel {
  position: relative;
  width: 100%;
  max-width: 480px;
  max-height: 90vh;
  overflow-y: auto;
  padding: 24px;
  border-radius: 10px;
  background: var(--pj-card);
  color: var(--pj-ink);
  box-shadow: 0 18px 48px rgba(9, 14, 20, 0.32);
}

@media (max-width: 576px) {
  .gpd-backdrop {
    padding: 0;
  }

  .gpd-panel {
    max-width: none;
    max-height: 100vh;
    height: 100vh;
    border-radius: 0;
  }
}

.gpd-close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 44px;
  height: 44px;
  min-height: 44px;
  border: 0;
  background: transparent;
  color: var(--pj-muted);
  font-size: 1.6rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.gpd-title {
  margin: 0 0 4px;
  padding-right: 40px;
  font-size: 1.15rem;
}

.gpd-intro {
  margin: 0 0 12px;
  color: var(--pj-muted);
  font-size: 0.9rem;
}

.gpd-cost {
  margin: 0 0 12px;
  font-weight: 600;
}

.gpd-consent {
  margin-bottom: 12px;
  font-size: 0.85rem;
}

.gpd-status {
  margin: 10px 0 0;
  font-size: 0.85rem;
  color: var(--pj-muted);
}

.gpd-spent {
  margin: 0 0 14px;
  font-weight: 600;
}

.gpd-fields {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-bottom: 16px;
}

.gpd-field {
  padding: 10px 12px;
  border-radius: 8px;
  background: var(--pj-card-2);
}

.gpd-field-check {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
  font-weight: 600;
}

.gpd-field-before,
.gpd-field-after {
  margin: 0;
  font-size: 0.85rem;
  color: var(--pj-muted);
}

.gpd-field-after {
  color: var(--pj-ink);
}

.gpd-field-before span,
.gpd-field-after span {
  font-weight: 600;
  margin-right: 4px;
}

.gpd-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 12px;
}

.gpd-actions .btn {
  flex: 1 1 180px;
  width: auto;
  min-height: 44px;
}

/* =====================================================================
   Console d'administration — accueil (`.adb-`)
   =====================================================================
   Ces regles vivent ici et non dans la feuille de composants parce que
   `.layout-admin` eteint `prosyjob.css` : sur cette pile, seule cette feuille
   est chargee, et c'est elle qui recopie l'echelle `--pj-*` (voir la note du
   §9 du contrat). Une regle de la console ecrite ailleurs n'existerait pas.

   Mobile d'abord : ce qui suit est le dessin a 360 px, les media queries
   enrichissent. */
.adb-stack {
  display: grid;
  gap: 20px;
  margin-top: 4px;
}

.adb-card {
  margin: 0;
}

.adb-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 14px;
}

.adb-head__title {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 700;
}

.adb-head__total {
  color: var(--pj-muted);
  font-weight: 600;
}

.adb-period {
  width: auto;
  min-height: 44px;
}

.adb-chip {
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--pj-card-2);
  color: var(--pj-muted);
  font-size: 0.8rem;
  font-weight: 600;
}

/* ── La file d'attente ─────────────────────────────────────────────────── */

.adb-item {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr);
  align-items: center;
  gap: 4px 10px;
  padding: 12px 0;
  border-bottom: 1px solid var(--pj-line);
}

.adb-item:last-of-type {
  border-bottom: 0;
}

.adb-item__icon {
  color: var(--pj-muted);
  text-align: center;
}

.adb-item__title {
  font-weight: 600;
  overflow-wrap: anywhere;
}

.adb-item__meta {
  color: var(--pj-muted);
  font-size: 0.85rem;
}

.adb-item__text,
.adb-badge,
.adb-open {
  grid-column: 2;
}

.adb-badge {
  justify-self: start;
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--pj-card-2);
  color: var(--pj-muted);
  font-size: 0.78rem;
  font-weight: 600;
}

/* L'urgence porte un mot ET une couleur : la couleur seule ne se lit pas en
   daltonisme ni en impression. */
.adb-badge--urgent {
  background: var(--pj-orange-soft);
  color: var(--pj-orange-text);
}

.adb-open {
  display: inline-flex;
  align-items: center;
  justify-self: start;
  min-height: 44px;
  color: var(--pj-orange-text);
  font-weight: 600;
}

.adb-rest {
  margin-top: 12px;
}

/* ── Supervision ───────────────────────────────────────────────────────────
   Quatre tuiles et, au besoin, la liste des comptes dispersés. Le niveau se
   lit par la COULEUR ET par l'épaisseur du liseré gauche : un daltonien
   distingue le second. Le vert n'existe pas ici — « tout va bien » est l'état
   normal et n'a pas à se signaler ; seuls l'attention et le critique parlent.
--------------------------------------------------------------------------- */

.adb-sup {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px;
}

.adb-sup__tile {
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-border);
  border-radius: 8px;
  padding: 14px 16px;
  background: var(--color-surface);
}

/* Les deux surfaces viennent des jetons de signal, pas d'un rgba() fabrique :
   un aplat littéral ne suit pas le thème sombre, et la tuile se retrouve
   claire dans une carte devenue noire. */
.adb-sup__tile--attention {
  border-left-color: var(--pj-orange);
  border-left-width: 5px;
  background: var(--pj-orange-soft);
}

.adb-sup__tile--critique {
  border-left-color: var(--pj-danger);
  border-left-width: 5px;
  background: var(--surface-chip);
}

.adb-sup__value {
  margin: 0 0 2px;
  font-size: var(--pj-fs-title);
  line-height: 1.1;
}

.adb-sup__label {
  margin: 0;
  font-size: var(--pj-fs-sm);
  font-weight: 600;
}

.adb-sup__note {
  margin: 4px 0 0;
  font-size: var(--pj-fs-xs);
  color: var(--pj-muted);
}

/* La liste prend toute la largeur de la grille : elle n'est pas une tuile de
   plus, elle est le détail de la première. */
.adb-sup__spread {
  grid-column: 1 / -1;
  border-top: 1px solid var(--color-border);
  padding-top: 12px;
}

.adb-sup__list {
  margin: 6px 0 0;
  padding-left: 18px;
  font-size: var(--pj-fs-xs);
  color: var(--pj-muted);
}

/* ── Les deux courbes ──────────────────────────────────────────────────── */

/* La fenêtre d'un jour n'a qu'un point : on écrit le total au lieu de tracer
   une ligne qui n'existe pas. */
.adb-chart__single {
  margin: 0;
  font-size: var(--pj-fs-display);
  line-height: 1.1;
  font-weight: 700;
}

.adb-charts,
.adb-lists {
  display: grid;
  gap: 20px;
}

/* Une seule serie par cadre, donc une seule couleur et aucune legende a lire :
   le titre nomme la serie. Trait de 2 px, aire tres pale dessous. */
.adb-chart {
  display: block;
  width: 100%;
  height: 160px;
  overflow: visible;
}

.adb-chart__line {
  fill: none;
  stroke: var(--pj-orange-fill);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
  stroke-linejoin: round;
}

.adb-chart__area {
  fill: var(--pj-orange-fill);
  opacity: 0.12;
  stroke: none;
}

/* Les reperes sont du HTML sous le cadre : dans le SVG ils seraient etires par
   `preserveAspectRatio="none"` avec le trace. */
.adb-chart__axis {
  display: flex;
  justify-content: space-between;
  margin-top: 8px;
  color: var(--pj-muted);
  font-size: 0.78rem;
}

/* ── Les quatre listes ─────────────────────────────────────────────────── */

/* Un tableau large defile dans sa propre boite plutot que d'elargir la page. */
.adb-table {
  overflow-x: auto;
}

.adb-table table {
  width: 100%;
  border-collapse: collapse;
}

.adb-table th,
.adb-table td {
  padding: 9px 10px 9px 0;
  border-bottom: 1px solid var(--pj-line);
  text-align: left;
  white-space: nowrap;
}

.adb-table th {
  color: var(--pj-muted);
  font-size: 0.78rem;
  font-weight: 600;
}

.adb-table tbody tr:last-child td {
  border-bottom: 0;
}

.adb-table__date {
  color: var(--pj-muted);
  font-variant-numeric: tabular-nums;
}

@media (min-width: 768px) {
  .adb-item {
    grid-template-columns: 28px minmax(0, 1fr) auto auto;
    gap: 4px 14px;
  }

  .adb-item__text,
  .adb-badge,
  .adb-open {
    grid-column: auto;
  }
}

@media (min-width: 1200px) {
  .adb-charts,
  .adb-lists {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* =====================================================================
   Console des assignations (`.assign-`)
   ===================================================================== */

/* Trois boutons par ligne — historique, modifier, revoquer. Sans cela ils
   s'empilent en pleine largeur des que la colonne se resserre. */
.assign-actions {
  white-space: nowrap;
}

.assign-actions .btn {
  width: auto;
  margin-left: 4px;
}

/* Le fil d'une assignation : une entree par acte, la plus recente en haut. */
.assign-history {
  margin: 0;
  padding: 0;
  list-style: none;
}

.assign-history li {
  padding: 10px 0;
  border-bottom: 1px solid var(--pj-line);
}

.assign-history li:last-child {
  border-bottom: 0;
}

/* =====================================================================
   Rôles d'administration (`.rl-`, E59-07)
   =====================================================================
   Dans cette feuille pour la même raison que `.assign-` et `.dup-` :
   `.layout-admin` éteint `prosyjob.css`.

   Une seule chose y est appuyée : le sélecteur à trois positions de chaque
   écran. « Voir » prend le bleu d'information, « Gérer » l'orange de la
   marque — la couleur des gestes qui écrivent — et « Rien » reste neutre. Le
   reste suit la console : cartes, filets, typographie du gabarit. */

.rl-intro {
  max-width: 72ch;
}

.btn.rl-compose {
  width: auto;
  min-height: 44px;
}

/* La liste : une carte par rôle, empilée sur téléphone, actions dessous ;
   à droite à partir de 768px. */
.rl-list {
  display: grid;
  gap: 12px;
}

.rl-role {
  display: grid;
  gap: 10px;
  padding: 14px 16px;
  border: 1px solid var(--pj-line);
  border-radius: var(--pj-radius-sm);
  background: var(--pj-card);
}

.rl-role--archived {
  background: var(--pj-card-2);
}

.rl-role__name {
  margin: 0;
  font-size: var(--pj-fs-md);
  color: var(--pj-ink);
}

.rl-role__code,
.rl-role__desc,
.rl-role__facts {
  margin: 2px 0 0;
  font-size: var(--pj-fs-sm);
  color: var(--pj-muted);
}

.rl-role__desc {
  color: var(--pj-ink);
  max-width: 72ch;
}

.rl-role__facts {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
}

.rl-role__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.rl-role__actions .btn {
  width: auto;
  min-height: 44px;
}

.rl-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 8px;
  border-radius: var(--pj-radius-pill);
  background: var(--pj-neutral-soft);
  color: var(--pj-neutral-ink);
  font-size: var(--pj-fs-xs);
  font-weight: 600;
  vertical-align: middle;
}

.rl-tag--locked {
  background: var(--pj-warning-soft);
  color: var(--pj-warning-ink);
}

.rl-archived {
  margin-top: 8px;
}

.rl-archived > summary {
  min-height: 44px;
  padding: 10px 0;
  cursor: pointer;
  color: var(--pj-muted);
}

.rl-archived > .rl-role {
  margin-top: 12px;
}

@media (min-width: 768px) {
  .rl-role {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: start;
  }

  .rl-role__actions {
    justify-content: flex-end;
  }
}

/* L'éditeur. */
.btn.rl-back {
  width: auto;
  min-height: 44px;
  padding-left: 0;
}

.rl-editor-title {
  margin: 0 0 6px;
  font-size: var(--pj-fs-card);
}

.rl-editor-note {
  max-width: 72ch;
}

.rl-fields {
  display: grid;
  gap: 0 16px;
}

@media (min-width: 768px) {
  .rl-fields {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .rl-description {
    grid-column: 1 / -1;
  }
}

.rl-grid-head h4 {
  margin: 8px 0 2px;
  font-size: var(--pj-fs-lg);
}

.rl-grid {
  display: grid;
  gap: 10px;
  /* La barre collante en bas ne doit pas masquer la dernière ligne. */
  padding-bottom: 12px;
}

.rl-group {
  border: 1px solid var(--pj-line);
  border-radius: var(--pj-radius-sm);
  background: var(--pj-card);
}

.rl-group__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 48px;
  padding: 10px 16px;
  cursor: pointer;
  list-style: none;
}

.rl-group__head::-webkit-details-marker {
  display: none;
}

.rl-group__name {
  font-weight: 600;
  color: var(--pj-ink);
}

.rl-group__count {
  font-size: var(--pj-fs-sm);
  color: var(--pj-muted);
  font-variant-numeric: tabular-nums;
}

.rl-group__bulk {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 0 16px 10px;
  font-size: var(--pj-fs-sm);
  color: var(--pj-muted);
}

.rl-group__bulk .rl-chip {
  width: auto;
  min-height: 36px;
  padding: 4px 12px;
  border: 1px solid var(--pj-line);
  border-radius: var(--pj-radius-pill);
  background: transparent;
  color: var(--pj-ink);
  font-size: var(--pj-fs-sm);
}

.rl-group__bulk .rl-chip:hover {
  border-color: var(--pj-muted);
}

.rl-rows {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Une ligne par écran. Sur téléphone le sélecteur passe sous le nom ; il le
   rejoint à droite dès qu'il y a la place. Le liseré gauche marque une ligne
   dont le niveau diffère de l'enregistré. */
.rl-row {
  display: grid;
  gap: 8px;
  padding: 10px 16px 10px 13px;
  border-top: 1px solid var(--pj-line);
  border-left: 3px solid transparent;
}

.rl-row--changed {
  border-left-color: var(--pj-orange);
  background: var(--pj-orange-soft);
}

.rl-row__name {
  align-self: center;
  color: var(--pj-ink);
}

@media (min-width: 576px) {
  .rl-row {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
  }
}

/* Le sélecteur à trois positions. Chaque règle est qualifiée par son parent :
   `.layout-admin button` (0,1,1) remet la hauteur minimale de tout bouton de
   la console à `auto`, et une classe seule (0,1,0) perdait — 19px mesurés
   au lieu de 44, quel que soit l'ordre des feuilles. */
.rl-seg {
  display: inline-flex;
  justify-self: start;
  border: 1px solid var(--pj-line);
  border-radius: var(--pj-radius-pill);
  overflow: hidden;
  background: var(--pj-card-2);
}

.rl-seg .rl-seg__opt {
  width: auto;
  min-width: 72px;
  min-height: 44px;
  padding: 0 14px;
  border: 0;
  background: transparent;
  color: var(--pj-muted);
  font-size: var(--pj-fs-sm);
  font-weight: 600;
}

.rl-seg .rl-seg__opt + .rl-seg__opt {
  border-left: 1px solid var(--pj-line);
}

.rl-seg .rl-seg__opt:focus-visible {
  outline: 2px solid var(--pj-blue);
  outline-offset: -2px;
}

.rl-seg .rl-seg__opt:disabled {
  cursor: default;
}

.rl-seg .rl-seg__opt--none.is-on {
  background: var(--pj-neutral-soft);
  color: var(--pj-neutral-ink);
}

.rl-seg .rl-seg__opt--view.is-on {
  background: var(--pj-info-soft);
  color: var(--pj-info-ink);
}

.rl-seg .rl-seg__opt--manage.is-on {
  background: var(--pj-orange-fill);
  /* Le libellé des boutons orange du §5 (`--bs-btn-color`) : le fond est le
     même orange dans les deux thèmes, le blanc aussi. */
  color: #fff;
}

.rl-extra {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  margin: 0;
  color: var(--pj-ink);
  cursor: pointer;
}

.rl-extra input {
  width: 20px;
  height: 20px;
}

/* La barre d'enregistrement : collée en bas, le compte et les deux gestes. */
.rl-savebar {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  border: 1px solid var(--pj-line);
  border-radius: var(--pj-radius-sm);
  background: var(--pj-card);
  box-shadow: var(--pj-shadow-sm);
}

.rl-savebar__count {
  flex: 1 1 100%;
  margin: 0;
  font-weight: 600;
  color: var(--pj-ink);
}

.rl-savebar .btn {
  width: auto;
  min-height: 44px;
}

@media (min-width: 576px) {
  .rl-savebar__count {
    flex-basis: auto;
  }
}

/* L'historique : une version par entrée, la plus récente en haut. */
.rl-history {
  margin: 0;
  padding-left: 20px;
}

.rl-history li {
  padding: 10px 0;
  border-bottom: 1px solid var(--pj-line);
}

.rl-history li:last-child {
  border-bottom: 0;
}

.rl-history__head,
.rl-diff {
  margin: 0;
}

.rl-diff {
  margin-top: 4px;
  font-size: var(--pj-fs-sm);
}

.rl-diff--add {
  color: var(--pj-success);
}

.rl-diff--del {
  color: var(--pj-danger);
}

/* =====================================================================
   Revue des comptes en double (`.dup-`)
   =====================================================================
   Dans `prosyjob-app.css` pour la meme raison que `.alv-` et `.adb-` :
   `.layout-admin` eteint `prosyjob.css`, donc c'est la seule feuille chargee
   sur cette pile. */

/* La barre de selection. Collante en haut : sur l'onglet « Sans identifiant »
   une page porte vingt-cinq lignes, et cocher en bas pour remonter chercher le
   bouton est exactement le geste qu'on fait de travers. Elle n'existe que
   lorsqu'il y a une selection — l'attribut `hidden` la retire, la regle
   `[hidden]{display:none!important}` de cette feuille s'en chargeant. */
.dup-selection {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
  padding: 10px 14px;
  border: 1px solid var(--pj-line);
  border-radius: var(--pj-radius-sm);
  background: var(--pj-card);
}

.dup-selection__count {
  flex: 1 1 auto;
  margin: 0;
  font-weight: 600;
}

/* `.layout-admin` neutralise deja le `button { width: 100% }`, mais compter
   dessus est un pari sur une feuille du vendeur. */
.dup-selection .btn {
  width: auto;
  min-height: 38px;
}

/* La case vit dans la cellule du nom : une case a cocher seule en tete de
   ligne ne dit pas ce qu'elle coche, et une colonne de plus faisait defiler la
   boite. Le `padding-left` de la variante sans case aligne le nom sur ceux qui
   en portent une, pour que la colonne reste une colonne. */
.dup-name {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  cursor: pointer;
}

.dup-name input[type="checkbox"] {
  width: 18px;
  height: 18px;
  margin: 0;
  flex: 0 0 auto;
  cursor: pointer;
}

.dup-name--fixed {
  padding-left: 26px;
  cursor: default;
}

/* Neuf colonnes — nom, login d'origine, les deux identifiants, pays, statut,
   les deux dates, actions — ne tiennent pas dans la boite ; six n'y tenaient
   deja pas (1124px pour 1001). La derniere se collait donc
   au-dela du bord et « Rendre principal » arrivait coupe, ce qui se lit comme
   un defaut d'affichage plutot que comme un tableau a faire defiler. Elle reste
   au bord droit ; le reste glisse dessous. Meme correctif que `.alv-col-actions`
   sur la liste des comptes, et pour la meme raison : l'action est le point de
   l'ecran, elle ne se cherche pas. Reservee au grand format — sous 768px le
   `.table-responsive` du theme defile deja et coller n'aurait plus de sens. */
@media (min-width: 768px) {
  .dup-col-actions {
    position: sticky;
    right: 0;
    z-index: 1;
    background: var(--pj-card);
    box-shadow: -10px 0 10px -10px rgba(0, 0, 0, 0.28);
  }
}

/* =====================================================================
   Listes d'administration (`.alv-`)
   =====================================================================
   Le gabarit unique des quatre ecrans que le tableau de bord ouvre. Dans
   `prosyjob-app.css` pour la meme raison que `.adb-` : `.layout-admin` eteint
   `prosyjob.css`, donc c'est la seule feuille chargee sur cette pile. */
.alv-count {
  color: var(--pj-muted);
  font-weight: 600;
}

/* Un second nom sous le premier, dans une cellule de liste : le nom
   d'activation d'un partenaire sous celui que le public lit. En BLOC, parce
   qu'un nom qui se faufile en bout de ligne ne se lit plus, et discret parce
   qu'il n'est la que pour le jour ou on en a besoin. */
.alv-subname {
  display: block;
  font-style: normal;
  font-weight: 400;
  font-size: var(--pj-fs-xs);
  color: var(--pj-muted);
}

.alv-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
  margin-bottom: 16px;
}

.alv-field {
  display: grid;
  flex: 1 1 180px;
  gap: 4px;
  margin: 0;
}

.alv-field--search {
  flex: 2 1 240px;
}

.alv-field > span {
  color: var(--pj-muted);
  font-size: 0.8rem;
  font-weight: 600;
}

.alv-field .form-control {
  min-height: 44px;
}

/* `.layout-admin` neutralise le `button { width: 100% }`, mais on le declare
   quand meme : la regle est la, et compter dessus est un pari sur une feuille
   du vendeur. */
.alv-submit {
  width: auto;
  min-height: 44px;
}

.alv-badge {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--pj-card-2);
  color: var(--pj-muted);
  font-size: 0.78rem;
  font-weight: 600;
}

.alv-badge--ok {
  background: var(--pj-card-2);
  color: var(--color-success, #1e8e5a);
}

.alv-badge--warn {
  background: var(--pj-orange-soft);
  color: var(--pj-orange-text);
}

/* Deux canaux de verification cote a cote : la puce porte le canal, le signe
   porte l'etat. Elles se suivent sur une ligne et retombent l'une sous l'autre
   dans la carte mobile plutot que de deborder. */
.alv-badge--chan {
  margin-right: 6px;
  white-space: nowrap;
}

/* Les actions d'une ligne. `.layout-admin` neutralise deja le
   `button { width: 100% }`, mais une rangee de boutons le declare quand meme :
   compter sur une feuille du vendeur est un pari. */
.alv-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.alv-actions .btn {
  width: auto;
}

/* La vignette d'une publication (E33) : toujours visible dans la liste plutot
   qu'un clic a part, pour qu'un contenu problematique porte par l'image (et
   non le titre) ne puisse pas etre saute. Le clic n'agrandit — meme motif
   qu'une case tactile 44px minimum ici trop petite pour vraiment juger une
   image, jamais une deuxieme facon d'agir dessus. */
/* Specificite `.alv-table .alv-thumb` deliberee : `.layout-admin button` (une
   classe + un element) impose deja `width: auto`, et une simple `.alv-thumb`
   (une classe) perd face a elle quel que soit l'ordre des feuilles — la
   vignette prenait alors la largeur de l'image au lieu de 48px fixes. */
.alv-table .alv-thumb {
  display: block;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 0;
  border-radius: var(--pj-radius-sm);
  overflow: hidden;
  cursor: zoom-in;
  flex: 0 0 auto;
}

.alv-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.alv-thumb--empty {
  background: var(--pj-card-2);
}

/* Une colonne qui reste au bord droit de la boite qui defile. Une liste a dix
   colonnes tient dans 1740px pour 1000 visibles : sans cela, le bouton d'action
   est hors champ sur chaque ligne et il faut defiler pour chaque compte.
   Reservee au grand format : sous 768px la table devient des cartes, ou tout
   est deja visible et ou coller n'aurait plus de sens. La teinte est celle de
   la carte, sinon les colonnes glissent visiblement dessous. */
@media (min-width: 768px) {
  .alv-table .alv-col-actions {
    position: sticky;
    right: 0;
    z-index: 1;
    background: var(--pj-card);
    box-shadow: -10px 0 10px -10px rgba(0, 0, 0, 0.28);
  }
}

/* Une table large defile dans sa propre boite plutot que d'elargir la page. */
.alv-table {
  overflow-x: auto;
}

/* ── Sous 768 px : la table devient des cartes ─────────────────────────────
   Le contrat l'impose (§9) et une liste d'administration est exactement
   l'ecran ou on l'oublie. Un seul rendu, retourne en CSS : chaque cellule
   porte son intitule en `data-label`, pose ici a gauche de la valeur. */
@media (max-width: 767.98px) {
  .alv-table table,
  .alv-table tbody,
  .alv-table tr,
  .alv-table td {
    display: block;
    width: 100%;
  }

  .alv-table thead {
    /* Pas `display: none` : l'en-tete disparait a l'oeil mais reste lu par une
       synthese vocale, qui en a toujours besoin pour nommer les colonnes. */
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }

  .alv-table tr {
    margin-bottom: 12px;
    padding: 6px 12px;
    border: 1px solid var(--pj-line);
    border-radius: var(--pj-radius-sm);
  }

  .alv-table td {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 7px 0;
    border: 0;
    border-bottom: 1px solid var(--pj-line);
    text-align: right;
    /* Les deux lignes qui font tenir la carte dans 360px.

       Le theme pose `white-space: nowrap` sur les cellules du tableau, ce qui
       est juste tant qu'on est un tableau : une colonne s'elargit. Repliee en
       carte, la cellule fait toute la largeur, et une valeur qui refuse de se
       couper elargit alors la boite `.alv-table` — un quartier saisi en texte
       libre (« Route de Foumbot, face grand Bazar… », l'heritage l'a garde
       tel quel) reclamait 382px contre 269 disponibles, et lire la ligne
       demandait un geste lateral sur l'ecran ou arrivent ~90% du public.

       `normal` seul ne suffit pas : un email n'a aucune espace ou se couper,
       et reste un seul mot. `anywhere` le coupe — et il agit bien sur la
       taille min-content, la ou `break-word` ne l'aurait pas fait, ce qui est
       exactement ce dont un item flex a besoin pour accepter de retrecir. */
    white-space: normal;
    overflow-wrap: anywhere;
  }

  .alv-table tr td:last-child {
    border-bottom: 0;
  }

  /* La ou le doigt agit : 44px, mesures plutot que supposees. Inutile de
     l'imposer au clavier et a la souris, ou la rangee serait deux fois trop
     haute pour rien. */
  .alv-actions .btn {
    min-height: 44px;
  }

  /* Comme toute autre valeur de la carte : contre le bord droit, en face de
     son intitule. */
  .alv-actions {
    justify-content: flex-end;
  }

  .alv-table td::before {
    content: attr(data-label);
    /* L'intitule ne se replie pas, la valeur si. Le `white-space: normal` pose
       sur la cellule s'herite ici aussi, et « Email » revenait coupe en
       « Em / ail » : c'est un libelle fixe, court, dont la largeur est connue
       — c'est a la valeur de ceder la place, pas a lui. */
    flex: none;
    white-space: nowrap;
    color: var(--pj-muted);
    font-size: 0.8rem;
    font-weight: 600;
    text-align: left;
  }
}

/* =====================================================================
   Lanceur de rubriques (`.pj-search__*`)
   =====================================================================
   Le champ de la barre du haut n'etait qu'un decor ; il propose desormais les
   rubriques que la barre laterale montre. Les regles vivent ici parce que la
   barre du haut existe sur les trois coquilles, et que `prosyjob-app.css` est
   la seule feuille que les trois chargent. */
.pj-search {
  position: relative;
}

.pj-search__results {
  position: absolute;
  z-index: 1075;
  top: calc(100% + 6px);
  left: 0;
  width: 100%;
  min-width: 260px;
  padding: 6px;
  border: 1px solid var(--pj-line);
  border-radius: var(--pj-radius-sm);
  background: var(--pj-card);
  box-shadow: var(--pj-shadow);
}

/* Le fond de fermeture : un element reel plutot qu'un ecouteur sur `document`,
   qui survivrait a la barre du haut (§9) et s'accumulerait a chaque rendu. */
.pj-search__backdrop {
  position: fixed;
  z-index: 1074;
  inset: 0;
}

.pj-search__row {
  display: flex;
  width: 100%;
  min-height: 44px;
  align-items: center;
  gap: 10px;
  padding: 0 10px;
  border: 0;
  border-radius: var(--pj-radius-sm);
  background: none;
  color: var(--pj-ink);
  text-align: left;
  cursor: pointer;
}

/* Le survol et la selection au clavier partagent le meme etat visible : sans
   cela, flecher dans la liste ne se voit pas. */
.pj-search__row:hover,
.pj-search__row.is-active {
  background: var(--pj-card-2);
}

.pj-search__row i {
  width: 18px;
  color: var(--pj-muted);
  text-align: center;
}

.pj-search__empty,
.pj-search__more {
  margin: 0;
  padding: 10px;
  color: var(--pj-muted);
  font-size: 0.85rem;
}

/* La derniere ligne du lanceur : chercher le terme dans les contenus. Separee
   des rubriques par un filet, parce que c'est un geste d'une autre nature —
   on quitte la navigation pour interroger la base. */
.pj-search__row--global {
  margin-top: 4px;
  border-top: 1px solid var(--pj-line);
  border-radius: 0;
  color: var(--pj-orange-text);
  font-weight: 600;
}


/* ── Un seul survol pour les trois menus (2026-08-12) ──────────────────
   Trois listes de navigation, trois survols écrits par trois mains :

     .pj-nav a        (sidebar)          fond pâle + icône orange
     .pj-shortcut     (menu contextuel)  fond pâle, et rien d'autre
     .dropdown-item   (menu avatar)      le défaut Bootstrap, et rien d'autre

   Mesuré : le fond du thème (#F5F7FB) contre le blanc de la carte est un
   écart de 2 % — invisible seul. Ce qui rend le survol de la sidebar
   *lisible*, c'est l'accent orange, et lui n'existait que là. Le menu
   contextuel était même en dessous de rien du tout : son carré d'icône est
   peint plus SOMBRE (#EEF1F6) que le fond de survol, donc la tuile se
   dissolvait dans sa propre ligne au lieu d'en ressortir.

   L'état est donc écrit une fois pour les trois : fond `--pj-card-2`,
   libellé et icône `--pj-orange-text`. La variante *texte* de la marque et
   non `--pj-orange`, qui mesure 3,03:1 sur ce fond — sous le plancher AA
   pour un libellé, et à 0,03 du plancher 1.4.11 pour un glyphe, ce qui est
   une conformité qu'un futur réglage de `--pj-card-2` casse sans prévenir.
   `--pj-orange-text` donne 4,69:1 en clair et 6,19:1 en sombre, mesurés.
   C'est la sidebar qui était fautive là (elle héritait de `.pj-app a:hover`,
   d'où le `--pj-orange`), pas les deux copies : on unifie vers le jeton qui
   passe plutôt que de propager le défaut. Et `--pj-orange-fill`, le troisième
   candidat, n'est PAS redéfini par le bloc sombre — c'est un fond sous du
   blanc, pas une encre : il tombe à 2,85:1 en sombre.

   Rien n'est scopé à un écran : c'est la même question posée trois fois. */

/* Le fond. `--pj-card-2` en clair comme en sombre, ce qui rend inutile de
   savoir quel thème est actif — et les deux surcharges sombres qui
   existaient déjà pour .pj-nav et .pj-shortcut disent la même valeur. */
.pj-app .pj-nav a:hover,
.pj-app .pj-shortcut:hover:not(.is-active) {
    background: var(--pj-card-2);
}

/* Le libellé. Sur la tuile du menu contextuel c'est `__t` qui porte la
   couleur, pas l'ancre — `.pj-shortcut__t` déclare la sienne. */
.pj-app .pj-nav a:hover,
.pj-app .pj-shortcut:hover:not(.is-active) .pj-shortcut__t {
    color: var(--pj-orange-text);
}

/* L'icône, du même jeton que le libellé : quand les deux portent l'accent,
   les voir dans deux oranges différents se remarque. */
.pj-app .pj-nav a:hover i,
.pj-app .pj-nav a:hover svg,
.pj-app .pj-shortcut:hover:not(.is-active) .pj-shortcut__ico,
.pj-app .dropdown-item:hover > i,
.pj-app .dropdown-item:focus > i {
    color: var(--pj-orange-text);
}

/* Le carré d'icône du menu contextuel : il doit s'éclairer, pas se fondre.
   Teinte douce plutôt que le plein orange, que `.is-active` occupe déjà —
   sans quoi survoler ressemblerait à sélectionner. */
.pj-app .pj-shortcut:hover:not(.is-active) .pj-shortcut__ico {
    background: var(--pj-orange-soft);
}

/* Les transitions manquantes : le thème n'anime que `background` sur la
   tuile, et rien du tout sur une entrée de menu. */
.pj-app .pj-shortcut,
.pj-app .pj-shortcut__ico,
.pj-app .dropdown-item,
.pj-app .dropdown-item > i {
    transition: background-color .18s, color .18s;
}

/* ── Le menu avatar, et la langue (même composant) ─────────────────────
   Ici on ne se bat pas sur la spécificité : Bootstrap lit ses propres
   jetons pour peindre `.dropdown-item:hover`, alors on les redéfinit sur le
   conteneur — le même geste que `.btn-primary` plus haut. Une règle
   `:root[data-theme=dark] .pj-app .dropdown-item { color }` aurait pesé
   (0,4,0) et battu le `:hover` en (0,3,1) : l'accent aurait disparu en
   sombre exactement là où il manquait déjà.

   Le fond, lui, doit rester une déclaration : `user-template/css/style.css`
   force `.dropdown-menu { background-color: #fff }`, ce qui court-circuite
   `--bs-dropdown-bg` et laissait, en thème sombre, une boîte BLANCHE avec
   des libellés `#dee2e6` — 1,3:1 mesuré, c'est-à-dire illisible — et un
   survol `#2b3035`, bandeau noir à l'envers du reste du menu. */
.pj-app .dropdown-menu {
    --bs-dropdown-bg: var(--pj-card);
    --bs-dropdown-border-color: var(--pj-line);
    --bs-dropdown-link-color: var(--pj-ink);
    --bs-dropdown-link-hover-color: var(--pj-orange-text);
    --bs-dropdown-link-hover-bg: var(--pj-card-2);
    --bs-dropdown-link-active-color: var(--pj-orange-text);
    --bs-dropdown-link-active-bg: var(--pj-orange-soft);
    --bs-dropdown-header-color: var(--pj-muted);
    --bs-dropdown-divider-bg: var(--pj-line);
    background-color: var(--pj-card);
    border-color: var(--pj-line);
    border-radius: var(--pj-radius-sm);
    /* La pastille de survol a besoin d'une marge pour ne pas toucher les
       bords : sans elle, un coin arrondi sur une entrée pleine largeur ne
       se voit pas. */
    padding: 8px;
}

.pj-app .dropdown-item {
    border-radius: var(--pj-radius-sm);
}

.pj-app .dropdown-header {
    padding-left: 8px;
    padding-right: 8px;
}

/* ------------------------------------------------------------------ *
 * M22 « Vos annonces » — les quatre emplacements
 *
 * Le catalogue vend quatre placements. Jusqu'ici un seul était dessiné :
 * une campagne achetée sur les trois autres était payée, modérée, mise
 * en ligne par le moteur — et peinte nulle part. Ces règles sont l'autre
 * moitié de la vente.
 *
 * Un emplacement sans annonce reste `hidden`, et non « vide » : une bande
 * vide occupe quand même sa ligne et repousse le pied de page sur toutes
 * les pages du site, c'est-à-dire presque toujours.
 * ------------------------------------------------------------------ */

/* Le lien lui-même, commun aux quatre emplacements (AdSlotComponent). */
.pj-ad__link {
    display: block;
    position: relative;
    color: inherit;
    text-decoration: none;
}

/* L'image porte le ratio de son emplacement et ne doit jamais déborder de
   sa colonne : le rail est étroit sur mobile, et une bannière fournie en
   600px de large y ferait défiler toute la page. */
.pj-ad__link img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--pj-radius-sm);
}

/* ── L'annonce texte ──────────────────────────────────────────────────
   La forme demandée par le propriétaire du produit (annonce_texte.png) :
   une tuile d'icône à gauche, un titre en gras, un paragraphe gris.
   L'icône est fixe et décorative — une campagne texte ne téléverse rien —
   d'où `aria-hidden` côté markup : le lecteur d'écran reçoit le titre et
   le texte, pas un porte-voix. */
.pj-ad__link--text {
    align-items: flex-start;
    display: flex;
    gap: 14px;
}

.pj-ad__icon {
    align-items: center;
    background: var(--pj-orange-soft);
    border-radius: var(--pj-radius-sm);
    color: var(--pj-orange-text);
    display: flex;
    /* Ni rétrécie ni étirée : c'est un carré, quelle que soit la longueur
       du texte à côté. */
    flex: 0 0 64px;
    font-size: 24px;
    height: 64px;
    justify-content: center;
}

.pj-ad__copy {
    display: block;
    min-width: 0;
}

.pj-ad__copy strong,
.pj-ad__copy span {
    display: block;
}

.pj-ad__copy strong {
    color: var(--pj-ink);
    font-size: 15px;
    line-height: 1.3;
    margin-bottom: 4px;
}

.pj-ad__copy span {
    color: var(--pj-muted);
    font-size: 13px;
    line-height: 1.5;
}

/* Le passage d'une annonce à la suivante. Une apparition, pas un
   glissement : la carte ne bouge pas de place, seul son contenu change,
   ce qui évite de déplacer sous le doigt ce que quelqu'un s'apprêtait à
   toucher. */
@keyframes pj-ad-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

.pj-ad--inline .pj-ad__link {
    animation: pj-ad-in 320ms ease-out;
}

/* Qui demande moins de mouvement n'en reçoit aucun. La rotation, elle,
   continue : la supprimer priverait purement et simplement d'audience le
   deuxième annonceur de la liste. */
@media (prefers-reduced-motion: reduce) {
    .pj-ad--inline .pj-ad__link {
        animation: none;
    }
}

/* Sous 480px la tuile et le texte se marchent dessus — 64px de carré plus
   14 de gouttière sur une colonne de 297, il ne reste pas de quoi lire
   trois mots par ligne. Mesuré, pas supposé. */
@media (max-width: 480px) {
    .pj-ad__icon {
        flex-basis: 48px;
        font-size: 20px;
        height: 48px;
    }
}

/* Le rail (300x250) garde le cadre du thème : une annonce doit ressembler
   à l'emplacement, pas le remplacer. Seul le contenu change.
 *
 * Et il garde surtout ses **couleurs**. Le bloc `.pj-promo` du thème est
 * peint en bleu, alors que la carte texte est écrite pour un fond blanc :
 * `--pj-ink` sur ce bleu-là, c'est du bleu nuit sur bleu nuit. Mesuré à
 * l'écran — la tuile d'icône s'affichait, le titre et le texte non. Le
 * `inherit` rend la typographie au bloc porteur. */
.pj-promo--ad .pj-ad__copy strong,
.pj-promo--ad .pj-ad__copy span {
    color: inherit;
}

/* La tuile suit, pour la même raison : un aplat pêche sur le bleu du
   thème est une pièce rapportée. */
.pj-promo--ad .pj-ad__icon {
    /* Dérivée de la couleur du texte plutôt qu'écrite : pas de valeur
       brute (règle du projet), et la tuile suit le bloc quel que soit le
       thème, sans qu'un jeton de plus soit à miroiter dans les deux
       copies de l'échelle. */
    background: color-mix(in srgb, currentColor 16%, transparent);
    color: inherit;
}

/* Les hôtes que la coquille pose elle-même — la bande de pied de page et
   la verticale. Ils portent leur propre carte, n'ayant pas de bloc thème
   sous eux. */
.pj-ad--band,
.pj-ad--side {
    background: var(--pj-card);
    border: 1px solid var(--pj-line);
    border-radius: var(--pj-radius);
    box-shadow: var(--pj-shadow-sm);
    display: grid;
    gap: 8px;
    padding: 12px;
}

/* La bande 728x90. Elle est un frère de `.pj-shell` dans `#app`, qui est
   un flex en colonne : sans `width: 100%` une marge auto sur l'axe
   transverse annule le `stretch` et la bande se dimensionne sur son
   propre contenu — le défaut que §2 corrige pour la coquille et le pied
   de page. Même cause, même remède. */
.pj-ad--band {
    width: 100%;
    max-width: var(--pj-max);
    margin: 0 auto 16px;
}

/* ── Tableau de bord : parcours et espaces (maquette du 2026-09-27) ─────

   Les trois parcours et les quatre tuiles de profil partagent un conteneur,
   `.dash-lanes`, et c'est SA largeur qui choisit la mise en page — pas celle
   de l'écran. À 1366px la colonne centrale ne fait qu'environ 690px entre le
   menu et le rail : une media query y aurait posé trois parcours côte à côte
   illisibles. La container query les y empile, et ne les aligne qu'à partir
   de 860px de colonne (un écran de 1920px). (mesuré).

   Téléphone (et colonne étroite) : parcours en lignes compactes — pastille,
   titre, chevron, sans la description — et tuiles de profil en 2×2.
   Colonne large : trois cartes de parcours avec leur description, quatre
   tuiles sur une ligne.

   `display: grid` + `gap` sur chaque boîte intermédiaire : une `.pj-card` ne
   porte aucune marge, donc une boîte sans gap absorberait l'espacement
   (CLAUDE.md §9, check-card-gaps.mjs). */
.dash-lanes {
    container: dash-lanes / inline-size;
    display: grid;
    gap: 16px;
}

/* L'en-tête : `.pj-hero p` (prosyjob.css) réserve 22px sous l'intro pour un
   bouton que cette carte n'a pas — la maquette la veut serrée. */
.dash-hero p {
    margin-bottom: 0;
}

/* ── Les pastilles de section ── */
.dash-pill {
    display: inline-block;
    padding: 5px 12px;
    border-radius: var(--pj-radius-pill);
    font-size: var(--pj-fs-xs);
    font-weight: 700;
}

.dash-pill--resume {
    background: var(--pj-orange-soft);
    color: var(--pj-orange-text);
}

.dash-pill--next {
    background: var(--pj-info-soft);
    color: var(--pj-info-ink);
}

/* Le premier rappel de « Reprenez où vous en étiez » est le titre de la carte :
   le travail le plus récent, que le backend a mis en tête. */
#dash-resume .dash-actions__item:first-child .dash-next__text {
    font-weight: 700;
}

/* ── Les trois parcours ── */
.dash-journeys {
    display: grid;
    gap: 10px;
    grid-template-columns: minmax(0, 1fr);
}

/* Classes doublées : `.pj-app a { color: inherit }` pèse (0,1,1), et
   `.pj-card` (prosyjob.css) peint le fond blanc à (0,1,0) — une classe seule
   perdrait l'un et l'autre. */
.dash-journey.dash-journey {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 56px;
    padding: 10px 16px;
    color: var(--pj-ink);
    text-decoration: none;
    border: 1px solid transparent;
    background: color-mix(in srgb, var(--dj-soft) 55%, var(--pj-card));
    transition: border-color .15s ease;
}

.dash-journey.dash-journey:hover {
    border-color: var(--dj-ink);
}

.dash-journey--blue   { --dj-soft: var(--pj-info-soft);    --dj-ink: var(--pj-info-ink); }
.dash-journey--green  { --dj-soft: var(--pj-success-soft); --dj-ink: var(--pj-success); }
.dash-journey--orange { --dj-soft: var(--pj-orange-soft);  --dj-ink: var(--pj-orange-text); }

.dash-journey__ico {
    flex: none;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--dj-soft);
    color: var(--dj-ink);
    font-size: 16px;
}

.dash-journey__body {
    flex: 1;
    min-width: 0;
}

.dash-journey h2,
.dash-journey__title {
    margin: 0;
    font-size: var(--pj-fs-body);
    font-weight: 700;
    line-height: 1.25;
}

/* La description ne s'affiche qu'en colonne large : sur téléphone, la
   maquette réduit chaque parcours à une ligne. */
.dash-journey p,
.dash-journey__text {
    display: none;
    margin: 4px 0 0;
    color: var(--pj-muted);
    font-size: var(--pj-fs-sm);
    line-height: 1.4;
}

.dash-journey__go {
    flex: none;
    color: var(--pj-ink);
    font-size: 14px;
}

/* The same card as a <button> — the answers of /demarrer (2026-09-29). A
   button may only hold phrasing content, hence the `__title`/`__text` spans
   beside the dashboard's h2/p; the title span must become a block itself.
   `width: 100%` because the shell's bare-button rule is not what sizes a grid
   item here, and `text-align` because a button centres by default. */
.dash-journey__title {
    display: block;
}

button.dash-journey.dash-journey {
    width: 100%;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

button.dash-journey.dash-journey:disabled {
    opacity: .6;
    cursor: progress;
}

/* ── Les quatre espaces ── */
.dash-tiles {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
}

/* Classe doublee : `.pj-app a { color: inherit }` (prosyjob.css) pese
   (0,1,1) et battait cette regle (0,1,0), donc le lien heritait de la
   couleur de son parent au lieu de la sienne. Voir check-link-colors.mjs. */
.dash-tile.dash-tile {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 64px;
    padding: 10px 8px;
    background: var(--pj-card);
    border: 1px solid var(--pj-line);
    border-radius: var(--pj-radius-sm);
    box-shadow: var(--pj-shadow-sm);
    color: var(--pj-ink);
    text-decoration: none;
}

.dash-tile:hover {
    border-color: var(--pj-orange);
}

/* `flex: none` parce qu'un flex item se laisse comprimer par défaut : sans lui
   la pastille rétrécit dès que le nom passe à la ligne. */
.dash-tile__ico {
    flex: none;
    /* 32px sur téléphone : la tuile n'y fait que 144px de large, et chaque
       pixel pris par la pastille est retiré à « Organisme de formation ». */
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--pj-card-2);
    color: var(--pj-navy);
    font-size: 14px;
}

.dash-tile__body {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.dash-tile__name {
    font-weight: 700;
    font-size: var(--pj-fs-sm);
    line-height: 1.25;
}

.dash-tile__stats {
    font-size: var(--pj-fs-2xs);
    line-height: 1.35;
    color: var(--pj-muted);
    display: flex;
    flex-wrap: wrap;
    gap: 0 6px;
}

.dash-tile__stats b {
    color: var(--pj-ink);
}

/* Le profil non activé se lit à la teinte, pas à un intertitre. */
.dash-tile--muted .dash-tile__ico {
    background: color-mix(in srgb, var(--pj-muted) 14%, transparent);
    color: var(--pj-muted);
}

.dash-tile--muted .dash-tile__name {
    font-weight: 600;
    color: var(--pj-muted);
}

/* ── Colonne moyenne (≥560px, une tablette ou 1366px d'écran, où la
   colonne mesure ~690px) : les parcours restent en lignes mais retrouvent
   leur description, les tuiles restent en 2×2 et grandissent. Trois
   parcours côte à côte n'y laisseraient qu'une centaine de pixels au texte. */
@container dash-lanes (min-width: 560px) {
    .dash-journey.dash-journey {
        padding: 14px 16px;
        gap: 14px;
    }

    .dash-journey__ico {
        width: 48px;
        height: 48px;
        font-size: 20px;
    }

    .dash-journey p,
    .dash-journey__text {
        display: block;
    }

    .dash-journey h2,
    .dash-journey__title {
        font-size: var(--pj-fs-md);
    }

    .dash-tiles {
        gap: 12px;
    }

    .dash-tile.dash-tile {
        gap: 12px;
        min-height: 76px;
        padding: 12px 14px;
    }

    .dash-tile__ico {
        width: 52px;
        height: 52px;
        font-size: 20px;
    }

    .dash-tile__name {
        font-size: var(--pj-fs-body);
    }

    .dash-tile__stats {
        font-size: var(--pj-fs-xs);
    }
}

/* ── Colonne large (≥860px, un écran de 1920px) : la mise en page
   « ordinateur » de la maquette — trois parcours, quatre tuiles. ── */
@container dash-lanes (min-width: 860px) {
    .dash-journeys {
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 16px;
    }

    .dash-journey.dash-journey {
        min-height: 96px;
    }

    .dash-journey__ico {
        width: 56px;
        height: 56px;
        font-size: 22px;
    }

    .dash-journey h2,
    .dash-journey__title {
        font-size: var(--pj-fs-md);
    }

    .dash-tiles {
        grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: 16px;
    }

    .dash-tile__stats {
        flex-direction: column;
    }
}

/* The same three-across layout on /demarrer, where the cards sit inside the
   question's card: its padding takes ~54px from the container, which at a
   1920px window measured 846px against the dashboard's 900. Lowering the
   threshold by that much makes both screens switch at the same window
   width. Same declarations as the block above, scoped to `.onb-goal`. */
@container dash-lanes (min-width: 800px) {
    .onb-goal .dash-journeys {
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 16px;
    }

    .onb-goal .dash-journey.dash-journey {
        min-height: 96px;
    }

    .onb-goal .dash-journey__ico {
        width: 56px;
        height: 56px;
        font-size: 22px;
    }

    .onb-goal .dash-journey__title {
        font-size: var(--pj-fs-md);
    }
}

/* ── « Quoi de neuf » (recette 2026-08) ────────────────────────────────
   Un champ, un compteur, un bouton. Volontairement pauvre : la recette
   demande « un court texte », pas un éditeur. */
.dash-post {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.dash-post__pick {
    align-self: flex-start;
    /* `width: auto` : le sélecteur d'entreprise n'a aucune raison de barrer
       la carte sur toute sa largeur, et `.field select` le ferait. */
    width: auto;
    max-width: 100%;
    min-height: 44px;
}

.dash-post__text {
    width: 100%;
    min-height: 66px;
    padding: 10px 12px;
    border: 1px solid var(--pj-line);
    border-radius: var(--pj-radius-sm);
    background: var(--pj-card);
    color: var(--pj-ink);
    font: inherit;
    resize: vertical;
}

.dash-post__text::placeholder {
    color: var(--pj-muted);
}

.dash-post__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.dash-post__count {
    font-size: 12px;
    color: var(--pj-muted);
}

/* `width: auto` : dans la coquille connectée un bouton nu prendrait toute la
   largeur, et le pied du composeur deviendrait deux lignes empilées. */
.dash-post__send {
    width: auto;
    min-height: 44px;
    flex: none;
}

.dash-feed__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}

.dash-feed__title {
    margin: 0;
    font-size: 16px;
}

/* 44px de cible, comme partout : mesuré à 18,8px au premier jet, parce
   qu'un lien texte ne fait que la hauteur de sa ligne. C'est pourtant l'un
   des deux gestes que la recette nomme dans ce bloc (« puis un lien pour en
   voir davantage »). `align-items: baseline` sur l'en-tête aligne toujours le
   texte sur le titre ; seule la zone cliquable grandit. */
.dash-feed__more {
    flex: none;
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 4px;
    font-size: 13px;
    font-weight: 700;
    color: var(--pj-orange-text);
    text-decoration: none;
}

.dash-feed__more:hover {
    text-decoration: underline;
}

.dash-feed__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 12px;
}

.dash-feed__item {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    /* 44px de cible garantis quel que soit le contenu : une entrée sans
       sous-titre ni méta n'est qu'une ligne de texte. */
    min-height: 44px;
    padding: 10px;
    border: 1px solid var(--pj-line);
    border-radius: var(--pj-radius-sm);
    color: var(--pj-ink);
    text-decoration: none;
}

.dash-feed__item:hover {
    border-color: var(--pj-orange);
    background: var(--pj-card-2);
}

.dash-feed__img {
    flex: none;
    width: 44px;
    height: 44px;
    border-radius: 8px;
    overflow: hidden;
    background: var(--pj-card-2);
}

.dash-feed__img img {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

/* `min-width: 0` : sans lui un titre sans espace (une URL collée dans un
   intitulé d'offre) élargit la piste de la grille et fait déborder la page. */
.dash-feed__body {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.dash-feed__name {
    font-weight: 700;
    font-size: 14px;
}

.dash-feed__sub,
.dash-feed__meta {
    font-size: 12px;
    color: var(--pj-muted);
}

/* ── Le bandeau des actualités diffusées (recette 2026-08) ─────────────
   Frère de `.pj-shell` dans `#app`, donc soumis exactement aux mêmes règles
   de largeur que la bande publicitaire juste au-dessus : sans `width: 100%`
   la marge auto sur l'axe transverse annule le `stretch` et le bandeau se
   dimensionne sur son propre contenu (§2). Même cause, même remède. */
.pj-news {
    width: 100%;
    max-width: var(--pj-max);
    margin: 0 auto 16px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.pj-news__item {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 14px 16px;
    background: var(--pj-card);
    border: 1px solid var(--pj-line);
    /* Le filet orange porte tout le sens : c'est ce qui distingue une
       annonce d'une carte ordinaire, sans recourir à un fond coloré qui
       demanderait sa propre paire de jetons clair/sombre. */
    border-left: 3px solid var(--pj-orange);
    border-radius: var(--pj-radius-sm);
}

.pj-news__ico {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 999px;
    background: var(--pj-orange-soft);
    color: var(--pj-orange-text);
    font-size: 13px;
}

.pj-news__body {
    flex: 1 1 auto;
    /* `min-width: 0` : sans lui un titre sans espace (une URL collée) élargit
       le flex item et fait déborder toute la page horizontalement. */
    min-width: 0;
}

.pj-news__title {
    display: block;
    font-weight: 700;
    color: var(--pj-ink);
    text-decoration: none;
}

.pj-news__title:hover {
    color: var(--pj-orange-text);
}

.pj-news__excerpt {
    margin: 2px 0 0;
    font-size: 13px;
    color: var(--pj-muted);
}

/* 44px de cible, mesurés et non déduits du padding — c'est la règle du
   projet et `.btn--sm` est resté cinq jalons à 38px faute de la mesurer. */
.pj-news__close {
    flex: none;
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin: -6px -6px 0 0;
    background: none;
    border: 0;
    border-radius: 999px;
    color: var(--pj-muted);
    cursor: pointer;
}

.pj-news__close:hover,
.pj-news__close:focus-visible {
    background: var(--pj-card-2);
    color: var(--pj-ink);
}

/* ── Bandeau « reprendre mon parcours » ───────────────────────────────────
   Meme gabarit que `.pj-news` juste au-dessus, et pour les memes raisons :
   frere de `.pj-shell`, donc `width: 100%` obligatoire (§2), sans quoi la
   marge auto sur l'axe transverse annule le `stretch` et la bande se
   dimensionne sur son propre contenu.

   Le filet est orange comme celui d'une actualite : c'est la meme famille de
   message — quelque chose que la plateforme signale sans l'imposer. */
.pj-resume {
    width: 100%;
    max-width: var(--pj-max);
    margin: 0 auto 16px;
}

.pj-resume__item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    background: var(--pj-card);
    border: 1px solid var(--pj-line);
    border-left: 3px solid var(--pj-orange);
    border-radius: var(--pj-radius-sm);
}

.pj-resume__ico {
    flex: none;
    color: var(--pj-orange);
    font-size: 18px;
}

/* `min-width: 0` : sans lui, un intitule de parcours long refuse de se
   reduire et pousse le bouton hors de la bande — un element flex refuse de
   passer sous sa largeur de contenu tant qu'on ne l'y autorise pas. */
.pj-resume__body {
    flex: 1 1 auto;
    min-width: 0;
}

.pj-resume__title {
    margin: 0;
    font-size: 14px;
    color: var(--pj-ink);
}

.pj-resume__name {
    margin: 2px 0 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--pj-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* `width: auto` : dans la coquille connectee tout `<button>` nu est a 100 %
   (prosyjob-components.css), et un lien mis en bouton doit s'en extraire
   lui-meme. 44px de haut, la cible minimale du projet.

   Classe doublee pour la meme raison que `.cd-frozen__cta` ci-dessous :
   `.pj-app a { color: inherit }` bat une classe unique, et ce lien-ci pose
   son propre fond. */
.pj-resume__go.pj-resume__go {
    flex: none;
    width: auto;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 0 16px;
    background: var(--pj-orange);
    color: #fff;
    border-radius: var(--pj-radius-sm);
    text-decoration: none;
    font-size: 14px;
    font-weight: 600;
}

.pj-resume__go.pj-resume__go:hover,
.pj-resume__go.pj-resume__go:focus-visible {
    filter: brightness(0.94);
    color: #fff;
}

.pj-resume__close {
    flex: none;
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: 0;
    border-radius: 999px;
    color: var(--pj-muted);
    cursor: pointer;
}

.pj-resume__close:hover,
.pj-resume__close:focus-visible {
    background: var(--pj-card-2);
    color: var(--pj-ink);
}

/* A 360px la bande tient encore sur une ligne si l'intitule s'efface ; en
   dessous de 480 on empile, le bouton passant pleine largeur plutot que de
   se faire ecraser a cote du titre. */
@media (max-width: 480px) {
    .pj-resume__item {
        flex-wrap: wrap;
    }

    /* Classe doublee ici AUSSI : la regle de base l'est, et une classe
       unique dans un media ne bat pas (0,2,0). Sans cela le bouton gardait
       son `flex: none` et restait a 124px a cote du titre au lieu de passer
       sur sa propre ligne. Mesure a 360px, pas deduite. */
    .pj-resume__go.pj-resume__go {
        flex: 1 1 100%;
        justify-content: center;
    }
}

/* ── « Un code de vérification vous attend » (E65) ─────────────────────────
   Le même gabarit que « reprendre mon parcours », dont il est le frère :
   CodeResumeComponent. Il peut porter plusieurs codes à la fois (un
   transfert de Miles et un changement d'adresse), d'où la grille — une
   ligne par code, espacées par le `gap` et rien d'autre.

   `--inline` : la même bande posée DANS une carte, sur les écrans sans
   coquille (connexion, inscription) — celle de la coquille y est masquée
   avec la barre du haut (`.is-chromeless`), sans quoi elle s'y affichait
   deux fois. La carte fait 480px au plus, donc la
   bande s'y empile toujours — le média à 480px ci-dessus regarde la fenêtre,
   pas la carte, et laissait titre, adresse, bouton et croix sur une ligne
   de 400px. */
#code-resume,
.pj-resume--inline {
    display: grid;
    gap: 8px;
}

.pj-resume--inline {
    max-width: none;
    margin: 0 0 16px;
}

.pj-resume--inline .pj-resume__item {
    flex-wrap: wrap;
}

.pj-resume--inline .pj-resume__go.pj-resume__go {
    flex: 1 1 100%;
    justify-content: center;
}

/* Once the band wraps (inline always, the shell's below 480px), the cross
   stays on the first line beside the title and the button takes the second
   on its own. Two things make that hold, both measured at 360px: `order`,
   or the cross wrapped alone onto a third line; and a zero basis on the
   text, because `flex: 1 1 auto` sizes it to its max-content — the address
   on one unbroken line — and pushed the cross off the line it shared. */
.pj-resume--inline .pj-resume__body {
    flex-basis: 0;
}

.pj-resume--inline .pj-resume__close {
    order: 2;
}

.pj-resume--inline .pj-resume__go.pj-resume__go {
    order: 3;
}

@media (max-width: 480px) {
    #code-resume .pj-resume__body {
        flex-basis: 0;
    }

    #code-resume .pj-resume__close {
        order: 2;
    }

    #code-resume .pj-resume__go.pj-resume__go {
        order: 3;
    }
}

/* /verifier : « Vous avez appuyé sur le bouton de l'e-mail ? connectez-vous ».
   Le bouton se lit comme un lien, et `width: auto` le sort de la règle qui met
   tout `<button>` nu à 100 %. */
.vf-link-hint {
    margin: 16px 0 0;
    text-align: center;
    font-size: var(--pj-fs-sm);
    color: var(--pj-muted);
}

.vf-link-hint__go {
    width: auto;
    min-height: 44px;
    padding: 0 4px;
    background: none;
    border: 0;
    color: var(--pj-orange-text);
    font: inherit;
    font-weight: 600;
    text-decoration: underline;
    cursor: pointer;
}

/* /verifier/lien — les trois faces du lien du mail. */
.vl-face h3 {
    margin: 12px 0 8px;
}

.vl-face .btn {
    margin-top: 16px;
}

.vl-face__icon {
    display: inline-flex;
    font-size: var(--pj-fs-display); /* fs-ok: pictogramme d'état, pas du texte */
    color: var(--pj-muted);
}

.vl-face__icon--ok {
    color: var(--pj-success);
}

.vl-face__icon--ko {
    color: var(--pj-danger);
}
.pj-news__all {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-height: 44px;
    padding: 0 4px;
    font-size: 13px;
    font-weight: 700;
    color: var(--pj-orange-text);
    text-decoration: none;
}

.pj-news__all:hover {
    text-decoration: underline;
}

.pj-ad--band .pj-ad__link img {
    /* 728x90 : on borne la hauteur plutôt que la largeur, sinon un visuel
       fourni en 1456px étire la bande sur les grands écrans. */
    max-height: 90px;
    width: auto;
    max-width: 100%;
    margin: 0 auto;
}

/* L'unité texte, dans le corps de la page.
 *
 * Une classe à elle plutôt que `.pj-ad--band`, pour une raison mesurée et
 * une seule : la bande porte `margin: 0 auto 16px`, faite pour se centrer
 * sous `--pj-max` entre deux frères de `#app`. Dans `#content`, qui est
 * un flex en colonne avec son propre `gap`, ces 16 px **s'ajoutent** à
 * l'espacement au lieu de le remplacer — la cumulation que le §9 de
 * CLAUDE.md interdit, « une seule mécanique d'espacement par page ».
 * Mesuré : 16 px de trop sous l'annonce avec `--band`, 0 avec celle-ci.
 * La largeur, elle, ne pose pas de problème dans les deux cas (625 px de
 * colonne, 625 px d'annonce) : le `width: 100%` de la bande suffit à
 * battre le rétrécissement qu'une marge auto provoquerait autrement. */
.pj-ad--inline {
    background: var(--pj-card);
    border: 1px solid var(--pj-line);
    border-radius: var(--pj-radius);
    box-shadow: var(--pj-shadow-sm);
    display: grid;
    gap: 8px;
    padding: 12px;
    width: 100%;
}

.pj-ad--inline .pj-ad__link img {
    max-height: 120px;
    width: auto;
    max-width: 100%;
}

/* La verticale 140x420, dans le rail connecté sous le bloc promo. */
.pj-ad--side {
    margin-top: 16px;
}

.pj-ad--side .pj-ad__link img {
    max-width: 140px;
    margin: 0 auto;
}

/* …et sa propre colonne dans la coquille visiteur, ajoutée seulement
   quand une annonce a été servie — `AppShell` pose la classe. Sans elle
   la grille reste à une colonne, donc une page sans annonce garde
   exactement la largeur qu'elle avait. */
.pj-shell--guest.has-adside {
    grid-template-columns: minmax(0, 1fr) 164px;
}

/* Sous 1100px la colonne n'existe pas : `AdSlotComponent.when()` refuse
   alors la requête, pour qu'aucune impression ne soit comptée pour une
   bannière que personne n'a vue. La règle ci-dessous n'est donc qu'un
   filet — elle rattrape le redimensionnement d'une fenêtre déjà peinte. */
@media (max-width: 1100px) {
    .pj-shell--guest.has-adside {
        grid-template-columns: minmax(0, 1fr);
    }

    .pj-shell--guest > .pj-ad--side {
        display: none;
    }
}


/* ── 5. Les boutons, ramenés sur les tokens ────────────────────────────
   Recensement du 2026-09-08 : 53 classes contenant `btn`, 2 022 usages dans
   236 fichiers, et 12 fonds distincts dans un seul thème. Ce que la mesure
   a trouvé, et que ce bloc corrige :

     .btn-success      #FF3115 — un ORANGE. Le bouton « succès » et le bouton
                       primaire avaient la même teinte, et l'état désactivé
                       repassait au vert de Bootstrap : la couleur changeait
                       de sens selon l'état.
     .btn-danger       #FF0100, un rouge sRGB pur que personne n'a choisi.
     .btn-outline-success  #00E65B — 1,56:1 sur blanc, illisible.
     .btn-link         #0D6EFD, le bleu de Bootstrap, dans une marque orange.
     .btn-secondary    #6C757D, le gris d'usine de Bootstrap.
     .btn-primary:hover  fond #B03A12 et texte #FF431A : 1,75:1. Le libellé
                       du bouton le plus utilisé de l'app (277 usages)
                       disparaissait au survol. C'est le défaut le plus grave
                       du lot et le moins visible en relecture, parce qu'il
                       n'existe que pendant le survol.

   ## Pourquoi il faut repointer les propriétés finales

   Bootstrap 5 peint `.btn` depuis des variables (`--bs-btn-bg`…), ce qui rend
   tous les états cohérents dès qu'on renseigne la variable. Mais les deux
   thèmes Kofejob peignent `.btn-primary`, `.btn-danger` et `.btn-success` en
   DUR sur la classe, et une déclaration directe sur `.btn-primary` (0,1,0)
   l'emporte sur celle de `.btn` (0,1,0) qui la précède. Renseigner
   `--bs-btn-bg` seul serait donc ignoré précisément pour les trois classes
   qui en ont le plus besoin.

   D'où les deux temps ci-dessous : on rend d'abord aux classes le mécanisme à
   variables, puis chaque classe ne déclare plus que ses valeurs. Une seule
   source par classe, et repos / survol / actif / désactivé suivent ensemble —
   c'est ce qui empêche un état de repartir dans une autre teinte.

   Les règles ne sont pas préfixées par une coquille : `.pj-app` et
   `.layout-admin` s'excluent, et un bouton doit avoir la même tête des deux
   côtés. C'est aussi ce que dit CLAUDE.md §9 — ce qui vaut partout s'écrit
   ici, non scopé.

   Pas de `!important`. prosyjob.css en pose sur `.pj-app .btn-primary` et
   `.pj-app .btn-outline-primary:hover`, mais il y écrit exactement les mêmes
   tokens que ce bloc : les deux disent la même chose, donc rien ne se dispute.
   Ce fichier étant VENDORÉ, si le designer livre un jour une autre valeur,
   c'est la sienne qui gagnera sur ces deux déclarations-là — et le tableau du
   recensement est ce qui le montrera.

   Intouchées, parce que ce sont des classes de forme et non de couleur :
   `.btn`, `.btn-sm`, `.btn-lg`, `.btn-block`, `.btn-close`. */

/* `.btn.btn-X` et non `.btn-X`, et la raison ne se voit que sur un écran réel.
   Une bonne moitié des boutons de l'app sont des `<a class="btn …">`, et
   prosyjob.css déclare `.pj-app a { color: inherit }` — (0,1,1), ce qui bat une
   règle à une seule classe (0,1,0) quel que soit l'ordre des feuilles. Le fond
   arrivait donc bien et le libellé héritait de son parent : sur « ← Espace
   entreprise » cela donnait un texte encre sur le fond orange doux, lisible et
   faux. Ajouter `.btn` porte la règle à (0,2,0) et couvre `<a>` comme
   `<button>` sans avoir à nommer une coquille.

   Les blocs de valeurs plus bas n'en ont pas besoin : ils ne déclarent que des
   propriétés personnalisées, que `.pj-app a` ne dispute pas. */
.btn.btn-primary,
.btn.btn-secondary,
.btn.btn-danger,
.btn.btn-success,
.btn.btn-outline-primary,
.btn.btn-outline-secondary,
.btn.btn-outline-danger,
.btn.btn-outline-success,
.btn.btn-link {
    background-color: var(--bs-btn-bg);
    border-color: var(--bs-btn-border-color);
    color: var(--bs-btn-color);
}

.btn.btn-primary:hover,
.btn.btn-secondary:hover,
.btn.btn-danger:hover,
.btn.btn-success:hover,
.btn.btn-outline-primary:hover,
.btn.btn-outline-secondary:hover,
.btn.btn-outline-danger:hover,
.btn.btn-outline-success:hover,
.btn.btn-link:hover,
.btn.btn-primary:focus-visible,
.btn.btn-secondary:focus-visible,
.btn.btn-danger:focus-visible,
.btn.btn-success:focus-visible,
.btn.btn-outline-primary:focus-visible,
.btn.btn-outline-secondary:focus-visible,
.btn.btn-outline-danger:focus-visible,
.btn.btn-outline-success:focus-visible,
.btn.btn-link:focus-visible {
    background-color: var(--bs-btn-hover-bg);
    border-color: var(--bs-btn-hover-border-color);
    color: var(--bs-btn-hover-color);
}

/* Le désactivé est la moitié oubliée : c'est lui qui faisait virer le bouton
   « succès » au vert pendant que son état actif était orange. Bootstrap ajoute
   son opacité par-dessus, donc la teinte suffit. */
.btn.btn-primary:disabled,
.btn.btn-secondary:disabled,
.btn.btn-danger:disabled,
.btn.btn-success:disabled,
.btn.btn-outline-primary:disabled,
.btn.btn-outline-secondary:disabled,
.btn.btn-outline-danger:disabled,
.btn.btn-outline-success:disabled,
.btn.btn-link:disabled {
    background-color: var(--bs-btn-disabled-bg);
    border-color: var(--bs-btn-disabled-border-color);
    color: var(--bs-btn-disabled-color);
}

/* --- Les valeurs ------------------------------------------------------- */

/* Le primaire. Le fond était déjà juste (prosyjob.css le corrige depuis
   longtemps) ; ce qui manquait était la couleur du libellé au survol. */
.btn-primary {
    --bs-btn-bg: var(--pj-orange-fill);
    --bs-btn-border-color: var(--pj-orange-fill);
    --bs-btn-color: #fff;
    --bs-btn-hover-bg: var(--pj-orange-fill-hover);
    --bs-btn-hover-border-color: var(--pj-orange-fill-hover);
    --bs-btn-hover-color: #fff;
    --bs-btn-active-bg: var(--pj-orange-fill-hover);
    --bs-btn-active-border-color: var(--pj-orange-fill-hover);
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-bg: var(--pj-orange-fill);
    --bs-btn-disabled-border-color: var(--pj-orange-fill);
    --bs-btn-disabled-color: #fff;
}

/* Le secondaire plein : le navy structurel, blanc dessus — 8,34:1 en clair,
   5,12:1 en sombre où le token est relevé. */
.btn-secondary {
    --bs-btn-bg: var(--pj-navy);
    --bs-btn-border-color: var(--pj-navy);
    --bs-btn-color: #fff;
    --bs-btn-hover-bg: var(--pj-navy-dark);
    --bs-btn-hover-border-color: var(--pj-navy-dark);
    --bs-btn-hover-color: #fff;
    --bs-btn-active-bg: var(--pj-navy-dark);
    --bs-btn-active-border-color: var(--pj-navy-dark);
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-bg: var(--pj-navy);
    --bs-btn-disabled-border-color: var(--pj-navy);
    --bs-btn-disabled-color: #fff;
}

/* Le contour orange franc. */
.btn-outline-primary {
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--pj-orange);
    --bs-btn-color: var(--pj-orange-text);
    --bs-btn-hover-bg: var(--pj-orange-fill);
    --bs-btn-hover-border-color: var(--pj-orange-fill);
    --bs-btn-hover-color: #fff;
    --bs-btn-active-bg: var(--pj-orange-fill-hover);
    --bs-btn-active-border-color: var(--pj-orange-fill-hover);
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--pj-orange);
    --bs-btn-disabled-color: var(--pj-orange-text);
}

/* Le secondaire, 364 usages : une SURFACE douce, pas un contour.
   Décision du 2026-09-08. `.btn-outline-primary` porte déjà le contour orange
   franc ; donner le même traitement au secondaire rendait les deux classes
   indiscernables sur 390 usages cumulés. Les séparer par la forme — l'une est
   une surface, l'autre un trait — les garde dans la même famille chromatique
   sans jamais les confondre, et c'est déjà la logique de la puce de menu
   active (§0, `--pj-orange-soft` + `--pj-orange-text`), mesurée 4,63:1 en
   clair et 6,23:1 en sombre.

   Le survol reste dans la famille douce plutôt que de basculer sur le fond
   plein : basculer aurait reconstruit la collision avec `.btn-outline-primary`
   au moment précis où l'on compare deux boutons, le curseur sur l'un d'eux. */
.btn-outline-secondary {
    --bs-btn-bg: var(--pj-orange-soft);
    --bs-btn-border-color: var(--pj-orange-soft);
    --bs-btn-color: var(--pj-orange-text);
    --bs-btn-hover-bg: color-mix(in srgb, var(--pj-orange) 18%, var(--pj-orange-soft));
    --bs-btn-hover-border-color: color-mix(in srgb, var(--pj-orange) 18%, var(--pj-orange-soft));
    --bs-btn-hover-color: var(--pj-orange-text);
    --bs-btn-active-bg: color-mix(in srgb, var(--pj-orange) 28%, var(--pj-orange-soft));
    --bs-btn-active-border-color: color-mix(in srgb, var(--pj-orange) 28%, var(--pj-orange-soft));
    --bs-btn-active-color: var(--pj-orange-text);
    --bs-btn-disabled-bg: var(--pj-orange-soft);
    --bs-btn-disabled-border-color: var(--pj-orange-soft);
    --bs-btn-disabled-color: var(--pj-orange-text);
}

.btn-danger {
    --bs-btn-bg: var(--pj-danger-fill);
    --bs-btn-border-color: var(--pj-danger-fill);
    --bs-btn-color: #fff;
    --bs-btn-hover-bg: color-mix(in srgb, #000 12%, var(--pj-danger-fill));
    --bs-btn-hover-border-color: color-mix(in srgb, #000 12%, var(--pj-danger-fill));
    --bs-btn-hover-color: #fff;
    --bs-btn-active-bg: color-mix(in srgb, #000 20%, var(--pj-danger-fill));
    --bs-btn-active-border-color: color-mix(in srgb, #000 20%, var(--pj-danger-fill));
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-bg: var(--pj-danger-fill);
    --bs-btn-disabled-border-color: var(--pj-danger-fill);
    --bs-btn-disabled-color: #fff;
}

.btn-outline-danger {
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--pj-danger);
    --bs-btn-color: var(--pj-danger);
    --bs-btn-hover-bg: var(--pj-danger-fill);
    --bs-btn-hover-border-color: var(--pj-danger-fill);
    --bs-btn-hover-color: #fff;
    --bs-btn-active-bg: color-mix(in srgb, #000 12%, var(--pj-danger-fill));
    --bs-btn-active-border-color: color-mix(in srgb, #000 12%, var(--pj-danger-fill));
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--pj-danger);
    --bs-btn-disabled-color: var(--pj-danger);
}

.btn-success {
    --bs-btn-bg: var(--pj-success-fill);
    --bs-btn-border-color: var(--pj-success-fill);
    --bs-btn-color: #fff;
    --bs-btn-hover-bg: color-mix(in srgb, #000 12%, var(--pj-success-fill));
    --bs-btn-hover-border-color: color-mix(in srgb, #000 12%, var(--pj-success-fill));
    --bs-btn-hover-color: #fff;
    --bs-btn-active-bg: color-mix(in srgb, #000 20%, var(--pj-success-fill));
    --bs-btn-active-border-color: color-mix(in srgb, #000 20%, var(--pj-success-fill));
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-bg: var(--pj-success-fill);
    --bs-btn-disabled-border-color: var(--pj-success-fill);
    --bs-btn-disabled-color: #fff;
}

.btn-outline-success {
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--pj-success);
    --bs-btn-color: var(--pj-success);
    --bs-btn-hover-bg: var(--pj-success-fill);
    --bs-btn-hover-border-color: var(--pj-success-fill);
    --bs-btn-hover-color: #fff;
    --bs-btn-active-bg: color-mix(in srgb, #000 12%, var(--pj-success-fill));
    --bs-btn-active-border-color: color-mix(in srgb, #000 12%, var(--pj-success-fill));
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--pj-success);
    --bs-btn-disabled-color: var(--pj-success);
}

/* Le bouton-lien : la marque comme texte, jamais le bleu de Bootstrap.
   `--pj-orange-text` est le token prévu pour ça — la marque assombrie là où
   elle est LUE (E10-02). */
.btn-link {
    --bs-btn-bg: transparent;
    --bs-btn-border-color: transparent;
    --bs-btn-color: var(--pj-orange-text);
    --bs-btn-hover-bg: transparent;
    --bs-btn-hover-border-color: transparent;
    --bs-btn-hover-color: var(--pj-orange-fill-hover);
    --bs-btn-active-bg: transparent;
    --bs-btn-active-border-color: transparent;
    --bs-btn-active-color: var(--pj-orange-fill-hover);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: transparent;
    --bs-btn-disabled-color: var(--pj-muted);
}


/* --- Les variantes « danger » posées sur un bouton secondaire -----------
   Trois boutons de l'app disent « supprimer mon compte », « quitter cette
   entreprise » : ce sont des `.btn.btn-outline-secondary` auxquels une classe
   maison ajoutait le rouge. Ils vivaient dans prosyjob-components.css avec une
   seule classe, ce qui suffisait tant que le secondaire n'était peint par
   personne.

   Le temps 1 les a cassés, et c'est instructif : `.btn.btn-outline-secondary`
   (0,2,0) surclasse `.co-danger__btn` (0,1,0), donc les trois ont perdu leur
   rouge et sont devenus des boutons secondaires ordinaires — sans que rien
   n'échoue, le contraste restant excellent. Un avertissement qui cesse
   d'avertir est une régression silencieuse ; c'est le balayage des écrans
   réels qui l'a montrée, pas une relecture.

   Ils sont donc ici, à la même spécificité et plus loin dans la feuille, sur
   `--pj-danger`. Le fond doux orange reste : c'est la forme du secondaire, et
   seul le sens change. */
/* E48 ajoute les deux siennes ici d'emblee — « supprimer ce CV » et
   « retirer ma candidature » — plutot que dans la feuille des composants,
   ou elles auraient perdu leur rouge exactement de la meme facon. */
.btn.co-danger__btn,
.btn.cand-danger__btn,
.btn.cvp-action--danger,
.btn.apx-action--danger {
    --bs-btn-color: var(--pj-danger);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--pj-danger-fill);
    --bs-btn-hover-border-color: var(--pj-danger-fill);
    --bs-btn-disabled-color: var(--pj-danger);
    color: var(--pj-danger);
    border-color: var(--pj-danger);
}

.btn.co-danger__btn:hover,
.btn.cand-danger__btn:hover,
.btn.cvp-action--danger:hover,
.btn.apx-action--danger:hover,
.btn.co-danger__btn:focus-visible,
.btn.cand-danger__btn:focus-visible,
.btn.cvp-action--danger:focus-visible,
.btn.apx-action--danger:focus-visible {
    background-color: var(--pj-danger-fill);
    border-color: var(--pj-danger-fill);
    color: #fff;
}


/* ── 5bis. Les dernières familles hors système ─────────────────────────
   Le §5 a traité les neuf classes standard, qui portent 93 % des usages. Ce
   bloc-ci finit le travail : plus aucun bouton de l'app ne peint une couleur
   qui ne soit pas l'un des six rôles (principal, secondaire, danger, succès,
   lien, icône). Décision du 2026-09-08, après recensement.

   Chacune de ces règles surcharge une feuille que nous n'écrivons pas — le
   thème vendoré `prosyjob.css` — ou une classe maison plus ancienne. Aucune
   n'a besoin de `!important` : prosyjob-app.css est la dernière feuille
   chargée, donc à spécificité égale elle gagne. */

/* « Annuler » était peint comme l'action principale.
   Onze modales de la console écrivent `class="btn btn-primary cancel-btn"`, à
   côté d'un `btn btn-primary continue-btn` qui supprime ou révoque. Deux
   boutons orange identiques, l'un qui annule et l'autre qui détruit, et rien
   pour les distinguer que leur libellé. C'est la hiérarchie que le recensement
   cherchait, prise en défaut par le HTML plutôt que par le CSS.

   Corrigé ici et non dans les gabarits : c'est le même choix que dans tout le
   reste de cette passe — la classe existe, elle dit « annuler », il suffit
   qu'elle soit peinte comme telle. Elle prend le rôle secondaire.
   `.btn.cancel-btn` (0,2,0) égale `.btn.btn-primary` et vient après. */
.btn.cancel-btn {
    --bs-btn-bg: var(--pj-orange-soft);
    --bs-btn-border-color: var(--pj-orange-soft);
    --bs-btn-color: var(--pj-orange-text);
    --bs-btn-hover-bg: color-mix(in srgb, var(--pj-orange) 18%, var(--pj-orange-soft));
    --bs-btn-hover-border-color: color-mix(in srgb, var(--pj-orange) 18%, var(--pj-orange-soft));
    --bs-btn-hover-color: var(--pj-orange-text);
    --bs-btn-active-bg: color-mix(in srgb, var(--pj-orange) 28%, var(--pj-orange-soft));
    --bs-btn-active-border-color: color-mix(in srgb, var(--pj-orange) 28%, var(--pj-orange-soft));
    --bs-btn-active-color: var(--pj-orange-text);
    --bs-btn-disabled-bg: var(--pj-orange-soft);
    --bs-btn-disabled-border-color: var(--pj-orange-soft);
    --bs-btn-disabled-color: var(--pj-orange-text);
    /* `!important`, et c'est le seul de tout ce travail. `prosyjob.css` pose
       `.pj-app .btn-primary { background-color: var(--pj-orange-fill)
       !important }` : une declaration importante bat n'importe quelle
       specificite, donc aucune regle ordinaire ne peut reprendre ce fond.
       Sans lui, la mesure donnait un libelle #C04017 sur un fond #CE4416 —
       1,2:1, illisible. La feuille qui l'impose est vendoree ; le jour ou
       elle perd son `!important`, celui-ci pourra partir. */
    background-color: var(--bs-btn-bg) !important;
    border-color: var(--bs-btn-border-color) !important;
    color: var(--bs-btn-color);
}

.btn.cancel-btn:hover,
.btn.cancel-btn:focus-visible {
    background-color: var(--bs-btn-hover-bg) !important;
    border-color: var(--bs-btn-hover-border-color) !important;
    color: var(--bs-btn-hover-color);
}

/* La paire de la page d'accueil visiteur (`HomePageGuest`).

   `.pj-btn--grad` portait un dégradé bleu → violet → orange, et
   `.pj-btn--outline` un fond blanc écrit en dur que le thème sombre devait
   rattraper ailleurs. Ce sont les deux premiers boutons qu'un visiteur voit,
   et ils ne ressemblaient à aucun bouton de l'intérieur du produit.

   Le dégradé était un choix délibéré du designer, sur ses propres tokens — ce
   n'était pas une dérive. Il part quand même : la consigne est qu'aucun bouton
   ne reste hors des six rôles, et une exception, fût-elle jolie, est la
   première marche du retour au désordre. Si le dégradé doit revenir un jour,
   il revient comme un septième rôle nommé, pas comme un cas particulier. */
.pj-btn.pj-btn--grad {
    background: var(--pj-orange-fill);
    border-color: var(--pj-orange-fill);
    color: #fff;
    box-shadow: none;
}

.pj-btn.pj-btn--grad:hover,
.pj-btn.pj-btn--grad:focus-visible {
    background: var(--pj-orange-fill-hover);
    border-color: var(--pj-orange-fill-hover);
    color: #fff;
}

.pj-btn.pj-btn--outline {
    background: var(--pj-orange-soft);
    border-color: var(--pj-orange-soft);
    color: var(--pj-orange-text);
}

.pj-btn.pj-btn--outline:hover,
.pj-btn.pj-btn--outline:focus-visible {
    background: color-mix(in srgb, var(--pj-orange) 18%, var(--pj-orange-soft));
    border-color: color-mix(in srgb, var(--pj-orange) 18%, var(--pj-orange-soft));
    color: var(--pj-orange-text);
}

/* Le rôle « icône » : les ronds de la barre du haut. Le vendor les peint
   `background: #fff`, un blanc qui ne suit aucun thème — le bloc sombre plus
   haut le rattrapait déjà, ce qui prouve qu'il manquait un token. Le voici, et
   le rattrapage devient inutile plutôt que contradictoire. Le survol du vendor
   (`--pj-orange-soft` / `--pj-orange`) est conservé : c'est déjà le rôle. */
.pj-iconbtn {
    background: var(--pj-card);
    border-color: var(--pj-line);
    color: var(--pj-navy);
}

/* Le bouton de la page de bêta, et la règle générale qu'il illustre.

   `.beta__btn` est un `<a>`, et `prosyjob.css` déclare
   `.pj-app a { color: inherit }` — (0,1,1), qui bat la règle à une classe qui
   le définit dans prosyjob-components.css. Résultat mesuré sur la page
   d'accueil de la bêta privée : libellé `--pj-ink` sur fond `--pj-orange-fill`,
   soit 2,6:1, sur le premier bouton que voit un visiteur invité.

   C'est le même défaut que §5 corrige pour les neuf classes standard avec
   `.btn.btn-X`, et il vaut pour **toute famille maison rendue en `<a>` et
   définie par une seule classe**. La parade est de nommer l'élément
   (`a.beta__btn` → (0,1,1), à égalité, et cette feuille est la dernière) ou
   d'ajouter la seconde classe quand il y en a une — ce que font
   `.pj-btn.pj-btn--grad` et `.pj-btn.pj-btn--outline` juste au-dessus.

   À retenir avant d'ajouter une famille : dans cette coquille, une couleur de
   texte posée à une seule classe ne tient pas sur une ancre. */
a.beta__btn,
button.beta__btn {
    color: #fff;
}

a.beta__btn:hover,
button.beta__btn:hover,
a.beta__btn:focus-visible,
button.beta__btn:focus-visible {
    color: #fff;
}

/* Même cause, mêmes familles maison rendues en ancre : elles reprennent ici la
   couleur que leur propre règle déclare, à la spécificité qui tient. */
a.btn--primary,
button.btn--primary {
    color: #fff;
}

a.btn--ghost,
button.btn--ghost {
    color: var(--pj-ink);
}

a.cv-linkbtn,
button.cv-linkbtn {
    color: var(--pj-orange-text);
}

/* Les trois etats de charge de la zone : corrects aujourd'hui parce que
   `ZoneChargesPage` les rend en `<button>`, et faux le jour ou quelqu'un en
   fait une ancre — la mesure donne alors 2,48:1 et 2,81:1, le libelle ayant
   herite de `.pj-app a`. Deux lignes d'avance valent mieux qu'un defaut qui
   attend un refactor. */
a.zf-btn--validated,
button.zf-btn--validated,
a.zf-btn--rejected,
button.zf-btn--rejected {
    color: var(--pj-card);
}

a.zf-btn--correction,
button.zf-btn--correction {
    color: var(--pj-orange-text);
}


/* ── 5ter. Les liens d'action des écrans de compte ─────────────────────
   Mesuré le 2026-09-08 sur `/login`, `/mot-de-passe-oublie` et `/inscription`
   à la demande du PO. Les *boutons* de ces trois pages étaient corrects
   (`#CE4416` sur blanc, 4,71:1) ; leurs deux liens d'action ne l'étaient pas,
   et ils portaient deux oranges différents.

   « Mot de passe oublié ? », « Retour à la connexion », « Passer pour
   l'instant » — `.forgot-link` — sortent de prosyjob.css :
   `.account-page .forgot-link { color: var(--pj-orange) !important }`. Le bon
   token, mais la mauvaise coupe : `--pj-orange` est la valeur des bordures et
   des icônes, et le commentaire d'E10-02 au début de ce fichier dit
   précisément qu'elle mesure 3,41:1 sur blanc — ce que la mesure a confirmé au
   centième. `--pj-orange-text` existe pour le cas où la marque est LUE. La
   déclaration vendorée étant `!important`, la reprendre en demande un ; c'est
   le deuxième et dernier de cette passe.

   « Créer un compte » et « Se connecter » n'ont, eux, aucune classe : ils
   héritent de `.login-right .dont-have a { color: #E65425 }` dans le thème
   Kofejob — un SIXIÈME orange, à 3,71:1. `.account-page .dont-have a` est à
   la même spécificité (0,2,1) et arrive après, donc pas d'`!important` ici.

   Ce que ça donnait à l'écran : sur `/inscription`, « conditions générales »
   en `--pj-orange-text` (5,27:1) juste au-dessus de « Se connecter » en
   `#E65425` (3,71:1) — deux liens voisins, deux oranges, dont un illisible.

   Note pour plus tard : la même règle vendorée peint aussi
   `.account-page .text-primary`, qui n'est pas un lien. Laissé tel quel — ce
   n'est pas une cible cliquable, et son texte peut être assez grand pour que
   3:1 suffise. À mesurer avant d'y toucher plutôt qu'à supposer. */
.account-page .forgot-link,
.account-page .login-header a,
.account-page .account-signup a,
.account-page a.forgot-pass {
    color: var(--pj-orange-text) !important;
}

.account-page .dont-have a {
    color: var(--pj-orange-text);
}

/* ─────────────────────────────────────────────────────────────────────────
   Le bandeau « nouvelle version disponible » (VersionWatcher).

   Frère de tout : il est posé sur `document.body` et non dans la coquille,
   parce qu'il doit exister sur les trois piles — invitée, connectée et
   console — et qu'aucune des trois ne partage de conteneur avec les autres.

   `bottom` et non `top` : un bandeau qui pousse le haut de la page déplace ce
   que la personne était en train de lire, et cette annonce n'a aucune urgence.
   z-index 2000 pour passer au-dessus des modales du thème (1080), sans quoi
   l'annonce arriverait derrière la fenêtre ouverte au moment où elle tombe.

   Aucun hex brut : tout descend de l'échelle --pj-*, en clair comme en
   sombre — le bandeau n'a donc pas de bloc de thème à lui. */
.pj-update-banner {
    position: fixed;
    left: 50%;
    bottom: 16px;
    transform: translateX(-50%);
    z-index: 2000;
    display: flex;
    align-items: center;
    gap: 12px;
    max-width: calc(100vw - 24px);
    padding: 10px 12px 10px 16px;
    border: 1px solid var(--pj-line);
    border-radius: var(--pj-radius-pill);
    background: var(--pj-card);
    color: var(--pj-ink);
    box-shadow: var(--pj-shadow);
    font-size: 14px;
}

.pj-update-banner__text {
    flex: 1 1 auto;
    min-width: 0;
}

/* §5 : tout `<button>` nu est en pleine largeur dans la coquille connectée.
   Une barre d'actions doit reprendre la main, sinon les deux boutons se
   posent l'un sous l'autre et le bandeau devient une colonne.

   `min-height: 44px` parce que la règle de ce projet est 44, et que mesuré
   sans elle le bouton « Recharger » sortait à 27 px de haut — `btn-sm` ne
   donne aucune garantie de cible tactile, c'est la leçon que `.btn--sm` a
   déjà coûtée. Sur un public à ~90% mobile, un bouton qu'on rate est un
   bandeau qu'on ferme par erreur. */
.pj-update-banner .btn,
.pj-update-banner__close {
    width: auto;
    flex: 0 0 auto;
    min-height: 44px;
}

.pj-update-banner__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    padding: 0 6px;
    border: 0;
    background: none;
    color: var(--pj-muted);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
}

.pj-update-banner__close:hover {
    color: var(--pj-ink);
}

/* Sous 480px la pastille prend toute la largeur : à 360px, le texte et deux
   boutons sur une ligne ne tiennent pas — ~90% des visites arrivent là. */
@media (max-width: 480px) {
    .pj-update-banner {
        left: 12px;
        right: 12px;
        transform: none;
        max-width: none;
        border-radius: var(--pj-radius);
    }
}

/* ── E44-07 — La fenêtre du QR d'un code en attente ──────────────────────
   Le fond et le panneau viennent de la chrome partagée plus haut ; ce qui
   suit est le contenu propre à cette modale. */

.qrd-panel {
  display: grid;
  gap: 12px;
  justify-items: center;
  text-align: center;
  outline: none;
}

.qrd-title {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 800;
  color: var(--pj-ink);
}

.qrd-subtitle {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--pj-orange-text);
}

/* Le cadre porte la borne, pas l'image : `prosyjob.css` déclare
   `.pj-app .pj-main img { max-width: 100% }` en (0,2,1), qui bat une classe
   seule — même piège que le QR de la confirmation. */
.qrd-frame {
  display: block;
  width: 100%;
  max-width: 240px;
  padding: 12px;
  border-radius: var(--pj-radius-sm);
  background: #fff;
}

.qrd-image {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
}

.qrd-link {
  width: 100%;
  display: grid;
  gap: 6px;
  text-align: left;
}

.qrd-link__label {
  font-weight: 700;
  font-size: 0.85rem;
  color: var(--pj-ink);
}

.qrd-link .tr-input {
  display: flex;
  align-items: center;
  padding-left: 12px;
  border: 1px solid var(--pj-line);
  border-radius: var(--pj-radius-sm);
  background: var(--pj-card);
}

.qrd-link input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 44px;
  border: 0;
  background: transparent;
  box-shadow: none;
  font-size: 0.85rem;
  color: var(--pj-ink);
}

.qrd-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  width: 100%;
}

/* Échappe au `button, .btn { width: 100% }` de la feuille des composants, et
   tient les 44 px de cible tactile. */
.qrd-actions .qrd-btn {
  width: auto;
  min-height: 44px;
  padding-inline: 20px;
}

/* ── Le détail d'une opération en miles (`MilesTransactionSheet`) ──────
   Le fond et le panneau viennent de la chrome partagée plus haut. Sur
   téléphone, la fenêtre devient une feuille ancrée en bas — le pouce y est
   déjà, et la carte qu'on vient de toucher reste visible au-dessus. */

.mhs-panel {
  display: grid;
  gap: 16px;
  outline: none;
}

.mhs-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.mhs-title {
  margin: 0;
  font-size: var(--pj-fs-lg);
  font-weight: 800;
  color: var(--pj-ink);
}

.mhs-x {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--pj-radius-sm);
  background: transparent;
  color: var(--pj-muted);
  cursor: pointer;
}

.mhs-x:hover {
  background: var(--pj-card-2);
  color: var(--pj-ink);
}

.mhs-hero {
  display: grid;
  justify-items: center;
  gap: 10px;
  padding: 16px;
  border-radius: var(--pj-radius-sm);
  background: var(--pj-card-2);
}

.mhs-amount {
  font-size: var(--pj-fs-title);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.mhs-amount--credit {
  color: var(--pj-success);
}

.mhs-amount--debit {
  color: var(--pj-danger);
}

.mhs-list {
  display: grid;
  margin: 0;
}

.mhs-list > div {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--pj-line);
}

.mhs-list > div:last-child {
  border-bottom: 0;
}

.mhs-list dt {
  font-size: var(--pj-fs-sm);
  font-weight: 700;
  color: var(--pj-muted);
}

.mhs-list dd {
  margin: 0;
  font-size: var(--pj-fs-body);
  color: var(--pj-ink);
  text-align: right;
  overflow-wrap: anywhere;
}

.mhs-ref {
  font-variant-numeric: tabular-nums;
}

.mhs-actions {
  display: grid;
  gap: 10px;
}

.mhs-actions .mhs-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 44px;
}

@media (max-width: 767.98px) {
  .mhs-backdrop {
    align-items: flex-end;
    padding: 0;
  }

  .mhs-panel {
    max-width: none;
    max-height: 85vh;
    padding-bottom: calc(22px + env(safe-area-inset-bottom, 0px));
    border-radius: var(--pj-radius) var(--pj-radius) 0 0;
  }
}

/* Le logo et le badge « bêta privée en cours » sur une ligne (E50-16,
   maquette `creation_compte.png`). Il enveloppe sur téléphone plutôt que
   de rétrécir le logo, et le `justify-content` garde l'ensemble centré
   comme le reste de l'en-tête. */
.pj-auth-brandline {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 12px;
}

/* Le badge porte une marge du bas quand il est seul sous le logo ; sur
   une ligne elle décale verticalement les deux éléments. */
.pj-auth-brandline .beta__badge {
    margin: 0;
}

/* =====================================================================
   §2bis — La cible d'une case à cocher, partout (2026-09-15)
   ---------------------------------------------------------------------
   Recensement des 37 cases des gabarits, mesurées dans le vrai contexte
   de feuilles plutôt qu'estimées. Trois patrons coexistent :

     label enveloppant   `<label><input> texte</label>`   158 × 26
     label frère         `<input id><label for>`          147 × 20
     `custom_check`      le patron du thème vendeur       660 × 44  ✅

   Le thème vendeur avait raison ; ce sont les deux patrons Bootstrap qui
   tombent sous la cible tactile, à 20 et 26px de haut. Et c'est la même
   erreur que la case des CGU : `.form-check` tient bien sa hauteur, mais
   une hauteur de CONTENEUR n'est pas une cible — le vide autour du label
   ne reçoit aucun clic.

   Le label est ce qu'on agrandit, jamais la case : `for` les rend déjà
   équivalents, et une case de 44px de côté ne ressemblerait plus à une
   case.

   Non scopée : elle doit tenir sur la coquille connectée ET sur la
   console d'administration, qui sont mutuellement exclusives. Elle ne
   vise que les deux patrons Bootstrap — `custom_check` a déjà ses 44px,
   et `label.dup-name` (les doublons de comptes, une case par ligne sur
   des tableaux de plusieurs centaines) n'est ni l'un ni l'autre, donc
   reste intact. Les boutons radio partagent `.form-check` et y gagnent
   la même cible, ce qui est également souhaitable.

   Le prix est une liste de bascules plus haute — les six de la page
   Webbuilder passent de ~120 à ~264px. C'est ce que coûte une cible
   tactile sur un public à 90 % mobile, et c'est le bon prix.
   ===================================================================== */
.form-check-label,
.form-check > label {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
}

/* ---------------------------------------------------------------------
   La case « j'accepte » (2026-10-13) — conditions à l'inscription, des
   services candidat / entreprise / organisme, envoi à l'IA, attestation
   de consentement d'une liste de contacts. Neuf écrans, écrits de quatre
   façons avant ce jour : la case flottante de Bootstrap collée au « J »
   (`margin-left: -1.5em` dans un `.form-check` dont le label n'était plus
   indenté), un flex centré qui mettait la case au milieu d'une phrase de
   trois lignes, et deux tailles de case. Une seule forme désormais :

     <label class="pj-consent">
         <input type="checkbox" class="form-check-input pj-consent__box">
         <span class="pj-consent__text">…</span>
     </label>

   — ou, quand la phrase porte un lien (les CGU à l'inscription), un
   `<div class="pj-consent">` et un `<label for>` dans le texte : un lien
   à l'intérieur d'un label coche la case en partant lire les conditions.

   La case est au bord gauche des champs, 10px avant le texte, calée sur
   la première ligne ; une phrase longue revient sous le texte, jamais
   sous la case. La hauteur de ligne est en px pour que ce calage (1px)
   tienne quelle que soit la taille de texte de l'écran.
   `tools/check-consent.mjs` refuse une case de consentement hors de ce
   patron.
   --------------------------------------------------------------------- */
.pj-consent {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: 10px;
    align-items: start;
    width: 100%;
    min-height: 44px;
    margin: 0;
    padding: 11px 0;
    line-height: 22px;
    font-weight: 400;
    text-align: start;
    cursor: pointer;
}

/* (0,2,0) and later than `.pj-app .form-check-input` (22px): same weight,
   this one wins on order. `float` and the negative margin are Bootstrap's;
   a grid item ignores the first, the second must go. */
.pj-consent > .pj-consent__box {
    float: none;
    flex: none;
    width: 20px;
    height: 20px;
    min-height: 0;
    margin: 1px 0 0;
    padding: 0;
    cursor: pointer;
}

/* Bootstrap draws the empty box in `--bs-border-color` (#dee2e6), 1,3:1 on a
   white card — a box one has to look for, on the one control of the form that
   says yes. `--pj-muted` holds 4,9:1, and follows the dark theme. */
.pj-consent > .pj-consent__box:not(:checked) {
    border-color: var(--pj-muted);
}

.pj-consent > .pj-consent__text {
    min-width: 0;
}

/* The inline `<label for>` of the link variant: vertical padding on an inline
   box is hit-tested without moving the line, so the words keep a 44px target
   without the row growing. */
.pj-consent__text > label {
    display: inline;
    margin: 0;
    padding-block: 14px;
    font: inherit;
    cursor: pointer;
}

/* =====================================================================
   §2ter — Le titre des six écrans d'authentification (E55)
   ---------------------------------------------------------------------
   Le thème vendeur le sert à 18px, pour une baseline à 16 : les deux
   lignes pèsent donc presque pareil, et le titre ne titre plus rien.
   Toutes les maquettes du client le montrent nettement dominant —
   `se_connecter.png`, `creation_compte.png`, les deux étapes de
   `creation_compte_etape*.png` — dans un rapport d'environ 1,6.

   Écrit ici et non dans la feuille des composants : c'est un réglage
   sur une classe du thème, et celle-ci charge après l'autre.

   Portée aux écrans d'authentification (`body.account-page`), qui sont
   les seuls à porter cette maquette. Les `h3` du reste du site ne
   bougent pas.
   ===================================================================== */
.account-page .login-header h3 {
    font-size: 26px;
    line-height: 1.25;
    font-weight: 700;
    margin-top: 14px;
}

@media (max-width: 575px) {
    .account-page .login-header h3 {
        font-size: 22px;
    }
}

/* =====================================================================
   §12 — La pastille de portée (ScopeBadge)
   ---------------------------------------------------------------------
   « Où cette chose est-elle visible ? », sur les trois listes qui gèrent
   du contenu publié : publications (Boutique), offres d'emploi,
   formations.

   Écrite ici, non dans la feuille des composants, parce qu'elle est
   partagée par trois familles CSS (`ml-`, `jo-`) : une règle rangée dans
   l'une d'elles serait une règle que les deux autres copieraient.

   Le ton est informatif et non neutre, à dessein : posée à côté de la
   pastille de statut (`.jo-status`, gris), elle doit se lire comme un
   second fait et non comme une variante du premier. Elle ne paraît que
   sur une restriction — voir le composant — donc sa présence est déjà
   l'information.
   ===================================================================== */
.pj-scope {
    display: inline-flex;
    align-items: center;
    padding: 3px 10px;
    border-radius: 999px;
    background: var(--pj-info-soft);
    color: var(--pj-info-ink);
    font-size: 0.76rem;
    font-weight: 700;
    white-space: nowrap;
}

/* Les deux puces de l'en-tête d'une carte « mes offres » / « mon catalogue » :
   le statut, puis la portée quand elle est restreinte.

   `flex-wrap` parce qu'à 360px les deux ne tiennent pas côte à côte —
   `.jo-mine-card__head` est déjà en `space-between`, donc sans cela la
   seconde puce pousserait la colonne du titre. `align-items: flex-end`
   les aligne à droite, du côté où l'en-tête les envoie. */
.jo-mine-card__chips {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: flex-end;
    gap: 6px;
}

/* =====================================================================
   §13 — La barre de sélection (SelectionBar)
   ---------------------------------------------------------------------
   Le traitement par lot des trois listes de gestion. Ici et non dans la
   feuille des composants, pour la même raison que §12 : trois familles
   CSS s'en servent.

   La barre est **collante en bas**. ~90 % des utilisateurs sont sur
   téléphone : une barre d'actions en haut d'une liste de vingt cartes
   est hors du pouce et hors de l'écran dès le premier défilement. Elle
   ajoute l'encoche à son propre `padding` plutôt que de compter sur
   celui de `:root`, sinon ses boutons passent sous la barre système.
   ===================================================================== */
.pj-select__idle {
    display: flex;
    justify-content: flex-end;
}

.pj-select__idle .btn {
    width: auto;
}

/* La barre est FIXE, et non collante.

   `position: sticky` ne peut pas fonctionner ici, et c'est structurel :
   `#app` porte `overflow-y: auto`, ce qui en fait un conteneur de
   defilement — mais son contenu tient dedans, donc il ne defile jamais.
   Un element colle se cale sur son conteneur de defilement le plus proche,
   qu'il defile ou non : celui-ci etant immobile, rien ne colle. C'est
   `body` qui defile, un cran plus haut. Mesure dans un cadre de 360px : la
   barre restait a 1158px d'une fenetre haute de 716, section en grille
   comme en bloc.

   Donc fixe, et pleine largeur a toutes les tailles. Une seconde geometrie
   pour le grand ecran serait une seconde chose a maintenir, et la colonne
   de contenu n'est pas centree (barre laterale a gauche, rail a droite) :
   elle ne se retrouve pas par un calc. Aucun ancetre ne porte de
   `transform`, `filter` ni `contain` — verifie, car l'un d'eux ferait de ce
   `fixed` un `absolute` sans rien dire.

   L'hote reserve la place en fin de liste pour que la derniere carte ne
   reste pas sous la barre. Cette hauteur est MESUREE et posee par le
   composant, non ecrite ici : la barre passe de une a trois rangees selon
   la largeur et le nombre d'actions offertes, donc toute valeur fixe
   serait fausse a une taille ou a une autre. */

.pj-select {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 30;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 12px;
    padding: 12px 14px;
    padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--pj-line);
    background: var(--pj-card);
    box-shadow: 0 -6px 18px rgb(0 0 0 / 12%);
}

.pj-select__count {
    font-weight: 700;
    color: var(--pj-ink);
}

.pj-select__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-left: auto;
}

/* La regle `button, .btn { width: 100% }` de la coquille connectee ferait
   de chaque action une ligne entiere. */
.pj-select__actions .btn {
    width: auto;
}

.pj-select__progress,
.pj-select__done,
.pj-select__failures {
    flex-basis: 100%;
    margin: 0;
    font-size: 0.86rem;
}

.pj-select__failures {
    color: var(--pj-danger);
}

.pj-select__failures ul {
    margin: 4px 0 0;
    padding-left: 18px;
}

/* La case d'une carte : cible de 44px mesuree, pas supposee — la case elle-meme
   n'en fait que 16, c'est le label qui porte la surface. */
.pj-select__box {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    margin: 0;
    cursor: pointer;
}

.pj-select__box input {
    width: 18px;
    height: 18px;
}

/* La case ajoute un TROISIEME enfant a un en-tete en `space-between`, qui
   jusque-la en avait deux : le bloc du titre se retrouvait pousse au centre,
   a 325px de sa place (mesure, et non estime). Il reprend l'espace libre.

   Ecrit sur `.pj-select__box + div` et non sur l'en-tete : la regle ne vaut
   donc que quand la case est la, et rien ne bouge hors du mode selection.
   `min-width: 0` pour qu'un titre long retrecisse au lieu de deborder — un
   element flex refuse de passer sous son contenu minimal sans cela. */
.pj-select__box + div {
    flex: 1 1 auto;
    min-width: 0;
}

/* L'ecart d'horloge entre le poste de l'administrateur et le serveur, sous le
   champ « en vigueur a partir de ». En ton d'alerte et non discret : c'est un
   avertissement dont l'absence a laisse passer neuf prix sans effet. */
.rf-clock-warning {
    color: var(--pj-warning-ink);
    font-weight: 600;
}

/* « Mettre à la une », dans l'encadré des options de publication. */
.jo-feature {
    margin: 0;
}

.jo-feature .btn {
    width: auto;
}

/* Déjà à la une : un fait, pas une action — le ton informatif le dit avant
   qu'on cherche le bouton qui n'y est pas. */
.jo-feature--active {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    border-radius: 999px;
    background: var(--pj-info-soft);
    color: var(--pj-info-ink);
    font-weight: 600;
    font-size: 0.86rem;
}

/* Le formulaire de formation en onglets (retour PO : trop long a remplir sur
   telephone). Les panneaux gardent le rythme de la carte qui les porte. */
.jo-editor__tabs {
    overflow-x: auto;
}

.jo-editor__panel {
    display: grid;
    gap: 16px;
}


/* La décision précédente, en tête du dialogue de vérification d'une entreprise
   (PO, 2026-09-21 : « où est-ce qu'un autre administrateur peut voir le motif
   de validation ou de refus ? »). La réponse était : nulle part. Le motif était
   stocké mais rendu par aucun écran, et l'auteur d'une décision ne sortait même
   pas de la base — un second administrateur ouvrait ce dialogue devant un
   verdict sans signature, et redécidait à l'aveugle une fiche qu'un collègue
   venait de trancher.

   Posé sur `--pj-bg` plutôt que sur la carte : ce bloc se lit, il ne se
   remplit pas, et le contraste avec les champs du formulaire en dessous dit
   cette différence sans un mot de plus. */
.ac-previous {
  display: grid;
  gap: 4px;
  margin-bottom: 16px;
  padding: 12px 14px;
  border: 1px solid var(--pj-line);
  border-radius: var(--pj-radius-sm);
  background: var(--pj-bg);
  color: var(--pj-muted);
  font-size: .92rem;
  line-height: 1.45;
}

.ac-previous__state strong {
  color: var(--pj-ink);
}

/* Le motif est le seul texte ici qui vienne d'un humain : il se détache du
   reste, qui n'est que de l'état. */
.ac-previous__motif {
  margin-top: 4px;
  padding-top: 8px;
  border-top: 1px solid var(--pj-line);
  color: var(--pj-ink);
}

.ac-previous__motif span {
  color: var(--pj-muted);
}

/* « En attente du certificat » : le bandeau du gel de l'Académie
   (PO, 2026-09-21). Tant que la formation n'existe pas, aucun parrain n'est
   certifié et aucun Mile n'est versé — mais le lien continue de rattacher les
   filleuls, et c'est ce que le parrain doit lire en premier, faute de quoi il
   conclura d'un solde immobile que son lien est mort.

   Sur `--pj-warning-soft` : une attente, pas une faute. Le rouge dirait que
   quelque chose a mal tourné, et rien n'a mal tourné. */
/* ── Activite de la periode (document « reseau de distribution », 2026-09-23,
   maquette du 2026-09-29) ───────────────────────────────────────────────────
   Titre et verdict sur une ligne, les dates dessous ; les actions mesurees en
   lignes a icone ronde et compteur ; un encadre bleu pour la regle ; les
   actions pas encore mesurees en liste a puces orange. Sur telephone le
   verdict passe sous les dates (grid-template-areas). Le verdict inactif reste
   pale et jamais rouge : « pas encore », pas « vous avez mal fait ». */
.cd-activity {
  margin-bottom: 16px;
  display: grid;
  gap: 16px;
}

.cd-activity__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "title state"
    "dates dates";
  align-items: start;
  gap: 8px 16px;
}

.cd-activity__title {
  grid-area: title;
  margin: 0;
  font-size: var(--pj-fs-lg);
  font-weight: 800;
  line-height: 1.25;
  color: var(--pj-ink);
}

.cd-activity__dates { grid-area: dates; display: grid; gap: 2px; }

.cd-activity__period {
  margin: 0;
  color: var(--pj-muted);
  font-size: var(--pj-fs-body);
}

.cd-activity__period i { margin-right: 6px; color: var(--pj-navy); }

.cd-activity__closes {
  margin: 0;
  color: var(--pj-muted);
  font-size: var(--pj-fs-sm);
}

.cd-activity__state {
  grid-area: state;
  justify-self: end;
  padding: 6px 14px;
  border-radius: var(--pj-radius-pill);
  background: var(--pj-info-soft);
  color: var(--pj-info-ink);
  font-size: var(--pj-fs-sm);
  font-weight: 600;
  white-space: nowrap;
}

.cd-activity__state.is-active {
  background: var(--pj-success-soft);
  color: var(--pj-success);
}

.cd-activity__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 8px;
}

.cd-activity__row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  border: 1px solid var(--pj-line);
  border-radius: var(--pj-radius-sm);
  color: var(--pj-ink);
  font-size: var(--pj-fs-body);
}

.cd-activity__icon {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  font-size: 1rem;
  background: var(--pj-neutral-soft);
  color: var(--pj-neutral-ink);
}

.cd-activity__row--green .cd-activity__icon { background: var(--pj-success-soft); color: var(--pj-success); }
.cd-activity__row--blue .cd-activity__icon { background: var(--pj-info-soft); color: var(--pj-info-ink); }
.cd-activity__row--orange .cd-activity__icon { background: var(--pj-orange-soft); color: var(--pj-orange-text); }

/* `min-width: 0` : un libelle long doit pouvoir se reduire, sans quoi il
   pousse le compteur hors de la ligne — un element flex refuse de passer sous
   sa largeur de contenu tant qu'on ne l'y autorise pas. */
.cd-activity__label {
  flex: 1 1 auto;
  min-width: 0;
}

.cd-activity__count {
  flex: none;
  min-width: 44px;
  padding: 4px 10px;
  border-radius: var(--pj-radius-sm);
  background: var(--pj-bg);
  font-size: var(--pj-fs-card);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* Une action faite colore son compteur : c'est le chiffre qui a change. */
.cd-activity__row.is-done { border-color: var(--pj-success); }
.cd-activity__row.is-done .cd-activity__count { background: var(--pj-success-soft); color: var(--pj-success); }

.cd-activity__info {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 14px;
  border-radius: var(--pj-radius-sm);
  background: var(--pj-info-soft);
}

.cd-activity__info-icon { flex: none; color: var(--pj-info-ink); font-size: 1.3rem; line-height: 1; }

.cd-activity__hint {
  margin: 0;
  color: var(--pj-info-ink);
  font-size: var(--pj-fs-body);
  font-weight: 700;
}

.cd-activity__hint--soft {
  margin: 2px 0 0;
  color: var(--pj-ink);
  font-size: var(--pj-fs-sm);
}

.cd-activity__pending {
  margin: 0;
  padding: 2px 14px;
  list-style: none;
  border: 1px solid color-mix(in srgb, var(--pj-orange) 18%, transparent);
  border-radius: var(--pj-radius-sm);
  background: color-mix(in srgb, var(--pj-orange-soft) 55%, var(--pj-card));
}

.cd-activity__pending li {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 0 9px 2px;
  color: var(--pj-ink);
  font-size: var(--pj-fs-body);
}

.cd-activity__pending li + li { border-top: 1px solid color-mix(in srgb, var(--pj-orange) 14%, transparent); }

.cd-activity__pending li::before {
  content: "";
  flex: none;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--pj-orange);
}

/* Same sizes on a phone: only the verdict moves, under the dates. */
@media (max-width: 767px) {
  .cd-activity { gap: 12px; }
  .cd-activity__head {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "title" "dates" "state";
  }
  .cd-activity__state { justify-self: start; margin-top: 4px; }
}

.cd-frozen {
  display: grid;
  gap: 8px;
  margin-bottom: 16px;
  padding: 14px 16px;
  border: 1px solid var(--pj-warning-ink);
  border-radius: var(--pj-radius-sm);
  background: var(--pj-warning-soft);
  color: var(--pj-warning-ink);
}

.cd-frozen__badge {
  justify-self: start;
  padding: 3px 10px;
  border-radius: var(--pj-radius-pill);
  background: var(--pj-warning-ink);
  color: var(--pj-warning-soft);
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .02em;
}

.cd-frozen__text {
  margin: 0;
  font-size: .92rem;
  line-height: 1.5;
}

/* Le lien vers l'Académie. Il est le seul geste du bandeau, donc il se voit :
   un bandeau qui annonce une démarche et la range dans un lien de la couleur
   du texte est un bandeau qu'on lit et qu'on quitte.

   LA CLASSE EST DOUBLÉE, et ce n'est pas une coquetterie. `prosyjob.css`
   porte `.pj-app a { color: inherit }`, qui pèse (0,1,1) et bat donc toute
   règle à classe unique (0,1,0) : ce lien perdait sa couleur de texte et
   héritait de celle du bandeau — c'est-à-dire, ici, exactement la couleur de
   son propre fond. Brun sur brun, invisible, et rien n'échouait nulle part.
   Signalé par le PO le 2026-09-24, mesuré au navigateur.

   Doubler la classe monte à (0,2,0) sans changer d'un élément ce que la
   règle atteint — préférable à `a.cd-frozen__cta`, qui supposerait que
   c'est toujours un lien, et à une seconde règle, qui redirait la couleur
   dans un deuxième endroit à tenir en phase.

   44px de haut, comme toute cible tactile ici — ce bandeau est lu sur un
   téléphone dans neuf cas sur dix. */
.cd-frozen__cta.cd-frozen__cta {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 16px;
  border-radius: var(--pj-radius-pill);
  background: var(--pj-warning-ink);
  color: var(--pj-warning-soft);
  font-weight: 600;
  font-size: .92rem;
  text-decoration: none;
}

.cd-frozen__cta.cd-frozen__cta:hover {
  opacity: .9;
  color: var(--pj-warning-soft);
}

/* Les groupes d'options d'une offre (PO, 2026-09-21 : « l'interface des options
   est vraiment brouillonne »).

   Six contrôles à plat mélangeaient trois décisions de nature différente : ce
   QU'EST l'annonce — « appel d'offre » change le sens du champ salaire —, OÙ
   elle se montre, et COMMENT les candidatures reviennent. Les deux options de
   la publication restreinte rendaient le désordre intenable : `afficher_minisite`
   et `listee` disent tous deux « où est-ce montré ».

   Un `fieldset` plutôt que trois `<div>` titrés : c'est l'élément que les
   lecteurs d'écran annoncent comme un groupe, et sa légende devient le contexte
   de chaque case qu'il contient. La grammaire du document fait ici le travail
   que des classes ne feraient qu'imiter. */
.jo-optgroup {
  margin: 0 0 24px;
  padding: 16px 18px;
  border: 1px solid var(--pj-line);
  border-radius: var(--pj-radius-sm);
  background: var(--pj-card);
}

.jo-optgroup:last-child {
  margin-bottom: 0;
}

/* `float: none` et `width: auto` : Bootstrap met une legend en pleine largeur
   avec un float, ce qui la décollerait de son cadre. */
.jo-optgroup__title {
  float: none;
  width: auto;
  margin: 0;
  padding: 0 6px;
  color: var(--pj-ink);
  font-size: 1rem;
  font-weight: 600;
}

.jo-optgroup__lead {
  margin: 2px 0 14px;
  color: var(--pj-muted);
  font-size: .88rem;
  line-height: 1.45;
}

/* `.pj-app .form-check` est une RANGEE (display:flex, 44px de haut) : c est la
   regle de cible tactile. Elle met donc le libelle et son texte d aide cote a
   cote, et un libelle de trois mots s enroule sur trois lignes pendant que
   l aide occupe la moitie droite. Ici on rend la ligne a la verticale et on
   deplace les 44px sur le LIBELLE, qui est ce qu on vise du doigt. Mesure
   plutot que vu a l oeil : c est getBoundingClientRect qui l a montre. */
.jo-optgroup .form-check {
  display: block;
  min-height: 0;
}

.jo-optgroup .form-check > label {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  margin: 0;
  font-weight: 500;
}

/* Alignes sous le TEXTE du libelle et non sous la case : la case fait 22px,
   plus les 10px de gouttiere. */
.jo-optgroup .form-check > small,
.jo-optgroup .form-check > .invalid-feedback {
  margin-left: 32px;
  line-height: 1.45;
}

/* Un bouton nu fait 100% de large dans la coquille connectee (regle de
   `button, .btn` en tete de prosyjob-components.css). Une action dans un
   groupe d options est un bouton, pas une barre. */
.jo-optgroup__action {
  width: auto;
  justify-self: start;
}

.jo-optgroup .form-group + .form-group {
  margin-top: 12px;
}

/* Une case désactivée doit se voir désactivée : sans cela, « sur invitation »
   cochée et verrouillée passe pour un choix qu'on vient de faire. */
.jo-optgroup input[disabled] + span,
.jo-optgroup input[disabled] {
  opacity: .65;
  cursor: not-allowed;
}

/* Le dialogue des invitations d'une offre restreinte (PO, 2026-09-21). */
.jo-invite__notice {
  margin: 0 0 16px;
  padding: 12px 14px;
  border: 1px solid var(--pj-warning-ink);
  border-radius: var(--pj-radius-sm);
  background: var(--pj-warning-soft);
  color: var(--pj-warning-ink);
  font-size: .88rem;
  line-height: 1.5;
}

.jo-invite__notice p {
  margin: 0;
}

/* L'insuffisance de solde se détache de l'avertissement de coût : le premier
   informe, le second bloque. */
.jo-invite__warn {
  margin-top: 8px !important;
  font-weight: 600;
}

.jo-invite__count {
  margin: 18px 0 8px;
  color: var(--pj-muted);
  font-size: .86rem;
}

.jo-invite__items {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 8px;
}

.jo-invite__item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border: 1px solid var(--pj-line);
  border-radius: var(--pj-radius-sm);
  background: var(--pj-bg);
}

/* L'adresse prend la place restante ; elle est masquée côté serveur, donc
   courte — mais un domaine long ne doit pas pousser l'état hors du cadre. */
.jo-invite__who {
  flex: 1 1 10ch;
  min-width: 0;
  overflow-wrap: anywhere;
  font-size: .9rem;
}

.jo-invite__state {
  padding: 2px 10px;
  border-radius: var(--pj-radius-pill);
  background: color-mix(in srgb, var(--pj-muted) 14%, transparent);
  color: var(--pj-muted);
  font-size: .78rem;
  font-weight: 600;
  white-space: nowrap;
}

/* Trois états seulement se distinguent : celui qui attend, celui qui a abouti,
   et ceux qui sont clos. Colorer les quatre ferait un arc-en-ciel où l'on ne
   lirait plus rien. */
.jo-invite__state--accepted {
  background: color-mix(in srgb, var(--pj-success) 16%, transparent);
  color: var(--pj-success);
}

.jo-invite__state--pending {
  background: var(--pj-warning-soft);
  color: var(--pj-warning-ink);
}

.jo-invite__item .btn {
  width: auto;
}


/* =====================================================================
   §14 — La coquille de l'Académie (2026-09-23)

   L'Académie est un site à part : `academy.prosyjob.com` en production, le
   préfixe `/academy` en développement. Ce qu'elle retire est le sujet —
   barre latérale, rail de raccourcis, bandeau d'actualités, les deux
   emplacements publicitaires — parce qu'on y passe des examens.

   Elle garde en revanche la MÊME pile de styles que la coquille connectée :
   c'est le même produit, vu par la même personne. Un troisième thème lui
   ferait croire qu'elle a changé de société.
   ===================================================================== */

/* Une seule colonne, centrée, et un peu plus étroite que le site : une ligne
   de texte de 1200px se relit mal, et ici on lit des consignes d'examen. */
.pj-shell--academy {
  display: block;
  max-width: 900px;
}

/* Le sceau de certification devant le logo. Il ne remplace pas le logo — on
   n'est pas sorti de la maison — il dit lequel des deux sites on regarde. */
.pj-brand--academy {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
}

.pj-brand__seal {
  color: var(--pj-orange);
  font-size: 22px;
  line-height: 1;
}

/* LE MOT, EN ATTENDANT LE LOGO (2026-10-13).

   L'Academie recevra son identite propre, en preparation du passage a
   Prosyweb. Son logo n'est pas encore dessine, et celui de Prosyjob n'y a
   plus sa place : un mot tient la marque d'ici la.

   Il porte le jeton d'encre et non une couleur ecrite. Le logo qu'il remplace
   etait repeint en mode sombre par un filtre — `brightness(0) invert(1)...` —
   faute de variante claire fournie, ce que la feuille note elle-meme comme une
   lacune connue. Le mot, lui, suit le theme comme tout le reste. C'est
   accessoirement la premiere fois que la marque de cette barre est juste dans
   les deux themes.

   Pas de `pj-brand__space` ici : ce libelle secondaire disparaissait sous
   640px, ou la barre n'a plus la place de tout dire. Le reutiliser aurait fait
   disparaitre la marque entiere sur telephone, puisqu'elle n'est plus faite
   que de ce mot. */
.pj-brand__mot {
  color: var(--pj-ink);
  font-weight: 700;
  font-size: var(--pj-fs-lg);
  letter-spacing: 0.01em;
}

/* Pousse tout ce qui suit vers la droite de la barre. */
.pj-topbar--academy .pj-topbar__spacer {
  flex: 1 1 auto;
}

/* La seule sortie, et elle se lit. Un examen se quitte par un geste décidé,
   pas par un pictogramme qu'on effleure. */
.pj-academy-exit {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--pj-muted);
  text-decoration: none;
  font-size: 14px;
  padding: 6px 10px;
  border-radius: var(--pj-radius-pill);
}

.pj-academy-exit:hover {
  color: var(--pj-ink);
  background: var(--pj-card-2);
}

.pj-academy-learner {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.pj-academy-learner__avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  object-fit: cover;
}

.pj-academy-learner__name {
  font-weight: 600;
  font-size: 14px;
  color: var(--pj-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 160px;
}

/* ── LA MARQUE DE L'ACADÉMIE, EN THÈME SOMBRE ──────────────────────────────

   (0,2,1) : elle bat le bloc de marque — déclaré plus haut, avant les thèmes —
   ET le bloc de thème sombre. C'est la place que le commentaire de la marque
   annonce, et la seule qui donne la bonne priorité : marque, puis thème, puis
   marque-dans-un-thème.

   ## Elle ne change RIEN non plus pour l'instant

   Les valeurs sont celles que le thème sombre de Prosyjob donne aujourd'hui,
   relevées dans les blocs ci-dessus. Le bloc existe pour que l'Académie
   reçoive sa palette sombre en même temps que sa palette claire, et non six
   mois plus tard quand quelqu'un s'apercevra que la marque s'évapore la nuit.

   ## Pourquoi une palette sombre ne se déduit pas de la palette claire

   On est tenté de croire qu'il suffit d'éclaircir. L'orange de la maison le
   dit assez : #EF5A28 devient #ff6b3d, c'est-à-dire PLUS clair et plus saturé,
   parce qu'une couleur perd en lisibilité ce que le fond perd en luminosité.
   Et `--pj-orange-soft`, lui, s'inverse franchement — #FEEDE5 le jour,
   #3a1f14 la nuit : un fond tendre reste tendre, mais du côté sombre.

   Deux valeurs ne sont pas redéclarées, et ce n'est pas un oubli :
   `--pj-orange-fill` et `--pj-orange-fill-hover` servent de fond à du texte
   blanc, et un aplat qui porte déjà du blanc n'a pas besoin de s'éclaircir. Si
   la palette de l'Académie les change, c'est ici qu'elles reviendront. */
:root[data-theme="dark"][data-brand="academy"] {
    --pj-orange: #ff6b3d;
    --pj-orange-dark: #ff8355;
    --pj-orange-soft: #3a1f14;
    --pj-orange-text: #ff8355;

    --pj-navy: #4e7099;
    --pj-navy-dark: #3d5a7d;

    /* Par indirection, comme le thème : le bleu suit `--pj-blue`, qui est déjà
       la valeur sombre. Recopier la couleur resolue ici la figerait, et elle
       cesserait de suivre le jour où le thème la retouche. */
    --color-primary: var(--pj-blue);
    --color-primary-dark: var(--pj-navy);
}

/* ── LA FRISE DE L'ACADÉMIE ────────────────────────────────────────────────

   Des motifs d'école — toque, cahier, crayon, livres, règle, tableau, compas —
   posés au pied de page, coupés par le bas. On doit les reconnaître sans
   qu'ils s'imposent : c'est une ambiance, pas une illustration.

   ## Un MASQUE, et pas une image de fond

   `mask-image` plutôt que `background-image`, pour que la couleur vienne du
   jeton peint dessous. La frise suit donc le thème clair/sombre ET la marque
   de l'Académie sans qu'on la redessine — c'est tout l'intérêt du bloc de
   marque posé plus haut. Une image coloriée aurait figé un orange qui cessera
   d'être celui de l'Académie le jour où elle recevra sa palette.

   ## Au PIED, et seulement là

   L'Académie est l'endroit où l'on passe un examen, et sa coquille retire
   déjà le rail, le bandeau d'actualités et les deux emplacements
   publicitaires — « une bannière à côté d'un chronomètre est une invitation à
   perdre sa place ». Une frise derrière le texte d'une leçon serait la même
   faute en plus discret. Elle vit donc sous le contenu, dans le pied, qu'on
   n'atteint qu'en ayant fini de lire.

   `.layout-academy` et non `.pj-footer` seul : le pied est partagé par les
   trois coquilles `pj`, et les deux autres ne sont pas une école.

   ## 14 %, mesuré et pas choisi au hasard

   En dessous de 10 % la frise disparaît sur un écran de portable en plein
   jour ; au-dessus de 20 % elle devient un dessin, et le pied de page cesse
   d'être un pied de page. Le trait de 2 du SVG va avec : plus fin il
   s'évapore à cette opacité, plus épais il se remarque. */
.layout-academy .pj-footer {
    position: relative;
    /* De la place SOUS le texte pour que la frise ne passe pas derrière lui :
       elle doit se lire comme un fond, et un fond derrière du texte à 14 %
       reste un fond jusqu'au jour où le texte passe en gris clair. */
    padding-bottom: 76px;
}

.layout-academy .pj-footer::before {
    content: "";
    position: absolute;
    inset: auto 0 0 0;
    height: 64px;
    background-color: var(--pj-orange);
    opacity: .14;
    /* Le pied ne capte rien : la frise est décorative et ne doit intercepter
       ni clic ni survol des liens qu'elle borde. */
    pointer-events: none;
    -webkit-mask-image: url("/assets/brand/academy-frise.svg");
    mask-image: url("/assets/brand/academy-frise.svg");
    -webkit-mask-repeat: repeat-x;
    mask-repeat: repeat-x;
    -webkit-mask-position: left bottom;
    mask-position: left bottom;
}

/* Sous 640px, la frise se répète sur une largeur qui ne montre plus qu'un
   motif et demi : elle se lit alors comme un bord mal coupé plutôt que comme
   une frise. On la retire, et le pied redevient ce qu'il était. */
@media (max-width: 640px) {
    .layout-academy .pj-footer { padding-bottom: 30px; }
    .layout-academy .pj-footer::before { display: none; }
}

/* LES TITRES REVIENNENT AUX JETONS.

   Seule exception relevée le 2026-10-13 : sur les écrans de l'Académie, les
   titres étaient peints par la feuille vendue — `h1, h2, h3, h4, h5, h6 {
   color: #1B1B1B }` — et n'écoutaient donc aucun jeton. Tout le reste suivait
   déjà.

   Un gris presque identique à `--pj-ink`, invisible tant qu'il n'y a qu'une
   marque, et un titre resté noir au milieu d'une palette changée dès qu'il y
   en a deux. La règle est portée par la coquille de l'Académie, qui est le
   seul endroit où la question se pose aujourd'hui. */
.layout-academy h1,
.layout-academy h2,
.layout-academy h3,
.layout-academy h4,
.layout-academy h5,
.layout-academy h6 {
    color: var(--pj-ink);
}

/* Sous 640px la barre n'a plus la place de tout dire. Ce qui part est le
   texte, jamais la sortie elle-même : une personne qui ne trouve pas comment
   quitter un examen est une personne piégée.

   `pj-brand__space` figurait ici. Il a disparu avec le logo : la marque n'est
   plus faite que du mot, et le masquer sous 640px l'aurait effacée en entier
   sur téléphone. C'est pourquoi le mot porte sa propre classe. */
@media (max-width: 640px) {
  .pj-academy-exit__text,
  .pj-academy-learner__name {
    display: none;
  }
}

/* ── La visionneuse d'images (ImageLightbox), 2026-09-29 ──────────────────
   Ici et non dans la feuille des composants : le composant est du framework
   et peut s'ouvrir sur les trois piles. Le fond est sombre dans les deux
   thèmes ; aucun jeton ne reste clair en thème sombre, d'où deux variables
   locales tirées de la charte et inversées plus bas — pas de couleur écrite
   en dur. */
.pj-lightbox {
    --lb-bg: var(--pj-ink);
    --lb-fg: var(--pj-card);
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: grid;
    place-items: center;
    padding: 56px 12px;
    /* Opaque : à 94 %, le texte de la page restait lisible derrière l'image. */
    background: var(--lb-bg);
    cursor: zoom-out;
}

:root[data-theme="dark"] .pj-lightbox {
    --lb-bg: var(--pj-bg);
    --lb-fg: var(--pj-ink);
}

body.pj-lightbox-open {
    overflow: hidden;
}

.pj-lightbox__img {
    display: block;
    max-width: 100%;
    max-height: calc(100vh - 112px);
    max-height: calc(100dvh - 112px);
    object-fit: contain;
    border-radius: var(--pj-radius-sm);
    cursor: default;
    user-select: none;
}

.pj-lightbox .pj-lightbox__close,
.pj-lightbox .pj-lightbox__nav {
    position: absolute;
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: color-mix(in srgb, var(--lb-fg) 16%, transparent);
    color: var(--lb-fg);
    font-size: 1.25rem;
    cursor: pointer;
}

.pj-lightbox .pj-lightbox__close:hover,
.pj-lightbox .pj-lightbox__nav:hover {
    background: color-mix(in srgb, var(--lb-fg) 28%, transparent);
}

.pj-lightbox .pj-lightbox__close:focus-visible,
.pj-lightbox .pj-lightbox__nav:focus-visible {
    outline: 2px solid var(--pj-orange);
    outline-offset: 2px;
}

.pj-lightbox .pj-lightbox__close {
    top: 8px;
    right: 8px;
}

.pj-lightbox .pj-lightbox__nav {
    top: 50%;
    transform: translateY(-50%);
}

.pj-lightbox .pj-lightbox__nav--prev { left: 8px; }
.pj-lightbox .pj-lightbox__nav--next { right: 8px; }

.pj-lightbox__count {
    position: absolute;
    top: 18px;
    left: 50%;
    margin: 0;
    transform: translateX(-50%);
    color: var(--lb-fg);
    font-weight: 600;
}

@media (min-width: 768px) {
    .pj-lightbox {
        padding: 64px 88px;
    }

    .pj-lightbox__img {
        max-height: calc(100vh - 128px);
        max-height: calc(100dvh - 128px);
    }

    .pj-lightbox .pj-lightbox__nav--prev { left: 20px; }
    .pj-lightbox .pj-lightbox__nav--next { right: 20px; }
    .pj-lightbox .pj-lightbox__close { top: 14px; right: 20px; }
}

/* =====================================================================
   §15 — L'aide contextuelle (2026-09-28)

   Une feuille qui glisse du bas, ouverte par un bouton flottant, sur
   l'écran où l'on est. Le public visé est à 80 % au téléphone, à 90 % peu
   à l'aise avec la technologie et familier des réseaux sociaux : c'est de
   là que vient la forme — la feuille glissante est un geste déjà connu, et
   elle laisse derrière elle l'écran qu'on ne comprend pas, plutôt que
   d'obliger à le quitter pour aller chercher ailleurs.

   Ici et non dans la feuille des composants parce que le contrôle vit dans
   les TROIS coquilles `pj` (visiteur, connectée, Académie) : la règle et
   les tokens qu'elle emploie doivent être dans la seule feuille que toutes
   les piles chargent (CLAUDE.md §9).

   Deux pièges de ce coin d'écran, tous deux déjà payés ailleurs :

   - `button, .btn` met tout `<button>` nu en pleine largeur dans la
     coquille connectée (§5). Chaque bouton d'ici déclare donc sa largeur.
   - `.pj-savebar` est `fixed; bottom: 0` sous 576px, sur les cinq écrans
     qui portent FormAutosave — l'espace entreprise, l'espace organisme,
     l'éditeur d'offre, l'éditeur de formation, le profil candidat. Ce sont
     exactement les écrans où l'on a le plus besoin d'aide. Le bouton se
     relève donc de `--pj-help-lift`, que le composant MESURE (la hauteur
     de la barre dépend de ce qu'elle contient).
   ===================================================================== */

/* Le bouton flottant. Une pastille avec son libellé, pas un « ? » nu : sur
   un public peu familier de la technologie, une icône seule se devine, un
   mot se lit. 48px de haut, au-dessus du plancher tactile de 44. */
.pj-help__fab {
    position: fixed;
    right: 16px;
    bottom: calc(16px + var(--pj-help-lift, 0px) + env(safe-area-inset-bottom, 0px));
    /* Sous les modales (1080) et sous le tiroir de navigation (1040) : le
       bouton est une offre, jamais une interruption. La feuille, elle,
       monte au-dessus de tout — voir `.pj-help__layer`. */
    z-index: 1020;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    width: auto;
    min-height: 48px;
    padding: 0 18px;
    border: 0;
    border-radius: var(--pj-radius-pill);
    background: var(--pj-navy);
    color: var(--pj-card);
    font-size: var(--pj-fs-md);
    font-weight: 600;
    line-height: 1;
    box-shadow: 0 10px 28px -10px rgba(16, 24, 40, 0.55);
    cursor: pointer;
}

.pj-help__fab:hover,
.pj-help__fab:focus-visible {
    background: var(--pj-navy-dark);
    color: var(--pj-card);
}

.pj-help__fab i {
    font-size: 18px; /* fs-ok: icône du bouton flottant */
}

/* La couche : le fond sombre et la feuille. Au-dessus des modales du thème
   (1080) et sous le bandeau de mise à jour (2000), qui n'attend rien de
   personne et ne doit pas rester coincé dessous. */
.pj-help__layer {
    position: fixed;
    inset: 0;
    z-index: 1090;
}

.pj-help__backdrop {
    position: absolute;
    inset: 0;
    background: rgba(9, 14, 20, 0.55);
    opacity: 0;
    transition: opacity 0.2s ease;
}

.pj-help__layer.is-open .pj-help__backdrop {
    opacity: 1;
}

.pj-help__sheet {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    /* Centrée et bornée : au-delà, une feuille pleine largeur sur un écran
       de bureau met le titre à un mètre du bouton qui l'a ouverte. */
    max-width: 560px;
    margin: 0 auto;
    max-height: min(78vh, 620px);
    display: flex;
    flex-direction: column;
    background: var(--pj-card);
    color: var(--pj-ink);
    border-radius: var(--pj-radius) var(--pj-radius) 0 0;
    box-shadow: 0 -12px 40px -16px rgba(9, 14, 20, 0.6);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    transform: translateY(100%);
    transition: transform 0.25s ease;
}

.pj-help__layer.is-open .pj-help__sheet {
    transform: translateY(0);
}

/* La poignée. `touch-action: none` demande au navigateur de nous laisser
   ces pixels : sans quoi le glissement vers le bas devient un défilement
   de page et la feuille ne se referme jamais du doigt. */
.pj-help__grab {
    flex: 0 0 auto;
    padding: 10px 0 6px;
    touch-action: none;
    cursor: grab;
}

.pj-help__grab::before {
    content: "";
    display: block;
    width: 44px;
    height: 4px;
    margin: 0 auto;
    border-radius: var(--pj-radius-pill);
    background: var(--pj-line);
}

.pj-help__head {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 0 8px 8px 16px;
    touch-action: none;
}

.pj-help__title {
    margin: 0;
    font-size: var(--pj-fs-lg);
    font-weight: 700;
    color: var(--pj-ink);
}

.pj-help__close {
    width: 44px;
    height: 44px;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: var(--pj-radius-pill);
    background: transparent;
    color: var(--pj-muted);
    font-size: 18px; /* fs-ok: glyphe de fermeture */
    cursor: pointer;
}

.pj-help__close:hover {
    background: var(--pj-card-2);
    color: var(--pj-ink);
}

.pj-help__body {
    flex: 1 1 auto;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 0 16px 8px;
}

/* Le focus part dans le corps à l'ouverture, pour que le lecteur d'écran
   annonce le dialogue. C'est un conteneur, pas un contrôle : on ne lui
   peint pas d'anneau. */
.pj-help__body:focus {
    outline: none;
}

.pj-help__wait {
    margin: 8px 0;
    color: var(--pj-muted);
}

/* Les questions de l'écran, touchables du doigt. Aucune saisie : nommer ce
   qu'on cherche est précisément ce qui manque à qui a besoin d'aide. */
.pj-help__questions {
    display: grid;
    gap: 10px;
}

.pj-help__q {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    width: 100%;
    min-height: 56px;
    padding: 12px 14px;
    text-align: left;
    border: 1px solid var(--pj-line);
    border-radius: var(--pj-radius-sm);
    background: var(--pj-card-2);
    color: var(--pj-ink);
    font-size: var(--pj-fs-md);
    font-weight: 600;
    cursor: pointer;
}

.pj-help__q:hover,
.pj-help__q:focus-visible {
    border-color: var(--pj-navy);
}

.pj-help__q-text {
    min-width: 0;
}

.pj-help__q i {
    flex: 0 0 auto;
    color: var(--pj-muted);
    font-size: 12px; /* fs-ok: chevron de la question */
}

.pj-help__back {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    width: auto;
    min-height: 44px;
    padding: 0 4px;
    border: 0;
    background: transparent;
    color: var(--pj-navy);
    font-size: var(--pj-fs-body);
    font-weight: 600;
    cursor: pointer;
}

.pj-help__q-title {
    margin: 4px 0 10px;
    font-size: var(--pj-fs-lg);
    font-weight: 700;
    color: var(--pj-ink);
}

/* Les étapes, numérotées par un compteur : le numéro est de la décoration
   et n'a rien à dire à un lecteur d'écran, que `<ol>` renseigne déjà. */
.pj-help__steps {
    margin: 0;
    padding: 0;
    list-style: none;
    counter-reset: pj-help-step;
}

.pj-help__step {
    display: grid;
    grid-template-columns: 28px 1fr;
    gap: 8px 12px;
    align-items: start;
    padding: 10px 0;
    border-bottom: 1px solid var(--pj-line);
}

.pj-help__step:last-child {
    border-bottom: 0;
}

.pj-help__step::before {
    counter-increment: pj-help-step;
    content: counter(pj-help-step);
    grid-row: 1;
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--pj-radius-pill);
    /* La paire mesurée du bloc d'info : 6,35:1 en clair, 7,76:1 en sombre. */
    background: var(--pj-info-soft);
    color: var(--pj-info-ink);
    font-size: var(--pj-fs-sm);
    font-weight: 700;
}

.pj-help__step-text {
    grid-column: 2;
    grid-row: 1;
    align-self: center;
    font-size: var(--pj-fs-md);
    line-height: 1.45;
    color: var(--pj-ink);
}

/* « M'y emmener » : la moitié qui fait la différence entre une consigne et
   un chemin. Sous le texte de l'étape, aligné avec lui. */
.pj-help__go {
    grid-column: 2;
    justify-self: start;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    width: auto;
    min-height: 40px;
    padding: 0 14px;
    border: 1px solid var(--pj-navy);
    border-radius: var(--pj-radius-pill);
    background: transparent;
    color: var(--pj-navy);
    font-size: var(--pj-fs-body);
    font-weight: 600;
    cursor: pointer;
}

.pj-help__go:hover,
.pj-help__go:focus-visible {
    background: var(--pj-navy);
    color: var(--pj-card);
}

.pj-help__foot {
    flex: 0 0 auto;
    border-top: 1px solid var(--pj-line);
    padding: 8px 16px 12px;
}

.pj-help__all {
    width: 100%;
    min-height: 44px;
    border: 0;
    background: transparent;
    color: var(--pj-navy);
    font-size: var(--pj-fs-body);
    font-weight: 600;
    cursor: pointer;
}

/* La page derrière la feuille ne défile plus : un doigt qui glisse dans la
   feuille emporterait l'écran en dessous, et l'on ne retrouve alors ni
   l'un ni l'autre. */
body.pj-help-open {
    overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
    .pj-help__backdrop,
    .pj-help__sheet {
        transition: none;
    }
}

/* =====================================================================
   §16 — Le corps d'un DOCUMENT, et son dialecte (2026-10-03)

   Contrat de la fonction `renderDocumentBody()`
   (framework/utils/documentBody.js). Ici et non dans la feuille des
   composants parce que DEUX modules la partagent — CountryContent pour
   `/cgu`, Academy pour l'écran de signature — et qu'un contrat JS vit
   dans cette feuille-ci.

   Les deux classes existent pour une seule raison : le `white-space`.
   Un corps en texte brut n'a que ses retours à la ligne pour balisage,
   donc il lui faut `pre-wrap` ; un corps en HTML ne doit surtout pas
   l'avoir, ses `<p>` étant indentés dans le source — chaque bloc
   gagnerait un interligne vide. `.terms-body` posait `pre-wrap` pour
   tout le monde, ce qui était juste tant qu'il n'y avait qu'un dialecte.
   ===================================================================== */

/* Le texte brut : trente lignes de `country_terms` sont dans cet état, et
   le resteront jusqu'à ce que quelqu'un les réenregistre depuis la boîte
   riche (cf. migration 009 — on ne convertit pas un contrat à l'aveugle). */
.doc-body--plain {
    white-space: pre-wrap;
}

.doc-body--html {
    white-space: normal;
}

/* Les titres d'articles, qui sont tout l'objet de l'ajout de `h2`-`h4` à la
   liste blanche. Mesuré avant : « ARTICLE 1 — OBJET » faisait 121 px dans
   une colonne de 313 et ne se distinguait en rien du paragraphe suivant.
   La marge haute est plus grande que la basse — un titre appartient à ce
   qui le suit, pas à ce qui le précède. */
.doc-body--html h2,
.doc-body--html h3,
.doc-body--html h4 {
    margin: 24px 0 8px;
    font-weight: 700;
    line-height: 1.3;
}

/* RELATIFS AU CORPS, et non epingles sur l'echelle. Mesure le 2026-10-05
   sur la note legale : les `h3` sortaient a 14 px (`--pj-fs-body`) dans un
   `.terms-body` qui en fait 16 -- un titre PLUS PETIT que le texte qu'il
   annonce, donc lu comme une legende. La regle supposait que tout corps de
   document ferait 14 px ; celui-la n'est pas le seul, et le prochain fera
   autre chose.

   Corriger le jeton aurait fait coincider ici et se desaccorder ailleurs.
   Une taille relative tient quel que soit le conteneur, ce qui est
   exactement ce qu'un document injecte demande : il ne choisit pas ou on
   l'affiche. */
.doc-body--html h2 {
    font-size: 1.35em; /* fs-ok : relatif au corps, qui varie selon l'ecran */
}

.doc-body--html h3 {
    font-size: 1.15em; /* fs-ok : relatif au corps, qui varie selon l'ecran */
}

.doc-body--html h4 {
    font-size: 1.05em; /* fs-ok : relatif au corps, qui varie selon l'ecran */
}

/* Le premier enfant ne creuse pas un trou en haut du document. */
.doc-body--html > :first-child {
    margin-top: 0;
}

.doc-body--html p {
    margin: 0 0 12px;
}

.doc-body--html ul,
.doc-body--html ol {
    margin: 0 0 12px;
    padding-left: 22px;
}

.doc-body--html li {
    margin: 0 0 6px;
}

/* Un lien dans un contrat doit se voir comme un lien : c'est parfois lui qui
   porte l'annexe.

   `--pj-orange-text` et non un jeton de lien : il n'en existe pas, et un
   `var()` sans valeur ne retombe pas sur un defaut -- la declaration entiere
   devient `inherit`. Celui-ci est mesure (4,69:1 en clair, 6,19:1 en sombre)
   et c'est celui qu'`a.pj-terms__link` emploie deja.

   Le selecteur descend par la BALISE a dessein : `.pj-app a { color: inherit }`
   (prosyjob.css) pese (0,1,1), donc une classe seule perdrait EN SILENCE --
   c'est le defaut que `check-link-colors.mjs` existe pour attraper. Ici
   `.doc-body--html a` pese aussi (0,1,1) et gagne a l'ordre, cette feuille
   etant chargee en dernier. */
.doc-body--html a {
    color: var(--pj-orange-text);
    text-decoration: underline;
}

/* Une colonne de LECTURE, sur `/cgu` comme sur l'écran de signature, qui la
   posait déjà pour lui seul : un contrat se lit en entier, et une ligne de
   120 signes se relit trois fois. */
.terms-body.doc-body {
    max-width: 70ch;
}

/* ============================================================================
   §17 — Les pages de documents juridiques (2026-10-13)
   /cgu, /mentions-legales, /confidentialite, /cookies, /responsabilite-conjointe
   — une seule page, TermsPage, donc une seule mise en page.

   Sur la maquette `responsabilite.png` : deux cartes JUMELLES, la même
   bordure, le même rayon, la même ombre, et un air large à l'intérieur. La
   seconde était une `.pj-card` ordinaire (rayon 16, ombre plate, 26 px) sous
   un hero en 22 / ombre portée / 28 px : deux familles de cartes l'une sous
   l'autre. Le titre passe à l'encre de l'interface (`--pj-ink`, un bleu nuit
   qui suit le thème sombre) au lieu du presque-noir du thème, et l'intro à
   l'échelle d'un titre de carte, comme sur la maquette.

   Les tailles restent des pas de l'échelle (§ type scale) : la maquette est
   une image agrandie, et c'est la proportion entre les textes qu'on en garde,
   pas ses pixels.
   ========================================================================= */
.terms-page > .dashboard-hero-card,
.terms-page > #terms-box {
    padding: 44px;
    border-radius: 22px;
    border: 1px solid var(--connected-border);
    box-shadow: var(--connected-shadow);
}

.terms-page > .dashboard-hero-card h1 {
    color: var(--pj-ink);
    margin-bottom: 12px;
}

.terms-page > .dashboard-hero-card p {
    font-size: var(--pj-fs-card);
    line-height: 1.55;
    margin-bottom: 0;
}

/* Vide quand le pays servi n'a rien à signaler : il ne doit pas creuser. */
.terms-page #terms-pays-note:empty {
    display: none;
}

.terms-page #terms-pays-note:not(:empty) {
    margin-top: 12px;
    font-size: var(--pj-fs-md);
}

/* « Ce document n'est pas encore publié pour ce pays. » : le seul contenu de
   la carte, donc sans la marge basse d'un paragraphe suivi d'autre chose. */
.terms-page .terms-empty {
    margin: 0;
    font-size: var(--pj-fs-lg);
    line-height: 1.6;
}

@media (max-width: 575.98px) {
    .terms-page > .dashboard-hero-card,
    .terms-page > #terms-box {
        padding: 30px 26px;
    }

    .terms-page > .dashboard-hero-card p {
        font-size: var(--pj-fs-lg);
    }
}
