:root {
  --portailetu-primary: #1F82C0;
  /* Accent hover (charte 1.1, #17669a) : declared here because the charte
     lists it as "à déclarer, commit boutons" and this is that commit for the
     payment action buttons. A hover is a COLOR change, never a veil, so it
     needs its own token rather than an opacity over the accent. The matching
     --portailetu-primary-active is deliberately NOT declared: no rule consumes
     a pressed accent yet. */
  --portailetu-primary-hover: #17669a;
  --portailetu-success: #90B329;
  --portailetu-error: #b91c1c;
  /* Signal banner pairs (charter): amber = correctable/waiting states,
     neutral = administrative blocks. The neutral pair reuses values already
     present in this stylesheet (#f6f7f7 surface, #2c3338 text); the amber
     pair is lifted from the extranet signal tokens (no warning color existed
     here before). */
  --portailetu-warn-text: #8a6d00;
  --portailetu-warn-surface: #fcf3d6;
  --portailetu-neutral-text: #2c3338;
  --portailetu-neutral-surface: #f6f7f7;

  /* Design token scales — DECLARATION ONLY.
     These name the target scales for the coming harmonization commits;
     existing rules keep their current literals until each surface is
     migrated deliberately. Declaring them here (front.css :root) makes
     them reachable on every front surface: the public form, the workflow
     runner, and the extranet shell (extranet.css depends on this handle). */

  /* Spacing scale (px). */
  --portailetu-space-1: 4px;
  --portailetu-space-2: 8px;
  --portailetu-space-3: 12px;
  --portailetu-space-4: 16px;
  --portailetu-space-5: 20px;
  --portailetu-space-6: 24px;
  --portailetu-space-7: 32px;
  --portailetu-space-8: 48px;

  /* Radius scale. radius-lg is the "strongly rounded" step for PRIMARY action
     buttons : a marked corner, deliberately NOT the 999px pill. */
  --portailetu-radius-sm: 4px;
  --portailetu-radius-md: 8px;
  --portailetu-radius-lg: 12px;
  /* Card radius (charte §1.3 --radius-carte : 16px, « toutes les cartes, sans
     exception »). The single value for every card surface — extranet panels,
     profile form cards, save bar, transition card, public shell panel. The
     999px pill stays distinct below. */
  --portailetu-radius-xl: 16px;
  --portailetu-radius-pill: 999px;

  /* Elevation scale. The extranet drawer shadow keeps its own dedicated
     token (--portailetu-shell-drawer-shadow, extranet.css). */
  --portailetu-shadow-low: 0 3px 6px rgba(5, 5, 5, 0.06);
  --portailetu-shadow-high: 0 3px 12px rgba(5, 5, 5, 0.12);

  /* Type scale (size / line-height / weight per role). No element consumes
     these yet — wiring them is a later commit. */
  --portailetu-text-xl-size: 24px;
  --portailetu-text-xl-line: 32px;
  --portailetu-text-xl-weight: 600;
  --portailetu-text-lg-size: 18px;
  --portailetu-text-lg-line: 24px;
  --portailetu-text-lg-weight: 600;
  --portailetu-text-md-size: 15px;
  --portailetu-text-md-line: 24px;
  --portailetu-text-md-weight: 600;
  --portailetu-text-base-size: 14px;
  --portailetu-text-base-line: 20px;
  --portailetu-text-base-weight: 400;
  --portailetu-text-sm-size: 13px;
  --portailetu-text-sm-line: 16px;
  --portailetu-text-xs-size: 12px;
  --portailetu-text-xs-line: 16px;
  /* Méta réduite (charte 1.5 : 11px / 1.273) — completes the type scale for
     the integrated-label field (Micro-charte Profil §5). */
  --portailetu-text-2xs-size: 11px;
  --portailetu-text-2xs-line: 14px;

  /* Integrated-label field tokens (Micro-charte Profil §5 = the DEFAULTS,
     configurable like every --portailetu-* color). The locked surface
     reuses --portailetu-neutral-surface (#f6f7f7, identical value). The
     micro-charte error hue (#b32d2e) differs from the legacy
     --portailetu-error (#b91c1c), hence its own token. */
  --portailetu-field-surface: #fff;
  --portailetu-field-border: #dcdcde;
  --portailetu-field-border-locked: #f0f0f1;
  --portailetu-field-label: #646970;
  --portailetu-field-text: #1d2327;
  --portailetu-field-muted: #8c8f94;
  --portailetu-field-error: #b32d2e;
  --portailetu-field-ring-focus: rgba(31, 130, 192, 0.15);
  --portailetu-field-ring-error: rgba(179, 45, 46, 0.12);

  /* Button-state opacities — declared for the button commit to consume;
     no button is wired to them here. */
  --portailetu-hover-opacity: 0.08;
  --portailetu-pressed-opacity: 0.12;
  --portailetu-disabled-opacity: 0.45;

  /* Current-step pill (workflow stepper). Historical blue, deliberately NOT
     --portailetu-primary — lifted into a token as-is; whether it should join
     the primary is a harmonization decision, not this commit's. The text
     token completes the pair (charte 1.1 : states are fond/texte pairs) —
     it lifts the raw #fff the timeline rule carried, same value. These two
     serve ONLY the legacy horizontal timeline outside the shells ; the
     public-shell rail stepper converged its current color to the accent
     (charte 6) and never reads them. */
  --portailetu-step-current: #346a91;
  --portailetu-step-current-text: #fff;

  /* Accent surface (charte 1.1, #e4f0f8) : the light accent wash behind the
     CURRENT chip of the form-section stepper. Same default value as the
     extranet chrome twin (--portailetu-shell-accent-surface, extranet.css),
     declared here because extranet.css never loads on the public campaign
     page and the stepper serves BOTH surfaces. */
  --portailetu-accent-surface: #e4f0f8;

  /* Accent hatch (micro-charte 404) : the DARKER of the two light accent tones
     the 404 figure is hatched with, the lighter one being
     --portailetu-accent-surface above. Declared rather than reused because no
     existing token sits anywhere near this value — the light-accent family only
     held the two near-identical washes (#e4f0f8 / #e5f0fa), which would make a
     hatch invisible. It doubles as the SOLID fallback figure color where
     background-clip:text is unsupported (never the full accent). */
  --portailetu-accent-hatch: #a7cfe6;
}

/* Formulaire : largeur + marges, mais pas de fond ni de police imposés.
   ALIGNEMENT : le formulaire est calé À GAUCHE dans sa colonne de contenu,
   jamais centré horizontalement (règle front actée).
   LARGEUR (charte §4.1) : une carte principale prend 100% de sa colonne — le
   plafond vient désormais de la colonne bornée (55rem) et non plus d'un cap
   dur 760px sur le formulaire. Le profil extranet garde son override
   max-width:none (grille deux colonnes), inchangé.
   CONTAINER : la grille de champs (§6) et sa bascule < 720px s'évaluent
   contre la largeur du formulaire (container query, jamais le viewport). */
.portailetu-form {
  max-width: 100%;
  margin: 0 0 3rem;
  container-type: inline-size;
}

/* Public campaign pages now render inside the plugin's PUBLIC SHELL
   (public-shell.css, LOT 2.3a) : the runner never sits under a theme
   .is-layout-constrained group anymore, so the old 645px-cap compensation
   (max-width:760px on the wrapper) is gone with its selector — the shell
   geometry replaces it. Kept lesson : NO container-type on the runner
   wrapper — layout containment on an ancestor would trap any descendant
   position:sticky (profile save bar lesson). */

/* ======================================================================
   Integrated-label field (Micro-charte Profil §5) + deterministic width
   grid (§6) — shared by EVERY render_form_items surface : the extranet
   profile page, the public campaign form and the workflow runner. The box
   is the single clickable zone (label + value inside) ; states change one
   property at a time ; every color is a --portailetu-* token declared in
   :root above (micro-charte hex = token defaults).
   ====================================================================== */

/* Groupes de champs */
.portailetu-form-group {
  margin-bottom: 1rem;
}

/* Field grid (§6) : one run of consecutive visible fields. Sections, text
   blocks and hidden carriers break runs SERVER-SIDE, and the half/full
   classes (orphans already stretched) are computed server-side too — the
   grid only consumes them. */
.portailetu-form-fields {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--portailetu-space-3);
  margin-bottom: var(--portailetu-space-4);
}

/* Inside a section card the card's own flex gap paces the flow. */
.portailetu-form-card .portailetu-form-fields {
  margin-bottom: 0;
}

.portailetu-form-fields .portailetu-form-group {
  margin-bottom: 0;
  min-width: 0;
}

.portailetu-form-group--full {
  grid-column: 1 / -1;
}

/* §6 safety net (verbatim rule) : a trailing odd half stretches. The server
   plan already stretches orphans and keeps hidden carriers out of the grid,
   so this can only re-assert the server's own decision. */
.portailetu-form-fields > .portailetu-form-group--half:last-child:nth-child(odd) {
  grid-column: 1 / -1;
}

/* §6 responsive : below 720px of the form container, everything is 1/1. */
@container (max-width: 720px) {
  .portailetu-form-fields {
    grid-template-columns: 1fr;
  }
}

/* --- The box (rest state §5) ----------------------------------------- */
.portailetu-field-box {
  display: block;
  box-sizing: border-box;
  width: 100%;
  padding: 7px 12px;
  background: var(--portailetu-field-surface);
  border: 1px solid var(--portailetu-field-border);
  border-radius: var(--portailetu-radius-md);
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

/* Label row INSIDE the box : 11px label, dot host, optional lock. */
.portailetu-field-box__label {
  display: flex;
  align-items: center;
  gap: var(--portailetu-space-1);
  font-size: var(--portailetu-text-2xs-size);
  line-height: var(--portailetu-text-2xs-line);
  font-weight: 400;
  color: var(--portailetu-field-label);
  transition: color 0.12s ease;
}

.portailetu-field-box__label-text {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Champ requis (§5) : l'étoile est dessinée en CSS sur le libellé du box
   comme sur celui de la rangée checkbox — jamais sur les options radio. */
.portailetu-form-group--required .portailetu-field-box__label-text::after {
  content: " *";
  color: var(--portailetu-field-error);
}

/* Modified-unsaved dot HOST (§5) : 6px accent dot, shipped hidden — the
   dirty-tracking that reveals it ships with the sticky bar (next part). */
.portailetu-field-box__dot {
  flex: 0 0 auto;
  width: 6px;
  height: 6px;
  border-radius: var(--portailetu-radius-pill);
  background: var(--portailetu-primary);
}

.portailetu-field-box__dot[hidden] {
  display: none;
}

/* Lock glyph (§5 locked : 13px), right-aligned in the label row. */
.portailetu-field-box__lock {
  display: inline-flex;
  flex: 0 0 auto;
  margin-left: auto;
  color: var(--portailetu-field-muted);
}

/* Value controls INSIDE the box : the box is the only chrome. The box ring
   replaces the native outline (focus stays visible at box level). */
.portailetu-field-box input:not([type="hidden"]):not([type="radio"]):not([type="checkbox"]),
.portailetu-field-box textarea {
  display: block;
  box-sizing: border-box;
  width: 100%;
  margin: 0;
  padding: 0;
  border: none;
  background: transparent;
  box-shadow: none;
  outline: none;
  /* The charte stack is applied at shell level and reaches the controls by
     inheritance only : without this the UA gives the textarea its monospace
     default while the input keeps another one, so the two value lines of the
     same form do not match. Same technique as .pe-login input.input. */
  font-family: inherit;
  font-size: var(--portailetu-text-base-size);
  line-height: var(--portailetu-text-base-line);
  color: var(--portailetu-field-text);
  font-variant-numeric: tabular-nums;
  caret-color: var(--portailetu-primary);
}

/* Empty value = gabarit in the muted hue, the label never disappears. */
.portailetu-field-box input::placeholder,
.portailetu-field-box textarea::placeholder {
  color: var(--portailetu-field-muted);
  opacity: 1;
}

/* Read-only value line (locked select, fixed choix_annee). */
.portailetu-field-box .portailetu-static-value {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: var(--portailetu-text-base-size);
  line-height: var(--portailetu-text-base-line);
  color: var(--portailetu-field-text);
  font-variant-numeric: tabular-nums;
}

/* --- Hover (§5) : the border alone ----------------------------------- */
.portailetu-field-box:hover {
  border-color: var(--portailetu-field-muted);
}

/* --- Focus (§5) : accent border + 3px ring + accent label + caret ----- */
.portailetu-field-box:focus-within {
  border-color: var(--portailetu-primary);
  box-shadow: 0 0 0 3px var(--portailetu-field-ring-focus);
}

.portailetu-field-box:focus-within .portailetu-field-box__label {
  color: var(--portailetu-primary);
}

/* --- Error (§5) : error border + ring + label, message under the box -- */
.portailetu-form-group--error .portailetu-field-box {
  border-color: var(--portailetu-field-error);
  box-shadow: 0 0 0 3px var(--portailetu-field-ring-error);
}

.portailetu-form-group--error .portailetu-field-box .portailetu-field-box__label {
  color: var(--portailetu-field-error);
}

.portailetu-field-error {
  margin: var(--portailetu-space-1) 0 0;
  font-size: var(--portailetu-text-xs-size);
  line-height: var(--portailetu-text-xs-line);
  color: var(--portailetu-field-error);
}

/* --- Locked (§5) : muted surface, sunken border, muted text ----------- */
.portailetu-field-box--locked {
  background: var(--portailetu-neutral-surface);
  border-color: var(--portailetu-field-border-locked);
  cursor: default;
}

.portailetu-field-box--locked:hover {
  border-color: var(--portailetu-field-border-locked);
}

.portailetu-field-box--locked input:not([type="hidden"]):not([type="radio"]):not([type="checkbox"]),
.portailetu-field-box--locked textarea,
.portailetu-field-box--locked .portailetu-static-value,
.portailetu-field-box--locked .portailetu-field-radio,
.portailetu-field-box--locked .portailetu-checkbox-label {
  color: var(--portailetu-field-muted);
  cursor: default;
}

/* --- Select : Pe_Dropdown INSIDE the box (§5) ------------------------- */
.portailetu-field-box--select {
  cursor: pointer;
}

.portailetu-field-box--select .pe-dropdown {
  display: block;
}

/* The trigger becomes the value line : no own chrome, chevron right. The
   body:not(.wp-admin) prefix out-specifies the front font normalization
   at the bottom of this sheet. */
body:not(.wp-admin) .portailetu-field-box--select .pe-dropdown__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: 0;
  margin: 0;
  padding: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  outline: none;
  font-size: var(--portailetu-text-base-size);
  line-height: var(--portailetu-text-base-line);
  color: var(--portailetu-field-text);
  cursor: pointer;
}

body:not(.wp-admin) .portailetu-field-box--select .pe-dropdown__toggle:focus,
body:not(.wp-admin) .portailetu-field-box--select .pe-dropdown.is-open .pe-dropdown__toggle {
  border: none;
  box-shadow: none;
}

body:not(.wp-admin) .portailetu-field-box--select .pe-dropdown__current-label {
  font-size: var(--portailetu-text-base-size);
  line-height: var(--portailetu-text-base-line);
}

body:not(.wp-admin) .portailetu-field-box--select .pe-dropdown__current-label[data-placeholder="1"] {
  color: var(--portailetu-field-muted);
}

/* §5 chevron : 16px, gris 600, right. */
.portailetu-field-box--select .pe-dropdown__caret {
  font-size: 16px;
  color: var(--portailetu-field-label);
}

/* --- Choix multiple : "Autre" free text under the picker -------------- */
.portailetu-field-box--multi-select .portailetu-multiselect-other {
  display: block;
  margin-top: var(--portailetu-space-2);
}

/* display:block above would defeat the hidden attribute — restate it. */
.portailetu-field-box--multi-select .portailetu-multiselect-other[hidden] {
  display: none;
}

/* --- Téléphone : indicatif + numéro national dans le même box --------- */
/* La rangée vaut pour le contrôle éditable (box --tel) comme pour son
   miroir non éditable (box --locked), qui partagent le même balisage. */
.portailetu-field-box .portailetu-tel-composite {
  display: flex;
  align-items: center;
  gap: var(--portailetu-space-2);
}

/* Les bornes de pe-dropdown.css (min-width 200px, max-width 360px) sont
   taillées pour les rangées de form-table admin et écraseraient la rangée
   ici : neutralisées et REMPLACÉES par une borne intrinsèque. max-content
   est exactement la largeur de l'état fermé, c'est-à-dire le signe plus et
   ses 1 à 3 chiffres plus le chevron, ou le placeholder tant qu'aucun pays
   n'est choisi. Une largeur en pixels calée sur le plus long indicatif
   tronquerait ce placeholder, qui est l'état le plus courant (valeur
   historique sans indicatif). Le reste de la rangée revient au numéro. */
.portailetu-field-box .portailetu-tel-composite .pe-dropdown {
  flex: 0 0 auto;
  width: max-content;
  min-width: 0;
  max-width: none;
}

.portailetu-field-box .portailetu-tel-composite__national {
  flex: 1 1 auto;
  min-width: 0;
  cursor: text;
}

/* Miroir non éditable : l'indicatif en texte statique, le numéro dans le
   même input désactivé que la branche coeur émettait déjà. */
.portailetu-field-box .portailetu-tel-composite__dial-static {
  flex: 0 0 auto;
  font-size: var(--portailetu-text-base-size);
  line-height: var(--portailetu-text-base-line);
  color: var(--portailetu-field-text);
  font-variant-numeric: tabular-nums;
}

.portailetu-field-box--locked .portailetu-tel-composite__dial-static {
  color: var(--portailetu-field-muted);
}

/* --- Radio : options stacked under the integrated label --------------- */
.portailetu-field-box--radio .portailetu-radio-group {
  display: flex;
  flex-direction: column;
  gap: var(--portailetu-space-2);
  padding-top: var(--portailetu-space-1);
}

.portailetu-field-box .portailetu-field-radio {
  display: flex;
  align-items: center;
  gap: var(--portailetu-space-2);
  font-size: var(--portailetu-text-base-size);
  line-height: var(--portailetu-text-base-line);
  color: var(--portailetu-field-text);
  cursor: pointer;
}

.portailetu-field-box input[type="radio"] {
  margin: 0;
  accent-color: var(--portailetu-primary);
}

/* ======================================================================
   Checkbox row (§5) : the field is ONE full-width row — 18px box, checked
   accent fill + token-colored check, optional "Consulter" affordance on
   legal rows. The same row hosts the read-only (locked) rendering.
   ====================================================================== */
.portailetu-form-group--checkbox .portailetu-field--checkbox {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--portailetu-space-3);
  box-sizing: border-box;
  width: 100%;
  padding: 9px 12px;
  background: var(--portailetu-field-surface);
  border: 1px solid var(--portailetu-field-border);
  border-radius: var(--portailetu-radius-md);
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

.portailetu-form-group--checkbox .portailetu-field--checkbox:hover {
  border-color: var(--portailetu-field-muted);
}

.portailetu-form-group--checkbox .portailetu-field--checkbox:focus-within {
  border-color: var(--portailetu-primary);
  box-shadow: 0 0 0 3px var(--portailetu-field-ring-focus);
}

.portailetu-form-group--error .portailetu-field--checkbox {
  border-color: var(--portailetu-field-error);
  box-shadow: 0 0 0 3px var(--portailetu-field-ring-error);
}

.portailetu-form-group--checkbox .portailetu-checkbox-label {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  gap: var(--portailetu-space-3);
  min-width: 0;
  margin: 0;
  font-size: var(--portailetu-text-base-size);
  line-height: var(--portailetu-text-base-line);
  font-weight: 400;
  color: var(--portailetu-field-text);
  cursor: pointer;
}

.portailetu-form-group--checkbox.portailetu-form-group--required .portailetu-checkbox-label::after {
  content: " *";
  color: var(--portailetu-field-error);
}

.portailetu-form-group--checkbox input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  display: inline-grid;
  place-content: center;
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  margin: 0;
  border: 1px solid var(--portailetu-field-muted);
  border-radius: var(--portailetu-radius-sm);
  background: var(--portailetu-field-surface);
  cursor: pointer;
}

.portailetu-form-group--checkbox input[type="checkbox"]:checked {
  background: var(--portailetu-primary);
  border-color: var(--portailetu-primary);
}

/* The check : two token-colored strokes (no image URI, stays themable). */
.portailetu-form-group--checkbox input[type="checkbox"]:checked::after {
  content: "";
  width: 4px;
  height: 9px;
  margin-top: -2px;
  border: solid var(--portailetu-field-surface);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

/* "Consulter" (§5) : accent text right — the whole legal row already opens
   the document modal (legal-checkbox.js). */
.portailetu-checkbox-consult {
  flex: 0 0 auto;
  margin-left: auto;
  font-size: var(--portailetu-text-base-size);
  line-height: var(--portailetu-text-base-line);
  color: var(--portailetu-primary);
  cursor: pointer;
}

.portailetu-form-group--checkbox .portailetu-checkbox-description {
  flex-basis: 100%;
  font-size: var(--portailetu-text-xs-size);
  line-height: var(--portailetu-text-xs-line);
  color: var(--portailetu-field-label);
}

/* Locked checkbox row (§5 locked). */
.portailetu-form-group--locked .portailetu-field--checkbox {
  background: var(--portailetu-neutral-surface);
  border-color: var(--portailetu-field-border-locked);
}

.portailetu-form-group--locked .portailetu-field--checkbox:hover {
  border-color: var(--portailetu-field-border-locked);
}

.portailetu-form-group--locked .portailetu-checkbox-label,
.portailetu-form-group--locked input[type="checkbox"] {
  color: var(--portailetu-field-muted);
  cursor: default;
}

.portailetu-form-group--locked input[type="checkbox"]:checked {
  background: var(--portailetu-field-muted);
  border-color: var(--portailetu-field-muted);
}

/* Notices globales */
.portailetu-notice {
  padding: 0.6rem 0.8rem;
  border-radius: 4px;
  font-size: 0.9em;
  margin-bottom: 1rem;
  border-left: 4px solid transparent;
}

.portailetu-notice-success {
  border-color: var(--portailetu-success);
}

.portailetu-notice-error {
  border-color: var(--portailetu-error);
}

/* Bouton : on donne juste un style par défaut, le thème peut l’écraser facilement */
.portailetu-form-actions {
  margin-top: 1.5rem;
  text-align: right;
}

/* ======================================================================
   Canonical FRONT button family. PO charte rule, FINAL : SHAPE marks
   hierarchy, and the shape is owned by the MODIFIER, never by the base :
   - PRIMARY  = pill (radius-pill), solid primary fill ;
   - SECONDARY = squared (radius-md), quiet bordered treatment.
   Interaction states change ONE COLOR at a time. The ::before veil that
   used to carry the hover/pressed opacities has left the family : the
   primary hovers on --portailetu-primary-hover, the secondary on its own
   surface tokens. No overlay anywhere.

   The base carries the BOX and the TYPOGRAPHY, so every attached control
   is strictly identical whatever its modifier and whatever its element :
   two of the attachments are anchors, hence display + text-decoration
   here rather than on a modifier. 0.875rem / 1.429 is the token pair
   --portailetu-text-base-size / --portailetu-text-base-line expressed in
   the charte's own unit (14px / 20px).

   Families keep their own classes — the payment recap confirm now carries the
   base in its markup (payment-step.js) like every other attachment, but not
   the primary modifier : its skin is raccorded here through its OWN companion
   class, scoped to the payment step container.
   ====================================================================== */
/* The editor button block under the public shell is an attachment too : it
   cannot carry our classes (its markup belongs to core), so the family is
   raccorded to it through a scoped co-selector on every group. It is a
   DEFAULT, not a lock — the editor's own !important classes
   (has-*-background-color, has-*-color, no-border-radius) intentionally win
   over it. Scope is body.pe-public : the choice page AND the shortcode
   pages, never the extranet or wp-admin. */
.portailetu-button,
.pe-public .wp-block-button__link,
.pe-extranet-page .wp-block-button__link,
.pe-extranet__custom-page .wp-block-button__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.5rem 1.2rem;
  border-radius: var(--portailetu-radius-lg);
  border: none;
  font-size: 0.875rem;
  line-height: 1.429;
  text-decoration: none;
  cursor: pointer;
}

.portailetu-button-primary,
.portailetu-payment-step .pe-payment-recap__confirm,
.pe-public .wp-block-button__link,
.pe-extranet-page .wp-block-button__link,
.pe-extranet__custom-page .wp-block-button__link {
  padding: 0.5rem 1.2rem;
  border: none;
  border-radius: var(--portailetu-radius-pill);
  background-color: var(--portailetu-primary);
  color: #fff;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}

.portailetu-button-primary:hover,
.portailetu-payment-step .pe-payment-recap__confirm:hover,
.pe-public .wp-block-button__link:hover,
.pe-extranet-page .wp-block-button__link:hover,
.pe-extranet__custom-page .wp-block-button__link:hover {
  background-color: var(--portailetu-primary-hover);
}

/* The three mode action buttons (P5) are plain attachments of the family :
   they wear "portailetu-button portailetu-button-primary" in the markup
   (class-workflow-shortcodes.php) and own NO skin of their own any more.
   The whole local rule died with B2 — box, radius, fill, typography and the
   color hover it used to restate are the family's, and its 11px 22px box
   and 500 weight yielded to the family's 0.5rem 1.2rem and 600. Only the
   two shared state groups below still name the class, because they are
   scoped to the payment step and the recap pair rides them too. */

/* Keyboard focus : a visible ring, never suppressed for keyboard users ;
   the mouse-focus outline is dropped only when :focus-visible does not
   match (the ring is re-asserted right below). */
.portailetu-button-primary:focus:not(:focus-visible),
.portailetu-payment-step .pe-payment-action:focus:not(:focus-visible),
.portailetu-payment-step .pe-payment-recap__confirm:focus:not(:focus-visible),
.pe-public .wp-block-button__link:focus:not(:focus-visible),
.pe-extranet-page .wp-block-button__link:focus:not(:focus-visible),
.pe-extranet__custom-page .wp-block-button__link:focus:not(:focus-visible) {
  outline: none;
}

.portailetu-button-primary:focus-visible,
.portailetu-payment-step .pe-payment-action:focus-visible,
.portailetu-payment-step .pe-payment-recap__confirm:focus-visible,
.pe-public .wp-block-button__link:focus-visible,
.pe-extranet-page .wp-block-button__link:focus-visible,
.pe-extranet__custom-page .wp-block-button__link:focus-visible {
  outline: 2px solid var(--portailetu-primary);
  outline-offset: 2px;
}

.portailetu-button-primary:disabled,
.portailetu-button-primary[aria-disabled="true"],
.portailetu-payment-step .pe-payment-action:disabled,
.portailetu-payment-step .pe-payment-action[aria-disabled="true"],
.portailetu-payment-step .pe-payment-recap__confirm:disabled,
.portailetu-payment-step .pe-payment-recap__confirm[aria-disabled="true"],
.pe-public .wp-block-button__link:disabled,
.pe-public .wp-block-button__link[aria-disabled="true"],
.pe-extranet-page .wp-block-button__link:disabled,
.pe-extranet-page .wp-block-button__link[aria-disabled="true"],
.pe-extranet__custom-page .wp-block-button__link:disabled,
.pe-extranet__custom-page .wp-block-button__link[aria-disabled="true"] {
  opacity: var(--portailetu-disabled-opacity);
  pointer-events: none;
}

/* ======================================================================
   Canonical SECONDARY family (lot B1). PO charte rule, FINAL :
   PRIMARY = pill, SECONDARY = squared (moderate radius, bordered), with
   IDENTICAL classes and CSS everywhere on the front. Every attachment
   carries "portailetu-button portailetu-button-secondary" and keeps its own
   class for LAYOUT only.

   Tokens : the FRONT family (--portailetu-*) exclusively, never the shell
   family — the same control must render identically on the public shell,
   the extranet and the front dialog, and only front.css is guaranteed
   loaded on all three.

   The box (padding, cursor), the typography and the anchor pair
   (display: inline-flex + text-decoration: none) all come from the
   .portailetu-button base, which B2 promoted to carry them for the whole
   family ; this modifier overrides the SHAPE only, squared against the
   primary's pill. Hover / pressed / focus are the form-wizard prev pattern
   verbatim (the cleanest existing secondary) : one color changes at a time,
   no veil.
   ====================================================================== */
/* The .pe-public co-selector comes FIRST in every group here : the
   ExtranetProfileCardTest pins expect the canonical secondary selector
   glued to its opening brace. Do not reorder. */
.pe-public .is-style-outline .wp-block-button__link,
.pe-extranet-page .is-style-outline .wp-block-button__link,
.pe-extranet__custom-page .is-style-outline .wp-block-button__link,
.portailetu-button-secondary {
  border: 1px solid var(--portailetu-field-border);
  border-radius: var(--portailetu-radius-md);
  background: var(--portailetu-field-surface);
  color: var(--portailetu-field-text);
  font-weight: 400;
}

.pe-public .is-style-outline .wp-block-button__link:hover,
.pe-extranet-page .is-style-outline .wp-block-button__link:hover,
.pe-extranet__custom-page .is-style-outline .wp-block-button__link:hover,
.portailetu-button-secondary:hover {
  background: var(--portailetu-neutral-surface);
}

.pe-public .is-style-outline .wp-block-button__link:active,
.pe-extranet-page .is-style-outline .wp-block-button__link:active,
.pe-extranet__custom-page .is-style-outline .wp-block-button__link:active,
.portailetu-button-secondary:active {
  background: var(--portailetu-field-border-locked);
}

.pe-public .is-style-outline .wp-block-button__link:focus:not(:focus-visible),
.pe-extranet-page .is-style-outline .wp-block-button__link:focus:not(:focus-visible),
.pe-extranet__custom-page .is-style-outline .wp-block-button__link:focus:not(:focus-visible),
.portailetu-button-secondary:focus:not(:focus-visible) {
  outline: none;
}

.pe-public .is-style-outline .wp-block-button__link:focus-visible,
.pe-extranet-page .is-style-outline .wp-block-button__link:focus-visible,
.pe-extranet__custom-page .is-style-outline .wp-block-button__link:focus-visible,
.portailetu-button-secondary:focus-visible {
  outline: 2px solid var(--portailetu-primary);
  outline-offset: 2px;
}

.portailetu-test-badge {
  background: #1db954;
  color: #fff;
  padding: 4px 8px;
  border-radius: 4px;
  font-weight: 700;
  display: inline-block;
}

/* Payment step (front) — ONE visible card. Both shells already wrap the step
   in their panel (.pe-extranet__panel / its public twin .pe-public__panel),
   and that panel IS the card : surface, hairline, radius and padding are its
   four declarations and are never restated here (Documents lot 2 precedent).
   The step body is therefore flat — no border, no background, no radius, and
   no inset padding either, so its blocks align on the step title the runner
   renders as a sibling just above. */
.portailetu-payment-step {
  margin-top: var(--portailetu-space-5);
}

/* Step title spacing, payment step ONLY (:has, so no other step kind moves).
   The runner emits a bare <h3> for every step kind and no rule ever zeroed its
   margins, so the UA default (1em top / 1em bottom on the h3's font-size)
   applied. Its TOP margin could not collapse away : the shell panel's padding
   blocks collapsing, so it stacked on top of the 24px panel padding and pushed
   the title down. Zeroed here, and the title-to-subline gap is set to 2px on
   the step itself (the h3's bottom margin used to collapse with the step's
   20px top margin, whichever was larger). The 20px case is preserved for the
   configured-intro layout, where the step no longer follows the h3 directly.
   2px is a raw value : off the 4/8/12/16/20/24 scale, imposed by the lot. */
.portailetu-workflow-current:has(> .portailetu-payment-step) > h3 {
  margin-top: 0;
}

/* Bottom margin zeroed only when the step FOLLOWS the title directly, so a
   step configured with an intro block keeps its historical title-to-intro
   gap. */
.portailetu-workflow-current > h3:has(+ .portailetu-payment-step) {
  margin-bottom: 0;
}

.portailetu-workflow-current > h3 + .portailetu-payment-step {
  margin-top: 2px;
}

/* Internal vertical rhythm of the step : 20px between top-level blocks
   (subline, banner, amount block, mode tiles, notice zone). Set on the flow
   itself so no component rule has to carry the step's spacing.
   :not(:first-child) rather than `* + *` on purpose : it reaches 0-2-0, so it
   wins over the historical single-class top margin the notice zone still
   declares for its own sake. */
.portailetu-payment-step > :not(:first-child) {
  margin-top: var(--portailetu-space-5);
}

/* Step subline, under the runner's step title. Muted meta treatment from the
   existing tokens (same pair as .portailetu-meta-subline). */
.portailetu-payment-subline {
  margin: 0;
  font-size: var(--portailetu-text-base-size);
  line-height: var(--portailetu-text-base-line);
  color: var(--portailetu-field-label);
}

/* Amount block (G1) : recessed strip, kicker + amount at the left, the admin
   step label at the right. Every color and metric is an existing
   --portailetu-* token (charte 1.1 : no literal in component CSS). */
.portailetu-payment-header {
  display: flex;
  align-items: center;
  gap: var(--portailetu-space-3);
  padding: var(--portailetu-space-4) var(--portailetu-space-5);
  border-radius: var(--portailetu-radius-lg);
  background: var(--portailetu-neutral-surface);
}

.portailetu-payment-header__main {
  flex: 1;
  min-width: 0;
}

.portailetu-payment-header__kicker {
  display: block;
  font-size: var(--portailetu-text-xs-size);
  line-height: var(--portailetu-text-xs-line);
  color: var(--portailetu-field-label);
}

.portailetu-payment-header__amount {
  display: block;
  font-size: var(--portailetu-text-xl-size);
  line-height: var(--portailetu-text-xl-line);
  font-weight: var(--portailetu-text-xl-weight);
  font-variant-numeric: tabular-nums;
}

.portailetu-payment-header__step {
  flex: none;
  text-align: right;
  font-size: var(--portailetu-text-xs-size);
  line-height: var(--portailetu-text-xs-line);
  color: var(--portailetu-field-label);
}

/* Step label, then the dossier number under it : two stacked meta lines in the
   right column, same typography (both inherit the block above). */
.portailetu-payment-header__step-label,
.portailetu-payment-header__file {
  display: block;
}

/* Label / value row : no longer emitted by the step body (the amount block
   above replaced it) — kept for the recap panel, which builds these three
   classes in JS, and for the plan dropdown label inside the accordion. */
.portailetu-payment-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}

.portailetu-payment-label {
  font-size: 14px;
  color: #333;
}

.portailetu-payment-value {
  font-size: 14px;
  color: #111;
  font-weight: 600;
}

/* Mode tiles (G1) — replaces the vertical accordion. The grid is ALWAYS three
   columns ("modes en tiers") : a step that allows a single method still shows
   one tile a third of the width, so the block reads the same whatever the
   admin configured. Raw values below have no token : 10px (tile column gap),
   18px (radio dot), 1.5px / 6px (its two border widths), 32px (icon chip),
   font-weight 600 and the 18px label line-height (the sm triplet is 13/16). */
.portailetu-payment-modes__label {
  margin: 0 0 var(--portailetu-space-2);
  font-size: var(--portailetu-text-sm-size);
  line-height: 18px;
  font-weight: 600;
  color: var(--portailetu-field-text);
}

.portailetu-payment-modes__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--portailetu-space-3);
}

/* Selected method's content, below the grid. One visible at a time; the server
   renders the right one on every fresh render, the JS swaps [hidden]. */
.portailetu-payment-modes__panel {
  margin-top: var(--portailetu-space-4);
}

.portailetu-payment-modes__panel[hidden] {
  display: none;
}

.portailetu-payment-mode {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
  box-sizing: border-box;
  padding: var(--portailetu-space-4);
  border: 1px solid var(--portailetu-field-border);
  border-radius: var(--portailetu-radius-lg);
  background: var(--portailetu-field-surface);
  cursor: pointer;
}

/* The REAL radio drives every state : visually hidden with the house pattern
   (admin.css, campaign segmented switch), still focusable and still arrow-key
   navigable inside its group. */
.portailetu-payment-mode__input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(1px, 1px, 1px, 1px);
  clip-path: inset(50%);
  white-space: nowrap;
  margin: -1px;
  border: 0;
  padding: 0;
}

/* G1 groups the two markers at the LEFT of the tile — radio dot then icon
   chip, side by side — instead of pushing them to opposite edges. 10px is the
   same raw gap the tile itself uses (see the block comment above). */
.portailetu-payment-mode__top {
  display: flex;
  align-items: center;
  gap: 10px;
}

.portailetu-payment-mode__dot {
  flex: none;
  box-sizing: border-box;
  width: 18px;
  height: 18px;
  border: 1.5px solid var(--portailetu-field-border);
  border-radius: var(--portailetu-radius-pill);
  background: var(--portailetu-field-surface);
}

.portailetu-payment-mode__chip {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--portailetu-radius-md);
  background: var(--portailetu-neutral-surface);
  color: var(--portailetu-field-label);
}

.portailetu-payment-mode__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.portailetu-payment-mode__title {
  font-size: var(--portailetu-text-base-size);
  line-height: var(--portailetu-text-base-line);
  font-weight: 600;
  color: var(--portailetu-field-text);
}

.portailetu-payment-mode__sub {
  font-size: var(--portailetu-text-xs-size);
  line-height: var(--portailetu-text-xs-line);
  color: var(--portailetu-field-label);
}

.portailetu-payment-mode:hover:not(:has(.portailetu-payment-mode__input:checked)) {
  background: var(--portailetu-neutral-surface);
}

/* Selected : accent border + the declared focus-ring value (no new token). */
.portailetu-payment-mode:has(.portailetu-payment-mode__input:checked) {
  border-color: var(--portailetu-primary);
  box-shadow: 0 0 0 3px var(--portailetu-field-ring-focus);
}

.portailetu-payment-mode:has(.portailetu-payment-mode__input:checked) .portailetu-payment-mode__dot {
  border-width: 6px;
  border-color: var(--portailetu-primary);
}

.portailetu-payment-mode:has(.portailetu-payment-mode__input:checked) .portailetu-payment-mode__chip {
  background: var(--portailetu-accent-surface);
  color: var(--portailetu-primary);
}

.portailetu-payment-mode:has(.portailetu-payment-mode__input:checked) .portailetu-payment-mode__title {
  color: var(--portailetu-primary);
}

/* Keyboard focus on the hidden radio surfaces on the tile it dresses. */
.portailetu-payment-mode:has(.portailetu-payment-mode__input:focus-visible) {
  outline: 2px solid var(--portailetu-primary);
  outline-offset: 2px;
}

/* Narrow viewport : the three-column grid stacks, tiles take the full width.
   640px is the house narrow breakpoint (extranet.css). The installment row
   also relaxes at this width — its own block sits after its base rules, which
   is where it has to be to win the cascade (a media query adds no specificity,
   so only source order separates the two). */
@media (max-width: 640px) {
  .portailetu-payment-modes__grid {
    grid-template-columns: 1fr;
  }
}

/* Installment tiles (G1) — replaces the plan dropdown inside the online panel.
   The section opens on a hairline separator (--portailetu-field-border, the
   same token every hairline on this sheet reads) plus 16px of breathing room.
   Raw values below have no token : 200px (the narrow-viewport tile floor),
   10px (tile gap, the value the mode tile already uses), 18px (radio dot + the
   label line-height, the sm triplet being 13/16), 1.5px / 6px (the dot's two
   border widths), 2px (title to subtitle), font-weight 600 on the label and
   500 on the tile title. The 10px bottom margin is the historical spacing to
   the block below, unchanged. */
.portailetu-payment-installments {
  margin-bottom: 10px;
  padding-top: var(--portailetu-space-4);
  border-top: 1px solid var(--portailetu-field-border);
}

.portailetu-payment-installments__label {
  margin: 0 0 var(--portailetu-space-2);
  font-size: var(--portailetu-text-sm-size);
  line-height: 18px;
  font-weight: 600;
  color: var(--portailetu-field-text);
}

/* NO row cap : the row spans its container, like the mode grid above it. The
   880px cap this rule used to carry described a content column that is not the
   real one (the shell caps content at 70rem, decision 9), so it made a
   "quarter" tile narrower than an actual quarter of the row. Width now comes
   from the container alone; the quarter basis below does the rest. */
.portailetu-payment-installments__grid {
  display: flex;
  gap: var(--portailetu-space-3);
}

.portailetu-payment-plan {
  /* One QUARTER of the row each, whatever the count : the basis is one of four
     equal columns (the three 12px gaps taken off the row first, same token the
     grid gap above reads), and grow 0 keeps that width instead of stretching —
     three allowed tiles occupy three quarters and stay left-aligned. min-width
     0 lets the basis hold below the content width. The longhand after the
     shorthand is deliberate: `flex` resets the basis to auto. */
  flex: 0 0 auto;
  flex-basis: calc((100% - 3 * var(--portailetu-space-3)) / 4);
  min-width: 0;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  box-sizing: border-box;
  padding: var(--portailetu-space-3);
  border: 1px solid var(--portailetu-field-border);
  border-radius: var(--portailetu-radius-md);
  background: var(--portailetu-field-surface);
  cursor: pointer;
}

/* Same visually-hidden pattern as the mode tile radio : still focusable, still
   arrow-key navigable inside its group. */
.portailetu-payment-plan__input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(1px, 1px, 1px, 1px);
  clip-path: inset(50%);
  white-space: nowrap;
  margin: -1px;
  border: 0;
  padding: 0;
}

.portailetu-payment-plan__dot {
  flex: none;
  box-sizing: border-box;
  width: 18px;
  height: 18px;
  border: 1.5px solid var(--portailetu-field-border);
  border-radius: var(--portailetu-radius-pill);
  background: var(--portailetu-field-surface);
}

.portailetu-payment-plan__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.portailetu-payment-plan__title {
  font-size: var(--portailetu-text-base-size);
  line-height: var(--portailetu-text-base-line);
  font-weight: 500;
  color: var(--portailetu-field-text);
}

.portailetu-payment-plan__sub {
  font-size: var(--portailetu-text-xs-size);
  line-height: var(--portailetu-text-xs-line);
  color: var(--portailetu-field-label);
}

.portailetu-payment-plan:hover:not(:has(.portailetu-payment-plan__input:checked)) {
  background: var(--portailetu-neutral-surface);
}

.portailetu-payment-plan:has(.portailetu-payment-plan__input:checked) {
  background: var(--portailetu-accent-surface);
  border-color: var(--portailetu-primary);
}

.portailetu-payment-plan:has(.portailetu-payment-plan__input:checked) .portailetu-payment-plan__dot {
  border-width: 6px;
  border-color: var(--portailetu-primary);
}

.portailetu-payment-plan:has(.portailetu-payment-plan__input:checked) .portailetu-payment-plan__title {
  color: var(--portailetu-primary);
}

/* Keyboard focus on the hidden radio surfaces on the tile it dresses. */
.portailetu-payment-plan:has(.portailetu-payment-plan__input:focus-visible) {
  outline: 2px solid var(--portailetu-primary);
  outline-offset: 2px;
}

/* Narrow viewport, same 640px house breakpoint as the mode grid above. The row
   wraps instead of dividing further, and 200px becomes the tile floor (it
   overrides the min-width: 0 of the base rule, which is why this block must
   come after it — a media query carries no extra specificity). Grow is given
   back here for the same reason: the base rule holds the quarter width, this
   block keeps the historical wrap behaviour where a tile alone on its wrapped
   line takes the full width. */
@media (max-width: 640px) {
  .portailetu-payment-installments__grid {
    flex-wrap: wrap;
  }

  .portailetu-payment-plan {
    flex-grow: 1;
    min-width: 200px;
  }
}

/* Provisional schedule panel (P4, charte v2 §13.3) — recessed block under the
   installment tiles, inside the online panel. It carries no width cap of its
   own: like the tile row above it, it spans its container, and the width comes
   from the shell content column alone.
   Raw values below have no token : 22px (rank pastille, and the header spacer
   that reserves its column), 110px / 88px (the date and amount columns), 10px
   (row padding and the notes offset, the value this whole payment area already
   spaces with), 18px (line-height of the 13px role — the sm triplet is 13/16,
   the charte asks 13/18, same case as the installments label above), the
   line-height 1 that centers the rank digit in its circle, and the weights
   600 (title, rank) / 500 (column headers, amount). */
.pe-payment-schedule {
  display: flex;
  flex-direction: column;
  gap: var(--portailetu-space-2);
  margin: 0 0 10px;
  padding: var(--portailetu-space-4) var(--portailetu-space-5);
  border-radius: var(--portailetu-radius-lg);
  background: var(--portailetu-neutral-surface);
}

/* display:flex above would beat the UA [hidden] rule, so the hidden state is
   declared here — same pattern as the method panel that hosts this block. */
.pe-payment-schedule[hidden] {
  display: none;
}

.pe-payment-schedule__head {
  display: flex;
  align-items: baseline;
  gap: var(--portailetu-space-2);
}

.pe-payment-schedule__title {
  flex: 1;
  min-width: 0;
  font-size: var(--portailetu-text-md-size);
  line-height: var(--portailetu-text-base-line);
  font-weight: 600;
  color: var(--portailetu-field-text);
}

.pe-payment-schedule__meta {
  font-size: var(--portailetu-text-xs-size);
  line-height: var(--portailetu-text-xs-line);
  color: var(--portailetu-field-label);
  font-variant-numeric: tabular-nums;
}

/* The column header row and every data row share ONE geometry, so the columns
   cannot drift apart: same flex, same gap, same fixed widths. */
.pe-payment-schedule__cols,
.pe-payment-schedule__row {
  display: flex;
  align-items: center;
  gap: var(--portailetu-space-3);
}

.pe-payment-schedule__cols {
  font-size: var(--portailetu-text-xs-size);
  line-height: var(--portailetu-text-xs-line);
  font-weight: 500;
  color: var(--portailetu-field-label);
}

.pe-payment-schedule__col-rank {
  flex: none;
  width: 22px;
}

.pe-payment-schedule__col-label {
  flex: 1;
  min-width: 0;
}

.pe-payment-schedule__col-date,
.pe-payment-schedule__date {
  flex: none;
  width: 110px;
  text-align: right;
}

.pe-payment-schedule__col-amount,
.pe-payment-schedule__amount {
  flex: none;
  width: 88px;
  text-align: right;
}

.pe-payment-schedule__rows {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
}

.pe-payment-schedule__row {
  padding: 10px 0;
}

/* Hairline BETWEEN rows only: the first row opens on the column headers. */
.pe-payment-schedule__row + .pe-payment-schedule__row {
  border-top: 1px solid var(--portailetu-field-border-locked);
}

.pe-payment-schedule__rank {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 22px;
  height: 22px;
  border-radius: var(--portailetu-radius-pill);
  background: var(--portailetu-neutral-surface);
  color: var(--portailetu-field-muted);
  font-size: var(--portailetu-text-2xs-size);
  line-height: 1;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Row 1 is the installment charged today — the current one, in accent. */
.pe-payment-schedule__row:first-child .pe-payment-schedule__rank {
  background: var(--portailetu-accent-surface);
  color: var(--portailetu-primary);
}

.pe-payment-schedule__label {
  flex: 1;
  min-width: 0;
  font-size: var(--portailetu-text-base-size);
  line-height: var(--portailetu-text-base-line);
  color: var(--portailetu-field-text);
  font-variant-numeric: tabular-nums;
}

.pe-payment-schedule__today {
  font-size: var(--portailetu-text-xs-size);
  color: var(--portailetu-primary);
}

.pe-payment-schedule__date {
  font-size: var(--portailetu-text-sm-size);
  line-height: 18px;
  color: var(--portailetu-field-label);
  font-variant-numeric: tabular-nums;
}

.pe-payment-schedule__amount {
  font-size: var(--portailetu-text-base-size);
  line-height: var(--portailetu-text-base-line);
  font-weight: 500;
  color: var(--portailetu-field-text);
  font-variant-numeric: tabular-nums;
}

.pe-payment-schedule__notes {
  display: flex;
  flex-direction: column;
  gap: var(--portailetu-space-1);
  padding-top: 10px;
  border-top: 1px solid var(--portailetu-field-border);
  font-size: var(--portailetu-text-sm-size);
  line-height: 18px;
  color: var(--portailetu-field-label);
}

.pe-payment-schedule__note {
  margin: 0;
}

/* Closing warning: lighter than the rules above it, it states that nothing is
   engaged yet rather than prescribing anything. */
.pe-payment-schedule__note--final {
  font-size: var(--portailetu-text-xs-size);
  line-height: var(--portailetu-text-xs-line);
  color: var(--portailetu-field-muted);
}

.portailetu-payment-help {
    margin: 0 0 10px;
    color: #555;
}

.pe-payment-method-text {
    margin: 10px 0;
}

.pe-payment-method-text ul,
.pe-payment-method-text ol {
    padding-left: 20px;
    margin: 8px 0;
}

/* Footer note (P5) — closes the step body under the mode panels, on the same
   hairline token every other separator of this payment area reads. Visibility
   is server-rendered and client-toggled through the hidden attribute, so no
   display rule is needed here: a <p> obeys the UA [hidden] rule on its own.
   Raw value: none. */
.pe-payment-footer-note {
  margin: 0;
  padding-top: var(--portailetu-space-4);
  border-top: 1px solid var(--portailetu-field-border);
  font-size: var(--portailetu-text-xs-size);
  line-height: var(--portailetu-text-xs-line);
  color: var(--portailetu-field-muted);
  text-align: left;
}

.portailetu-payment-state {
  margin-bottom: 12px;
  font-size: 13px;
  color: #333;
}

/* Signal banner — the single full-width strip at the top of the payment
   step. Extends .portailetu-notice (padding/radius/margin base); the
   severity variants consume the :root banner tokens only. */
.portailetu-payment-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  width: 100%;
  box-sizing: border-box;
  border-left: 0;
  padding: 10px 14px;
  border-radius: 6px;
  font-size: 14px;
  font-weight: 600;
}

/* Banner icon : inherits the variant's text color (same treatment as the
   extranet card signal banner icon). */
.portailetu-payment-banner__icon {
  display: inline-flex;
  flex: 0 0 auto;
}

.portailetu-payment-banner--warning {
  background: var(--portailetu-warn-surface);
  color: var(--portailetu-warn-text);
}

.portailetu-payment-banner--neutral {
  background: var(--portailetu-neutral-surface);
  color: var(--portailetu-neutral-text);
}

.portailetu-payment-banner__secondary {
  display: block;
  flex-basis: 100%;
  margin-top: 2px;
  font-weight: 400;
}

.portailetu-payment-notice {
  margin-top: 12px;
  font-size: 13px;
}

.portailetu-payment-notice--error {
  color: #b32d2e;
}

.portailetu-payment-notice--info {
  color: #2c3338;
}

/* Payment recap panel (before PSP redirect) */
.pe-payment-recap {
  border: 1px solid #c3c4c7;
  border-radius: 4px;
  padding: 16px;
  margin: 12px 0;
  background: #f9f9f9;
}

.pe-payment-recap h4 {
  margin: 0 0 12px;
  font-size: 15px;
}

.pe-payment-recap__schedule {
  width: 100%;
  border-collapse: collapse;
  margin: 10px 0 14px;
  font-size: 13px;
}

.pe-payment-recap__schedule th,
.pe-payment-recap__schedule td {
  padding: 6px 10px;
  border: 1px solid #ddd;
  text-align: left;
}

.pe-payment-recap__schedule th {
  background: #f0f0f1;
  font-weight: 600;
}

.pe-payment-recap__actions {
  margin-top: 14px;
  display: flex;
  gap: 8px;
  align-items: center;
}

li.portailetu-workflow-step.portailetu-workflow-step--current span {
    font-weight: 600;
    padding: 0px 15px;
    color: var(--portailetu-step-current-text);
    background-color: var(--portailetu-step-current);
    border-radius: 20px;
}

/* Meta subline under the dossier page title (both surfaces) : "Dossier
   CAND-... · Étape X sur N : <label>". ONE shared rule — the extranet resume
   view and the public runner emit the same Dossier_Meta_Subline markup ;
   extranet.css only tightens the gap under the shell h1. Muted meta treatment
   from the existing tokens ; digits in tabular-nums so the X/N pair keeps a
   stable width. */
.portailetu-meta-subline {
  margin: 0 0 var(--portailetu-space-5);
  font-size: var(--portailetu-text-sm-size);
  line-height: var(--portailetu-text-sm-line);
  color: var(--portailetu-field-label);
  font-variant-numeric: tabular-nums;
}

/* Outro paragraph below the level picker block (select + Continuer) on the
   public level-choice form. Muted helper treatment from the existing tokens
   only ; renders nothing at all when the per-type text is empty (the core
   emits no element). */
.portailetu-workflow-level-outro {
  margin: var(--portailetu-space-3) 0 0;
  font-size: var(--portailetu-text-sm-size);
  line-height: var(--portailetu-text-sm-line);
  color: var(--portailetu-field-muted);
}

/* Configured step intro / outro — the two per-step rich-text blocks the admin
   fills in (class-workflow-shortcodes.php emits them around the step body).
   Neither ever carried a typography rule of its own, and no ancestor supplied
   one on the public shell : .pe-public declares color and background but no
   font-size, so both blocks inherited the THEME body scale (Twenty
   Twenty-Five : font-size var(--wp--preset--font-size--large), i.e.
   clamp(1.125rem, …, 1.375rem) = 18px→22px, at font-weight 300) and read as a
   heading. Pinned here on the canonical body-text tokens so they read as plain
   text on every surface. The extranet already resolved 14px through
   .pe-extranet, so only its line-height moves there (1.5 → the 20px token).
   No margin is declared : the block is a bare <div> at 0, and the <p> the
   editor emits inherits the three properties below, so its UA 1em margins now
   scale off 14px instead of the theme scale. */
.portailetu-workflow-step-intro,
.portailetu-workflow-step-outro {
  font-size: var(--portailetu-text-base-size);
  line-height: var(--portailetu-text-base-line);
  font-weight: 400;
  color: var(--portailetu-field-text);
}

/* Identity / auth bar */
.portailetu-auth-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.6rem;
  margin-bottom: 1rem;
  padding: 0.6rem 0.9rem;
  font-size: 0.9em;
  border: 1px solid var(--portailetu-primary);
  border-left-width: 4px;
  border-radius: 6px;
}

.portailetu-auth-bar__lead {
  color: inherit;
}

.portailetu-auth-bar__action {
  font-weight: 600;
  color: var(--portailetu-primary);
}

/*
 * Logged-in variant keeps the plain inline identity line (no card chrome):
 * it is informational, not a call to action.
 */
.portailetu-auth-bar--logged-in {
  display: block;
  padding: 0;
  font-size: 0.85em;
  border: 0;
  border-radius: 0;
}

/*
 * Error state (single round-trip): the logged-out submit posted an email that
 * already maps to an account. The encart mounts red and fades back to the base
 * primary accent once on reload — no real-time transition, no AJAX. Colors come
 * only from the existing --portailetu-* tokens.
 */
.portailetu-auth-bar--error {
  border-color: var(--portailetu-error);
  animation: portailetu-auth-bar-fade 3.6s ease-out forwards;
}

.portailetu-auth-bar--error .portailetu-auth-bar__action {
  animation: portailetu-auth-bar-fade-text 3.6s ease-out forwards;
}

/* Hold the error color for ~2s (55.56% of 3.6s), then run the unchanged
   ~1.6s fade to the primary color. */
@keyframes portailetu-auth-bar-fade {
  0%      { border-color: var(--portailetu-error); }
  55.56%  { border-color: var(--portailetu-error); }
  100%    { border-color: var(--portailetu-primary); }
}

@keyframes portailetu-auth-bar-fade-text {
  0%      { color: var(--portailetu-error); }
  55.56%  { color: var(--portailetu-error); }
  100%    { color: var(--portailetu-primary); }
}

/* Success notice (e.g. account created) */
.portailetu-notice--success {
  padding: 0.6rem 0.9rem;
  margin-bottom: 1rem;
  font-size: 0.9em;
  border-left: 4px solid var(--portailetu-success);
  background: #f3f8eb;
}

/*
 * Pe_Dropdown — front-only font normalization (V18 / Bug B).
 * The menu is portaled to <body> at open time, so theme presets
 * (--wp--preset--font-family--*, --wp--preset--font-size--*) inherited
 * from the site root leak into the dropdown subtree and blow up the
 * option font on front. The checkmark ::before in pe-dropdown.css is
 * positioned for the admin's 13px option; restoring 13px here realigns
 * the checkmark without touching the shared component CSS.
 *
 * Scoped with body:not(.wp-admin) so admin dropdowns are provably
 * untouched. Specificity (0,2,1) beats pe-dropdown.css (0,1,0) without
 * !important.
 */
body:not(.wp-admin) .pe-dropdown__toggle,
body:not(.wp-admin) .pe-dropdown__current-label,
body:not(.wp-admin) .pe-dropdown__search,
body:not(.wp-admin) .pe-dropdown__option,
body:not(.wp-admin) .pe-dropdown__option-label {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
  font-size: 13px;
  line-height: 1.4;
}
/* ======================================================================
   Form-section stepper + section-slice wizard (charte 6.2, chips only —
   the PO sober variant : NO progress bar, NO counter line, NO kicker).
   Rendered by Workflow_Shortcodes::build_form_stepper_html on BOTH
   surfaces (public campaign page and extranet shell) whenever a form
   step carries 2+ section slices. Chips are spans, never links. Every
   color is a --portailetu-* token (charte hex = token defaults) :
   identical-value tokens are reused (#dcdcde=field-border, #f0f0f1=
   field-border-locked, #646970=field-label, #1d2327=field-text,
   #8c8f94=field-muted, #fff=field-surface) ; #e4f0f8 is the
   --portailetu-accent-surface declared in :root above.
   ====================================================================== */

/* Section intertitle — the visible slice heading in wizard mode and the
   inline intertitle in no-JS. Card-title role (charte 1.5 : 18px/1.333/600),
   previously unstyled (browser default). */
.portailetu-form-section__title {
  margin: var(--portailetu-space-6) 0 var(--portailetu-space-4);
  font-size: var(--portailetu-text-lg-size);
  line-height: calc(var(--portailetu-text-lg-line) / var(--portailetu-text-lg-size));
  font-weight: var(--portailetu-text-lg-weight);
  color: var(--portailetu-field-text);
}

/* Focus target on slice change (tabindex=-1 set by the wizard JS) : no ring,
   the moved focus is programmatic, keyboard users land on the heading text. */
.portailetu-form-section__title:focus {
  outline: none;
}

/* Stepper solidaire de .portailetu-form (règle front actée) : même largeur,
   même bord gauche ET même bord droit que le formulaire. Il suit donc le même
   mécanisme (max-width:100%), le plafond venant de la colonne bornée (charte
   §4.1) et non plus d'un cap dur 760px — sans quoi la rangée de chips serait
   plus étroite que les champs sur le parcours public. */
.portailetu-form-stepper {
  max-width: 100%;
  margin: 0 0 var(--portailetu-space-5);
}

/* Wrap constraints (charte 6.2) : flex-wrap + 6px gap ; labels ellipsize ;
   no chip is ever cut, no horizontal overflow. */
.portailetu-form-stepper__chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
}

/* Chip pill : padding 4px 10px 4px 5px at 12px, disc 1.125rem + number
   0.625rem/600, label 0.75rem/1.333. */
.portailetu-form-stepper__chip {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-width: 0;
  flex: 0 1 auto;
  padding: 0.333em 0.833em 0.333em 0.417em;
  font-size: 0.75rem;
  line-height: 1.333;
  border: 1px solid var(--portailetu-field-border);
  border-radius: var(--portailetu-radius-pill);
  background: var(--portailetu-field-surface);
}

.portailetu-form-stepper__chip:last-child {
  flex: none;
}

.portailetu-form-stepper__disc {
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.625rem;
  font-weight: 600;
}

.portailetu-form-stepper__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Per-state colors (charte 6.2). */
.portailetu-form-stepper__chip--upcoming .portailetu-form-stepper__disc {
  background: var(--portailetu-field-border-locked);
  color: var(--portailetu-field-label);
}

.portailetu-form-stepper__chip--upcoming .portailetu-form-stepper__label {
  font-weight: 400;
  color: var(--portailetu-field-label);
}

.portailetu-form-stepper__chip--current {
  background: var(--portailetu-accent-surface);
  border-color: var(--portailetu-primary);
}

.portailetu-form-stepper__chip--current .portailetu-form-stepper__disc {
  background: var(--portailetu-primary);
  color: var(--portailetu-field-surface);
}

.portailetu-form-stepper__chip--current .portailetu-form-stepper__label {
  font-weight: 500;
  color: var(--portailetu-field-text);
}

.portailetu-form-stepper__chip--done .portailetu-form-stepper__disc {
  background: var(--portailetu-primary);
  color: var(--portailetu-field-surface);
  /* The digit yields to the check : same disc, one visible mark. */
  font-size: 0;
}

.portailetu-form-stepper__chip--done .portailetu-form-stepper__disc::before {
  content: "\2713";
  font-size: 0.625rem;
  font-weight: 600;
}

.portailetu-form-stepper__chip--done .portailetu-form-stepper__label {
  font-weight: 400;
  color: var(--portailetu-field-text);
}

/* Slice-in-error chip (server re-render with submission errors) : the
   error PAIR on border and label only, the disc keeps its state colors. */
.portailetu-form-stepper__chip--error {
  border-color: var(--portailetu-field-error);
}

.portailetu-form-stepper__chip--error .portailetu-form-stepper__label {
  color: var(--portailetu-field-error);
}

/* Connector : hairline flex filler between chips ; the last chip stays
   flex:none so the run never stretches it. */
.portailetu-form-stepper__link {
  flex: 1 1 0.5rem;
  min-width: 0.5rem;
  height: 1px;
  background: var(--portailetu-field-border);
}

/* Wizard visibility toggle — the ONLY hiding mechanism : the JS never
   moves, re-parents or clones nodes, so initialized widgets (datepicker,
   Pe_Dropdown) keep working inside hidden slices and every input still
   posts. Declared after the display rules it must win over at equal
   specificity (.portailetu-form-fields, .portailetu-form-actions). */
.portailetu-wizard-off {
  display: none;
}

/* Runner email-confirm comfort : this class is applied by form-wizard.js
   ONLY (on the data-pe-email-confirm-reveal companion while the master
   e-mail is empty, and on the data-pe-email-confirm-change companion while
   the logged-in master keeps its initially rendered value) — never in
   static markup, so a no-JS visitor keeps the permanently visible
   companion. */
.portailetu-email-confirm-off {
  display: none;
}

/* Companion spacing (PO smoke) : the confirmation group is a nested CHILD of
   the e-mail grid cell, so the .portailetu-form-fields grid gap never plays
   between the two boxes and they render glued. Re-create the standard gap
   with the SAME token, scoped to the runner wrapper (the profile and the
   standalone shortcode keep their historical spacing untouched). */
.portailetu-workflow .portailetu-form-group--email-confirm {
  margin-top: var(--portailetu-space-3);
}

/* Pre-boot pending state (anti-flash, PO smoke) : the inline marker script
   inside the stepper block flags the root BEFORE the form below is parsed,
   so the full form is never painted ahead of the wizard slicing. Under the
   flag the sliceable content (form + outro) is hidden and a compact static
   skeleton stands in : two label/field grey bar pairs on existing tokens
   (#f0f0f1 family), NO shimmer, NO animation — the state lives well under
   a second. The wizard JS clears the flag in the same frame it reveals
   slice 1, on every fallback path, and through a 2s safety timeout. */
.portailetu-form-wizard-pending ~ .portailetu-form,
.portailetu-form-wizard-pending ~ .portailetu-workflow-step-outro {
  display: none;
}

.portailetu-form-skeleton {
  display: none;
}

.portailetu-form-wizard-pending .portailetu-form-skeleton {
  display: flex;
  flex-direction: column;
  gap: var(--portailetu-space-3);
  margin-top: var(--portailetu-space-6);
}

.portailetu-form-skeleton__bar {
  display: block;
  background: var(--portailetu-field-border-locked);
}

.portailetu-form-skeleton__bar--label {
  width: 30%;
  height: 0.625rem;
  border-radius: var(--portailetu-radius-sm);
}

.portailetu-form-skeleton__bar--field {
  height: 2.5rem;
  border-radius: var(--portailetu-radius-md);
}

/* Wizard navigation row (charte 2.3/6.2 hierarchy) : secondary square
   Précédent left, primary Valider right, opposite ends. */
.portailetu-form-wizard-nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1.5rem;
}

/* Précédent — the whole charte 2.2 skin now comes from the canonical
   .portailetu-button-secondary family above (lot B1 : this rule was the
   pattern it was copied from). Only what the family does not map stays
   local : the 0.875rem/1.429 pair, and the disabled treatment. */
.portailetu-form-wizard__prev {
  font-size: 0.875rem;
  line-height: 1.429;
}

.portailetu-form-wizard__prev:disabled {
  color: var(--portailetu-field-muted);
  border-color: var(--portailetu-field-border-locked);
  cursor: not-allowed;
}

/* Head-slice focus target (the form itself when the slice has no section
   heading) : programmatic focus, no ring. */
.portailetu-form[tabindex="-1"]:focus {
  outline: none;
}

/* ======================================================================
   EDITORIAL SURFACES — the core element charte of the plugin-owned
   editorial canvases.

   TWO surfaces, and ONLY these two :
     .pe-extranet-page          the "Extranet" page template canvas ;
     .pe-extranet__custom-page  a configured page rendered INSIDE the
                                extranet shell (Extranet_Custom_Page_View).
   Everything below is scoped to one of them, so the editorial dressing
   never leaks onto a normal theme page or into the admin. This
   block lives in front.css and not in a per-surface sheet because front.css
   is the ONLY stylesheet guaranteed loaded on both surfaces (the same reason
   the canonical button families live here).

   Tokens : the shell family (--portailetu-shell-*) for the surfaces, the
   front family for radius and spacing. Both surfaces declare the shell
   family — the extranet shell in extranet.css (.pe-extranet), the template
   canvas in extranet-page.css (.pe-extranet-page) — so the same rule
   resolves identically on either.
   ====================================================================== */

/* Block themes (TT5) cap every direct child of .is-layout-constrained at
   their contentSize through a `> :where(...)` rule (specificity 0,1,0).
   Core groups emit that class, so without this the blocks nested inside a
   group would be re-capped and re-centered against the plugin
   column. Specificity 0,3,0 wins whatever the load order, and the cap is
   released to the surface's own reading column. Scoped to the two plugin
   surfaces — the runner keeps its own acted cap (see the
   .is-layout-constrained > .portailetu-workflow rule). */
.pe-extranet-page .is-layout-constrained > *,
.pe-extranet__custom-page .is-layout-constrained > * {
  max-width: none;
}

/* ---- Core element charte on both surfaces : headings, paragraphs, lists,
   separators, images. Same normalization the choice page applies to its
   editorial (.pe-public__choice-content), so the three editorial surfaces
   read alike whatever base styles wp_head brings. Alignment is never
   touched : a natively centered block keeps its own text-align and its own
   margins. ---- */
.pe-extranet-page__content > :first-child,
.pe-extranet__custom-page > :first-child {
  margin-top: 0;
}

.pe-extranet-page__content > :last-child,
.pe-extranet__custom-page > :last-child {
  margin-bottom: 0;
}

.pe-extranet-page__content p,
.pe-extranet__custom-page p {
  margin: 0 0 var(--portailetu-space-4);
}

.pe-extranet-page__content h2,
.pe-extranet-page__content h3,
.pe-extranet-page__content h4,
.pe-extranet__custom-page h2,
.pe-extranet__custom-page h3,
.pe-extranet__custom-page h4 {
  margin: var(--portailetu-space-6) 0 var(--portailetu-space-2);
  color: var(--portailetu-shell-title);
  line-height: var(--portailetu-text-lg-line);
}

.pe-extranet-page__content h2,
.pe-extranet__custom-page h2 {
  font-size: var(--portailetu-text-xl-size);
  font-weight: var(--portailetu-text-xl-weight);
}

.pe-extranet-page__content h3,
.pe-extranet__custom-page h3 {
  font-size: var(--portailetu-text-lg-size);
  font-weight: var(--portailetu-text-lg-weight);
}

.pe-extranet-page__content h4,
.pe-extranet__custom-page h4 {
  font-size: var(--portailetu-text-md-size);
  font-weight: var(--portailetu-text-md-weight);
}

.pe-extranet-page__content ul,
.pe-extranet-page__content ol,
.pe-extranet__custom-page ul,
.pe-extranet__custom-page ol {
  margin: 0 0 var(--portailetu-space-4);
  padding-left: var(--portailetu-space-6);
}

.pe-extranet-page__content li,
.pe-extranet__custom-page li {
  margin-bottom: var(--portailetu-space-1);
}

/* Editorial links only. Anchors that are ATTACHMENTS of a canonical button
   family are excluded the same way the editor button block already is :
   their label color belongs to the family (white on the primary pill), and
   this rule out-specifies the family (0,2,1 against 0,1,0), so without the
   exclusion an encarts card CTA renders accent-on-accent and is unreadable. */
.pe-extranet-page__content a:not(.wp-block-button__link):not(.portailetu-button),
.pe-extranet__custom-page a:not(.wp-block-button__link):not(.portailetu-button) {
  color: var(--portailetu-shell-accent);
}

.pe-extranet-page__content a:not(.wp-block-button__link):not(.portailetu-button):hover,
.pe-extranet__custom-page a:not(.wp-block-button__link):not(.portailetu-button):hover {
  color: var(--portailetu-primary-hover);
}

.pe-extranet-page__content img,
.pe-extranet__custom-page img {
  max-width: 100%;
  height: auto;
}

.pe-extranet-page__content hr,
.pe-extranet__custom-page hr {
  margin: var(--portailetu-space-6) 0;
  border: none;
  border-top: 1px solid var(--portailetu-shell-border);
}

/* Buttons block : the row spacing only — the pill / squared SKIN comes from
   the canonical families above, where the two surfaces are declared as
   co-selectors on .wp-block-button__link. */
.pe-extranet-page__content .wp-block-buttons,
.pe-extranet__custom-page .wp-block-buttons {
  margin: var(--portailetu-space-4) 0 0;
}

/* ======================================================================
   ENCARTS COLUMN — the centered reading column of a "Page à encarts"
   rendered INSIDE the extranet shell (Extranet_Custom_Page_View, encarts
   regime : no panel). It holds the title, the editorial and the cards
   stack, so the three read as one column and the cards fill it edge to
   edge.

   Geometry is the reference reading column cloned, declaration for
   declaration : public-shell.css
   ".pe-public__content:has(> .pe-public__choice-content)" (60rem cap +
   margin-inline auto) — the same pair extranet-page.css already gives the
   public canvas (.pe-extranet-page__content), which is why that surface
   needs no wrapper of its own. The 48/32 BREATHING is deliberately NOT
   copied : the shell's own page padding (.pe-extranet__main, charte §4 :
   32px) already owns it here, and restating it would double it.

   Unscoped on purpose, exactly like the cards block below : the markup is
   only ever emitted by the view, and only on a page carrying the template.

   The READING ROLE of the chapeau (the editorial above the cards) is the
   choice page's, declaration for declaration : public-shell.css
   ".pe-public__choice-content" (text-md 15/24, base weight, shell-text).
   It is carried by the column itself rather than by a wrapper of its own,
   the same way the public canvas carries it on
   extranet-page.css ".pe-extranet-page__content" — one reading role, three
   surfaces. Card typography is fully explicit below, so nothing in the
   stack inherits it by accident.
   ====================================================================== */
.pe-encarts__column {
  max-width: 60rem;
  margin-inline: auto;
  font-size: var(--portailetu-text-md-size);
  line-height: var(--portailetu-text-md-line);
  font-weight: var(--portailetu-text-base-weight);
  color: var(--portailetu-shell-text);
}

/* ======================================================================
   ENCARTS CARDS — the cards stack of a "Page à encarts"
   (Encarts_Cards_Renderer).

   Values are the choice-page cards verbatim (public-shell.css
   ".pe-public__choice-card" family : same surface, same hairline, same
   card radius, the 20/24 inset, the 44px accent tile, the same narrow
   stacking rule), so the two card families read as one system. They live
   HERE and not in public-shell.css because a "Page à encarts" renders on
   the plugin canvas and inside the extranet shell, never on the public
   campaign shell — and front.css is the only stylesheet guaranteed loaded
   on both of those surfaces.

   Unscoped on purpose : the markup is only ever emitted by the renderer,
   which itself only runs on the two surfaces above.
   ====================================================================== */
.pe-encarts__cards {
  list-style: none;
  margin: var(--portailetu-space-6) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--portailetu-space-3);
}

.pe-encarts__card {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 20px 24px;
  background: var(--portailetu-shell-surface);
  border: 1px solid var(--portailetu-shell-border);
  border-radius: var(--portailetu-radius-xl);
}

/* 44px accent-surface square, accent icon (20px, currentColor via the
   tile color). */
.pe-encarts__card-pastille {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: var(--portailetu-radius-lg);
  background: var(--portailetu-accent-surface);
  color: var(--portailetu-shell-accent);
}

.pe-encarts__card-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.pe-encarts__card-title {
  font-size: 16px;
  font-weight: 600;
  color: var(--portailetu-shell-text);
}

/* The optional subtitle sits exactly where the choice card puts its dates
   line, and wears its treatment. */
.pe-encarts__card-subtitle {
  font-size: 12px;
  color: var(--portailetu-shell-muted);
}

.pe-encarts__card-desc {
  font-size: 13px;
  color: var(--portailetu-shell-muted);
}

.pe-encarts__card-desc p {
  margin: 0;
}

.pe-encarts__card-desc a {
  color: var(--portailetu-shell-accent);
}

.pe-encarts__card-desc a:hover {
  color: var(--portailetu-shell-accent-hover);
}

/* Right-side CTA. The SKIN is the canonical primary pill family above
   ("portailetu-button portailetu-button-primary" in the markup) ; this
   class carries COMPOSITION only : how the pill sits in the card row. */
.pe-encarts__card-cta {
  flex: 0 0 auto;
  white-space: nowrap;
}

.pe-encarts__card-cta-icon {
  display: inline-flex;
}

/* Narrow : stack the row, shrink the tile, drop the pill full-width below
   the text (choice-card responsive rule). */
@media (max-width: 600px) {
  .pe-encarts__card {
    flex-direction: column;
    align-items: stretch;
  }
  .pe-encarts__card-pastille {
    width: 40px;
    height: 40px;
  }
  .pe-encarts__card-cta {
    width: 100%;
    justify-content: center;
  }
}

/* ======================================================================
   PRODUCT 404 CARD (micro-charte 404) — the single "page introuvable" of
   the whole plugin (Not_Found_Card).

   It lives HERE and not in a per-surface sheet because the card renders on
   BOTH shells : inside the extranet shell for a connected visitor, inside
   the public shell for an anonymous one — and front.css is the only
   stylesheet guaranteed loaded on either (the same reason the canonical
   button families and the encarts cards live here).

   Tokens : the shell family (--portailetu-shell-*) for the surface, the
   front family for radius, spacing and type. Both shells declare the shell
   family, so the same rule resolves identically on each.

   Unscoped on purpose, exactly like the encarts cards : the markup is only
   ever emitted by Not_Found_Card, which only the two shells place.
   ====================================================================== */
.pe-404 {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--portailetu-space-6);
  padding: 32px 32px 36px;
  background: var(--portailetu-shell-surface);
  border: 1px solid var(--portailetu-shell-border);
  border-radius: var(--portailetu-radius-xl);
  text-align: center;
  /* No shadow : charte 404, the card is flat. */
}

/* The figure IS the page title. Tabular figures so the three glyphs keep
   identical advance widths whatever the font's default numeral set.

   BASE = the solid darker light tone : that IS the fallback where
   background-clip:text is unsupported (never the full accent, never a
   colored band behind, no text-stroke, no shadow). The hatch is layered on
   top only under the @supports guard right below. */
.pe-404__figure {
  margin: 0;
  font-size: 128px;
  line-height: 116px;
  font-weight: 700;
  letter-spacing: -0.05em;
  font-variant-numeric: tabular-nums;
  color: var(--portailetu-accent-hatch);
}

/* Hatch INSIDE the glyphs : 45deg stripes of the two light accent tones,
   clipped to the text. Progressive enhancement — a browser without
   background-clip:text keeps the solid figure above untouched. */
@supports (background-clip: text) or (-webkit-background-clip: text) {
  .pe-404__figure {
    background-image: repeating-linear-gradient(
      45deg,
      var(--portailetu-accent-hatch) 0 5px,
      var(--portailetu-accent-surface) 5px 10px
    );
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

.pe-404__text {
  max-width: 25rem;
  margin-inline: auto;
}

.pe-404__title {
  margin: 0;
  font-size: var(--portailetu-text-lg-size);
  line-height: var(--portailetu-text-lg-line);
  font-weight: var(--portailetu-text-lg-weight);
  color: var(--portailetu-shell-title);
}

.pe-404__desc {
  margin: var(--portailetu-space-2) 0 0;
  font-size: var(--portailetu-text-base-size);
  line-height: var(--portailetu-text-base-line);
  color: var(--portailetu-shell-muted);
}

/* Substituted editorial (configured 404 page). It spans the card and keeps
   its natural reading alignment : it is page content run through the content
   pipeline, not the card's own centered copy. */
.pe-404__custom {
  width: 100%;
  text-align: left;
}

/* The ONE button. The SKIN is the canonical primary pill family above
   ("portailetu-button portailetu-button-primary" in the markup) ; this class
   carries COMPOSITION only. */
.pe-404__cta {
  flex: 0 0 auto;
  white-space: nowrap;
}

.pe-404__cta-icon {
  display: inline-flex;
}

@media (max-width: 600px) {
  .pe-404 {
    gap: var(--portailetu-space-5);
    padding: 24px 16px 28px;
  }
  .pe-404__figure {
    font-size: 88px;
    line-height: 80px;
  }
  .pe-404__title {
    font-size: 16px;
    line-height: 22px;
  }
  .pe-404__desc {
    font-size: 13px;
    line-height: 18px;
  }
  .pe-404__cta {
    width: 100%;
    justify-content: center;
  }
}
