/**
 * Public campaign shell (canvas) — LOT 2.3a commit 2.
 *
 * Out-of-theme chrome for the shortcode-bearing public campaign pages,
 * served by Public_Shell's template override. Same color policy as the
 * extranet canvas (extranet.css) :
 *   - brand accents reuse the existing --portailetu-* brand tokens
 *     (front.css :root, loaded first — portailetu-front is a dependency) ;
 *   - structural NEUTRALS are the plugin's existing WP-admin greys, lifted
 *     into --portailetu-shell-* tokens below (same names and values as the
 *     extranet block, scoped here under .pe-public because extranet.css
 *     never loads on these pages). No raw hex in rules, no CSS system
 *     colors, no color-mix off the primary.
 *
 * Layout : a sticky full-height LEFT RAIL (back button now, macro-stepper
 * slot reserved for the next lot) + a RIGHT COLUMN (top bar : brand + auth,
 * then the main region hosting the runner output unchanged inside a
 * left-aligned token-capped content zone).
 *
 * NO container-type on the shell root, the rail, or any ancestor of the
 * rail : layout containment on an ancestor traps a descendant's viewport
 * position:sticky (profile save bar lesson, front.css / extranet.css), and
 * the rail below IS position:sticky.
 */

.pe-public {
  /* Neutral role tokens — the extranet shell's WP-admin neutrals, same
     values (extranet.css .pe-extranet block), declared here because that
     sheet never loads on the public pages. */
  --portailetu-shell-surface: #fff;
  --portailetu-shell-surface-muted: #f6f7f7;
  --portailetu-shell-surface-sunken: #f0f0f1;
  --portailetu-shell-border: #dcdcde;
  --portailetu-shell-border-strong: #8c8f94;
  --portailetu-shell-text: #1d2327;
  /* Copied verbatim from extranet.css:70 (.pe-extranet block) so the head h1
     rule lifted below from the extranet title resolves the same colour on the
     public shell, where extranet.css never loads. No new value : it aliases
     --portailetu-shell-text, already #1d2327 above. */
  --portailetu-shell-title: var(--portailetu-shell-text);
  --portailetu-shell-muted: #646970;
  --portailetu-shell-accent: var(--portailetu-primary);
  /* Solid accent hover (CD micro-charte "Shell public" : the "Continuer"
     pill darkens to #17669a on hover). New value in this codebase — the
     runner's primary buttons keep their opacity-veil hover ; this token
     serves the shell card CTA only. */
  --portailetu-shell-accent-hover: #17669a;
  --portailetu-shell-bg: var(--portailetu-shell-surface-muted);

  /* Geometry tokens. Rail width aligned on the extranet sidebar value
     (charte 5.1, 15.5rem = 248px) ; the sub-1280px rail width lives in the
     icon-rail media block. Content cap : NEUTRALIZED (none) after PO smoke —
     the public main must be geometrically indistinguishable from the
     logged-in extranet main until the global two-thirds pass (LOT 2.3c)
     adjusts BOTH surfaces together. The token stays declared as that future
     adjustment point. */
  --portailetu-public-rail-w: 15.5rem;
  --portailetu-public-content-max: none;
  /* Choice-page reading column cap. Distinct from the neutralized
     --portailetu-public-content-max above (which stays `none`, so the runner
     and level mains keep their acted full-width geometry until the global
     two-thirds pass) : the choice editorial + cards + resume encart read best
     in a centered ~960px column rather than spanning the full viewport. */
  --portailetu-choice-content-max: 60rem;

  /* Top-bar vertical rhythm. The bar height is a min-height token (not the
     tallest control's incidental height) so BOTH auth states resolve to the
     same bar : the anonymous state's "Se connecter" secondary button is the
     taller control (~63px bar) ; the logged-in identity + logout are plain
     text (~49px bar before this token). Unifying on the taller value is the
     single accepted visual change (the logged-in bar grows to match the
     anonymous one ; every control stays centered). */
  --portailetu-public-topbar-min-h: 63px;

  margin: 0;
  background: var(--portailetu-shell-bg);
  color: var(--portailetu-shell-text);
}

/* Shell wrapper : rail | column row. align-items stretch (default) keeps the
   column full height ; the rail handles its own viewport pinning. */
.pe-public__shell {
  display: flex;
  min-height: 100vh;
}

/* Shell background — the charte #f6f7f7 fill, on the rail-bearing runner
   journey only (:has(> .pe-public__rail) : the rail is emitted only by
   render_main). The railless level screen and the choice page keep the base
   .pe-public background, untouched. Full-width : the shell is the first layer
   under body and stays edge to edge — no frame, no max-width, no radius. */
.pe-public__shell:has(> .pe-public__rail) {
  background: var(--portailetu-shell-bg);
}

/* ======================================================================
   Left rail — sticky full viewport height (extranet sidebar mechanism :
   position:sticky + top:0 + height:100vh + align-self:flex-start).
   ====================================================================== */
.pe-public__rail {
  display: flex;
  flex-direction: column;
  flex: 0 0 var(--portailetu-public-rail-w);
  width: var(--portailetu-public-rail-w);
  /* Total rendered width parity with the extranet sidebar : extranet.css
     runs a scoped border-box reset, so its 15.5rem INCLUDES the 1rem padding
     and the 1px border. Declared here explicitly (no universal reset in this
     sheet — the runner content keeps the theme-era model it was smoked in). */
  box-sizing: border-box;
  padding: 1rem;
  gap: 0.5rem;
  background: var(--portailetu-shell-surface);
  border-right: 1px solid var(--portailetu-shell-border);
  position: sticky;
  top: 0;
  align-self: flex-start;
  height: 100vh;
}

/* Back button — charte 2.2 SECONDARY, exact values (same application as
   .portailetu-form-wizard__prev, front.css) : white surface, #1d2327 text,
   #dcdcde border, radius-md, 0.571em 1.143em, 0.875rem/1.429/400 ; states
   change one color at a time. The field tokens carry those exact charte
   values (front.css :root). */
.pe-public__back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  align-self: flex-start;
  padding: 0.571em 1.143em;
  font-size: 0.875rem;
  line-height: 1.429;
  font-weight: 400;
  background: var(--portailetu-field-surface);
  color: var(--portailetu-field-text);
  border: 1px solid var(--portailetu-field-border);
  border-radius: var(--portailetu-radius-md);
  text-decoration: none;
  cursor: pointer;
}

.pe-public__back:hover {
  background: var(--portailetu-neutral-surface);
}

.pe-public__back:active {
  background: var(--portailetu-field-border-locked);
}

.pe-public__back:focus:not(:focus-visible) {
  outline: none;
}

.pe-public__back:focus-visible {
  outline: 2px solid var(--portailetu-primary);
  outline-offset: 2px;
}

/* Desktop shows the bare "Retour" label — no icon this lot. The chevron
   span in the markup serves ONLY the sub-1280px icon-rail state below. */
.pe-public__back-icon {
  display: none;
}

/* Rail separator between the back block and the stepper block — the
   extranet sidebar's canonical hairline, exact .pe-extranet__menu-sep
   values (extranet.css) : 1px line on the border token, margin 10px 4px.
   Rendered only when the stepper has phases (renderer rule). align-self
   keeps it full-width under the icon rail's centered alignment. */
.pe-public__rail-sep {
  flex: 0 0 auto;
  align-self: stretch;
  height: 1px;
  margin: 10px 4px;
  background: var(--portailetu-shell-border);
}

/* Macro-stepper slot : grows to keep the rail column shaped ; hosts the
   vertical stepper below (empty when the page carried no runner phases). */
.pe-public__stepper-slot {
  flex: 1 1 auto;
  min-height: 0;
}

/* ======================================================================
   Vertical macro stepper (charte 6.1) — the extranet sidebar stepper
   visual values (extranet.css .pe-extranet__stepper-*) under pe-public
   classes, WITHOUT the "PARCOURS · ..." caption line (PO decision), on a
   stretched marker-column mechanic : the column spans the item height and
   the connector GROWS between dots (no fixed connector height — a wrapped
   label can never open a hole in the line). Values copied from
   charte-ui-front-V1.md §6.1 ; every color is a token (identical-value
   tokens reused, zero literals). The current-step color is the ACCENT
   (charte convergence) : the legacy --portailetu-step-current stays with
   the horizontal timeline outside the shell and is not used here.
   ====================================================================== */
.pe-public__stepper {
  list-style: none;
  margin: 0;
  /* Horizontal indentation : the exact .pe-extranet__stepper value (0 4px,
     extranet.css) so the available label width matches the extranet stepper
     zone — the same label wraps the same way in both rails. No nowrap, no
     font shrink, no ellipsis : a long label WRAPS and the growing connector
     below carries the line across the wrap. */
  padding: 0 4px;
}

/* One phase : the marker column + the label, gap 0.75rem (puce vers
   libellé). The children stretch to the item height so the column can run
   the connector down to the next dot ; the 1.25rem item rhythm now lives
   on the label padding + the connector min-height (below), no longer on
   the li. position:relative anchors the icon-rail sr-only label. */
.pe-public__stepper-step {
  position: relative;
  display: flex;
  align-items: stretch;
  gap: 0.75rem;
}

/* Marker column : spans the FULL item height — the fixed-size dot on top,
   the connector growing under it. This is the mechanic that keeps the
   line continuous whatever the label line count. */
.pe-public__stepper-col {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 1.5rem;
}

/* Vertical connector (charte 6.1) : width 2px, centered by the column,
   GROWING between the bottom edge of this dot and the top edge of the
   next — never a fixed height, never overlapping a dot (it stacks below
   the marker in the column flow). min-height keeps the charte 1.25rem
   item rhythm when the label is shorter than the column. The last phase
   carries no connector. */
.pe-public__stepper-col::after {
  content: "";
  flex: 1 1 auto;
  width: 2px;
  min-height: 1.25rem;
  background: var(--portailetu-shell-surface-sunken);
}

.pe-public__stepper-step:last-child .pe-public__stepper-col::after {
  display: none;
}

/* The connector is the accent under a DONE phase, the sunken track
   otherwise. */
.pe-public__stepper-step--done .pe-public__stepper-col::after {
  background: var(--portailetu-shell-accent);
}

/* Marker puce (charte 6.1) : 1.5rem round, ONE state mark. À-venir
   default — white surface, 1px hairline border, the strong-neutral
   number. border-box declared explicitly (no universal reset in this
   sheet, extranet parity) so the dot renders exactly 1.5rem and the
   connector abuts its true bottom edge. */
.pe-public__stepper-marker {
  flex: 0 0 auto;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--portailetu-radius-pill);
  border: 1px solid var(--portailetu-shell-border);
  background: var(--portailetu-shell-surface);
  color: var(--portailetu-shell-border-strong);
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.pe-public__stepper-marker svg {
  display: block;
  width: 0.6875rem;
  height: 0.6875rem;
}

/* Done : filled accent puce, white check 0.6875rem/600. */
.pe-public__stepper-step--done .pe-public__stepper-marker {
  border-color: var(--portailetu-shell-accent);
  background: var(--portailetu-shell-accent);
  color: var(--portailetu-shell-surface);
}

/* Current : white puce, 1px accent border, accent number. */
.pe-public__stepper-step--current .pe-public__stepper-marker {
  border-color: var(--portailetu-shell-accent);
  color: var(--portailetu-shell-accent);
}

/* Label (charte 6.1) : 0.8125rem/1.385 ; current 500 dark, done 400 dark,
   upcoming 400 muted. Nudged down to the marker's optical centre
   (extranet precedent). Carries the charte 1.25rem item bottom rhythm
   (moved off the li so the connector can run the full item height) ; the
   last phase drops it, keeping the retired li-padding geometry. */
.pe-public__stepper-text {
  min-width: 0;
  padding-top: 0.125rem;
  padding-bottom: 1.25rem;
  font-size: 0.8125rem;
  line-height: 1.385;
  color: var(--portailetu-shell-text);
}

.pe-public__stepper-step:last-child .pe-public__stepper-text {
  padding-bottom: 0;
}

.pe-public__stepper-step--current .pe-public__stepper-text {
  font-weight: 500;
}

.pe-public__stepper-step--upcoming .pe-public__stepper-text {
  color: var(--portailetu-shell-muted);
}

/* Visually-hidden utility (the canvas does not load wp-admin's copy).
   Standard WordPress screen-reader-text geometry, scoped to the shell so
   it never leaks into the theme (extranet precedent). Used by the stepper
   phases to convey done/current/upcoming to assistive tech without
   relying on color alone. */
.pe-public .screen-reader-text {
  border: 0;
  clip: rect(1px, 1px, 1px, 1px);
  clip-path: inset(50%);
  height: 1px;
  width: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  position: absolute;
  word-wrap: normal !important;
}

/* ======================================================================
   Right column — top bar + main region.
   ====================================================================== */
.pe-public__column {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* Top bar : HORIZONTAL padding only ; the vertical rhythm is the min-height
   token, and the direct children (brand anchor, auth zone) STRETCH to the full
   bar height (align-items:stretch) with their own internal centering. The bar
   separator is an INSET box-shadow, not a border-bottom, on purpose : a real
   border-bottom is 1px of layout the stretched children cannot fill, so the
   brand anchor would stop 1px short of the bar's bottom edge and that hairline
   band would hit-test to the bar itself (default cursor) — the residual
   pointer flicker at the bottom edge. The inset shadow paints the same 1px
   hairline WITHOUT consuming layout height, so the anchor covers the entire
   bar and every point over the brand columns resolves to the anchor. */
.pe-public__topbar {
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  gap: 1rem;
  padding: 0 2rem;
  min-height: var(--portailetu-public-topbar-min-h);
  background: var(--portailetu-shell-surface);
  box-shadow: inset 0 -1px 0 var(--portailetu-shell-border);
}

/* Brand link — the whole brand block is the way OUT (back-URL accessor), on
   both shell modes, AND the flex container of the brand itself : the logo and
   wordmark are its direct children (former intermediate .pe-public__brand div
   removed), so its hit area is one continuous rectangle covering logo + gap +
   wordmark. It is ALSO a full-height rectangle : the topbar stretches its
   direct children to the full bar height (align-items:stretch on the bar, no
   vertical padding, no border consuming the bottom row), so the anchor fills
   the bar top-to-bottom with NO negative margins, and every point of the bar
   over the brand columns resolves to the anchor (cursor pointer, no flicker).
   Its own align-items:center keeps the logo and wordmark optically centered,
   exactly where they sat before. Layout values (extranet render_brand : 1.5rem
   accent square or 26px-high image, "Espace …" 0.75rem muted, 10px gap) lifted
   verbatim from the former container. The wordmark keeps its own colors : color
   and text-decoration inherit (no underline, no accent recolor), only the
   cursor and the charte focus ring belong to the anchor. */
.pe-public__brand-link {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

.pe-public__brand-link:focus:not(:focus-visible) {
  outline: none;
}

.pe-public__brand-link:focus-visible {
  outline: 2px solid var(--portailetu-shell-accent);
  outline-offset: 2px;
}

.pe-public__logo {
  display: inline-block;
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--portailetu-radius-md);
  background: var(--portailetu-shell-accent);
}

.pe-public__logo--image {
  width: auto;
  height: 26px;
  max-width: 150px;
  border-radius: 0;
  background: none;
  object-fit: contain;
}

.pe-public__brand-name {
  min-width: 0;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--portailetu-shell-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Auth zone (top bar right). */
.pe-public__auth {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex: 0 0 auto;
}

.pe-public__identity {
  font-size: 0.875rem;
  line-height: 1.429;
  color: var(--portailetu-shell-text);
}

.pe-public__logout {
  font-size: 0.875rem;
  line-height: 1.429;
  color: var(--portailetu-shell-accent);
  text-decoration: underline;
}

/* "Se connecter" — charte 2.2 SECONDARY (maquette 17a), same exact values
   as the back button above. */
.pe-public__login {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.571em 1.143em;
  font-size: 0.875rem;
  line-height: 1.429;
  font-weight: 400;
  background: var(--portailetu-field-surface);
  color: var(--portailetu-field-text);
  border: 1px solid var(--portailetu-field-border);
  border-radius: var(--portailetu-radius-md);
  text-decoration: none;
  cursor: pointer;
}

.pe-public__login:hover {
  background: var(--portailetu-neutral-surface);
}

.pe-public__login:active {
  background: var(--portailetu-field-border-locked);
}

.pe-public__login:focus:not(:focus-visible) {
  outline: none;
}

.pe-public__login:focus-visible {
  outline: 2px solid var(--portailetu-primary);
  outline-offset: 2px;
}

/* ======================================================================
   Main region — EXACT extranet main geometry (.pe-extranet__main,
   extranet.css) : flex 1 1 auto, padding var(--portailetu-space-7) = 32px
   all around at every width (the extranet declares no media override),
   min-width 0, no max-width, no centering. The runner's internal widths
   (.portailetu-form 760px, .portailetu-form-stepper 760px) are untouched
   inside it.
   ====================================================================== */
.pe-public__main {
  flex: 1 1 auto;
  padding: var(--portailetu-space-7);
  min-width: 0;
  /* Charte §4.1 : content-box column, so the single-column ceiling below bounds
     the CONTENT with the 32px padding OUTSIDE it (PO value 70rem content →
     1184px hors padding). No universal reset in this sheet, so the column is
     already content-box — declared explicitly for parity with the extranet
     column and robustness against a future reset. */
  box-sizing: content-box;
}

/* Single-column ceiling : the rail-bearing runner journey ("une carte
   principale") caps its content column at 70rem (1120px), residual space to the
   RIGHT (flex-start default — never centered). PO decision : 70rem departs from
   the charte §4.1 canonical 55rem, on purpose. Scoped by the render-time state
   that already distinguishes the families : the journey's content zone carries
   .pe-public__panel, which the railless level screen and the choice page (both
   their own centered layouts, out of scope) never emit. */
.pe-public__main:has(> .pe-public__panel) {
  max-width: 70rem;
}

/* Content zone : geometrically neutral while the cap token is `none` (full
   width, like content sitting directly in the extranet main). Kept in the
   markup as the LOT 2.3c adjustment point. */
.pe-public__content {
  max-width: var(--portailetu-public-content-max);
  margin: 0 auto 0 0; /* pinned left — the shell geometry, not the theme's centering */
}

/* Rail-bearing journey : the content zone becomes the twin of the extranet
   resume panel (section.pe-extranet__panel). SURFACE TREATMENT ONLY — the
   geometry (max-width / left-pin) stays on the base .pe-public__content rule
   above. Surface declarations mirror extranet.css (.pe-extranet__panel) ;
   extranet.css never loads here, hence the copy. No new value :
   --portailetu-shell-surface / --portailetu-shell-border are already declared
   in public-shell.css :root. The corner radius is aligned to the charte §1.3 /
   §3.1 card value (1rem) via the existing --portailetu-radius-xl token ; this
   is a dedicated single-surface radius lot (the .pe-extranet__panel family is a
   shared rule and is deliberately left off-charte here). Padding stays the raw
   24px literal (out of scope). Emitted only on rail-bearing screens
   (render_main, railless=false) — the railless level screen and the choice
   page keep the bare .pe-public__content. */
.pe-public__panel {
  padding: 24px;
  background: var(--portailetu-shell-surface);
  border: 1px solid var(--portailetu-shell-border);
  border-radius: var(--portailetu-radius-xl);
}

/* Journey head h1, ABOVE the card (mirror of the extranet resume view's title
   above its panel). Declarations copied verbatim, declaration for declaration,
   from extranet.css:1491-1497 (.pe-extranet__profile-title) so the pe-public
   head reads identically to PATH A ; extranet.css never loads here, hence the
   copy. No new value : every token above is already declared in front.css :root
   (the type triplet) or public-shell.css :root (--portailetu-shell-title). The
   shared meta subline (.portailetu-meta-subline) keeps its front.css rule,
   already loaded on the public shell, so it is not copied. */
.pe-public__workflow-title {
  margin: 0 0 var(--portailetu-space-6);
  font-size: var(--portailetu-text-xl-size);
  line-height: var(--portailetu-text-xl-line);
  font-weight: var(--portailetu-text-xl-weight);
  color: var(--portailetu-shell-title);
}

/* Head spacing, mirror of the extranet resume view. The shared subline's
   front.css rule leaves margin-top 0, so the public title->subline gap sat at
   the title's full space-6 (24px) bottom margin — LARGER than the extranet's.
   Pull the subline back up so the collapsed gap is space-2 (8px), reproducing
   the extranet metric with the SAME override the extranet uses
   (extranet.css:1504-1506), same tokens, no new value. The subline keeps its
   own space-5 bottom margin (front.css) toward the panel. Inert on the level
   screen and the choice page : neither emits .pe-public__workflow-title, so the
   adjacent-sibling selector never matches there. */
.pe-public .pe-public__workflow-title + .portailetu-meta-subline {
  margin-top: calc(var(--portailetu-space-2) - var(--portailetu-space-6));
}

/* ======================================================================
   Icon rail (below 1280px) — mirror of the extranet icon-rail rules
   (extranet.css, charte 5.2) : the rail narrows to 3.5rem, the back button
   becomes a centered 2.25rem icon button (the mirrored chevron shows, the
   label moves into the pure-CSS tooltip right of the rail). The rail
   PERSISTS at every smaller width : never masked, never an overlay, never
   a burger.
   ====================================================================== */
@media (max-width: 1280px) {
  .pe-public__rail {
    flex-basis: 3.5rem;
    width: 3.5rem;
    padding: 1rem 0;
    align-items: center;
    /* The sticky rail must paint its overhanging tooltip OVER the main
       column (extranet precedent : z-index 2 wins over positioned view
       content). */
    z-index: 2;
  }

  .pe-public__back {
    position: relative;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    justify-content: center;
    align-self: center;
  }

  /* ONE chevron-right glyph mirrored by CSS (news-chevron precedent) so the
     rail state points back/left without a new glyph. */
  .pe-public__back-icon {
    display: inline-flex;
    transform: scaleX(-1);
    color: var(--portailetu-shell-muted);
  }

  /* The label IS the tooltip (extranet .pe-extranet__menu-label values,
     charte 5.2) : right of the rail, offset 0.5rem, dark surface, light text
     0.75rem/1.333, padding 0.25rem 0.625rem, radius-md, low shadow, nowrap,
     shown immediately on hover AND keyboard focus, hidden by opacity so the
     accessible name never leaves the tree. */
  .pe-public__back-label {
    position: absolute;
    left: calc(100% + 0.5rem);
    top: 50%;
    transform: translateY(-50%);
    background: var(--portailetu-shell-text);
    color: var(--portailetu-shell-surface);
    font-size: 0.75rem;
    line-height: 1.333;
    font-weight: 400;
    padding: 0.25rem 0.625rem;
    border-radius: var(--portailetu-radius-md);
    box-shadow: var(--portailetu-shadow-low);
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity 120ms ease-out;
  }

  .pe-public__back:hover .pe-public__back-label,
  .pe-public__back:focus .pe-public__back-label {
    opacity: 1;
  }

  /* Macro stepper in the icon rail (extranet precedent) : the labels leave
     the visual rail (kept in the markup for assistive tech) and the puces
     align on the rail's centered icon grammar — a compact column of state
     markers, still not clickable. The marker column centers itself (the
     connector is already centered inside it), no reposition needed. The
     desktop 4px horizontal indent is dropped (extranet parity : the rail
     centers the puces, the indent would offset them). */
  .pe-public__stepper {
    padding-left: 0;
    padding-right: 0;
  }

  .pe-public__stepper-text {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }

  .pe-public__stepper-step {
    justify-content: center;
    gap: 0;
  }

  /* Brand collapses to its logo (extranet rail precedent). */
  .pe-public__brand-name {
    display: none;
  }

  .pe-public__topbar {
    padding: 0 1rem;
  }

  /* No main padding override here : the extranet main keeps its 32px padding
     at every width, so the public main does too (geometry parity). */
}

/* ======================================================================
   Level-selection screen (LOT 2.3a step 3 — CD maquette 18a, PO order).
   The maquette LAYOUT is the exception to the shell's left-aligned rule :
   the rail is dropped (railless renderer mode) and BOTH the heading block
   and the card are horizontally CENTERED in the full-width main. Runner
   screens stay left-aligned and untouched.

   Heading block ABOVE the card : kicker + title + intro, centered, the intro
   line capped near 460px for readability.
   ====================================================================== */

/* Vertical centering : the railless main becomes a flex column at least as
   tall as the viewport minus the top bar, and its content block takes auto
   vertical margins so the heading + card pair centres in that height instead
   of hugging the top. dvh preferred (mobile browser chrome), vh declared first
   as the fallback. Content TALLER than the available height collapses the auto
   margins to 0 and flows from the top inside the main's own 32px padding —
   never clipped, never scroll-locked. Scoped by :has(.pe-public__level-card) —
   the level screen's own marker (already the level-scope hook below) — so the
   choice pages (their own flow) and the runner rail view are untouched. */
.pe-public__main:has(.pe-public__level-card) {
  display: flex;
  flex-direction: column;
  min-height: calc(100vh - var(--portailetu-public-topbar-min-h));
  min-height: calc(100dvh - var(--portailetu-public-topbar-min-h));
}

/* Optical centering : a mathematically centred stack reads visually LOW, so
   the free vertical space is split 1/4 ABOVE and 3/4 BELOW the content via two
   flex spacers on the scoped main — lifting the block so its CENTRE lands near
   the upper-third line (anchoring its TOP there, the previous 1/3 - 2/3 split,
   sat the block too low). ::before takes one share, ::after three ; both are
   zero-basis with min-height 0 so they only ever consume LEFTOVER height. When
   the content is taller than the available height both spacers collapse to 0
   and the flow returns to top-aligned inside the main's own 32px padding (the
   F.2 min-height above stays untouched) — never clipped, never scroll-locked. */
.pe-public__main:has(.pe-public__level-card)::before,
.pe-public__main:has(.pe-public__level-card)::after {
  content: "";
  min-height: 0;
}

.pe-public__main:has(.pe-public__level-card)::before {
  flex: 1 1 0;
}

.pe-public__main:has(.pe-public__level-card)::after {
  flex: 3 3 0;
}

.pe-public__main:has(.pe-public__level-card) > .pe-public__content {
  /* Horizontal centering : the flex-column main (above) turns this block into
     a flex item ; its base `margin-right:auto` disables the flex stretch, so
     without a definite width it shrinks to content and the auto right margin
     pins the whole stack LEFT. Cap it at the heading width (the widest child :
     the 28.75rem intro block ; the 27.5rem card centres within) and split the
     inline margins so the stack centres in the full-width main. Vertical
     placement is no longer this block's job : the scoped main's flex spacers
     (::before 1 share, ::after 2 shares) sit the stack on the upper-third line
     (rule-of-thirds optical centering, below). */
  max-width: 28.75rem;
  margin-inline: auto;
}

.pe-public__level-head {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  box-sizing: border-box;
  max-width: 28.75rem;
  margin: 0 auto 1.5rem;
  text-align: center;
}

/* The card hosts the historical level-select form unchanged (same nonce,
   same hidden fields, same Pe_Dropdown call) under charte chrome. Charte
   rule : cards NEVER carry shadows (the CD sheet shows one, the charte
   prevails) ; the floating dropdown panel below is the allowed exception.
   Centered in the main (maquette exception). */
.pe-public__level-card {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  box-sizing: border-box;
  max-width: 27.5rem;
  margin-inline: auto;
  padding: 1.5rem;
  background: var(--portailetu-shell-surface);
  border: 1px solid var(--portailetu-shell-border);
  border-radius: var(--portailetu-radius-xl);
}

/* 1 · Kicker : the campaign year (school_year, else opening-date year). */
.pe-public__level-kicker {
  margin: 0;
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--portailetu-shell-accent);
}

/* 2 · Title : the campaign title. */
.pe-public__level-title {
  margin: 0;
  font-size: 1.125rem;
  line-height: 1.333;
  font-weight: 600;
  color: var(--portailetu-shell-text);
}

/* 3 · Intro : the level_intro_text flow (same filter as everywhere). */
.pe-public__level-intro {
  margin: 0;
  font-size: 0.875rem;
  color: var(--portailetu-shell-muted);
}

/* 4 · Account hint banner. The link URL is wp_login_url() WITHOUT
   redirect_to (deliberate — Front_Access_Guard owns the routing). */
.pe-public__level-hint {
  margin: 0;
  padding: 0.625rem 0.875rem;
  background: var(--portailetu-shell-surface-muted);
  border-radius: var(--portailetu-radius-md);
  font-size: 0.8125rem;
  color: var(--portailetu-shell-muted);
}

.pe-public__level-hint-link {
  color: var(--portailetu-shell-accent);
  font-weight: 500;
  text-decoration: none;
}

.pe-public__level-hint-link:hover {
  color: var(--portailetu-shell-accent-hover);
  text-decoration: underline;
}

/* 6 · Field : the historical label above the Pe_Dropdown. */
.pe-public__level-card .portailetu-workflow-level-select {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5rem;
  margin: 0;
}

.pe-public__level-card .portailetu-workflow-level-select label {
  font-size: 0.75rem;
  color: var(--portailetu-shell-muted);
}

/* Pe_Dropdown closed box — card skin (canonical component, CSS only ; the
   admin sheet's 200-360px width window yields to the card width). */
.pe-public__level-card .pe-dropdown {
  display: block;
  width: 100%;
  min-width: 0;
  max-width: none;
}

.pe-public .pe-public__level-card .pe-dropdown__toggle {
  box-sizing: border-box;
  min-height: 2.5rem;
  padding: 0 0.5rem 0 0.75rem;
  border: 1px solid var(--portailetu-shell-border);
  border-radius: var(--portailetu-radius-md);
  background: var(--portailetu-shell-surface);
  color: var(--portailetu-shell-text);
  font-size: 0.875rem;
  line-height: 1.429;
}

.pe-public .pe-public__level-card .pe-dropdown__toggle:hover {
  border-color: var(--portailetu-shell-border-strong);
}

.pe-public .pe-public__level-card .pe-dropdown__toggle:focus,
.pe-public .pe-public__level-card .pe-dropdown.is-open .pe-dropdown__toggle {
  border-color: var(--portailetu-shell-accent);
  box-shadow: 0 0 0 3px var(--portailetu-field-ring-focus);
}

.pe-public .pe-public__level-card .pe-dropdown__current-label {
  font-size: 0.875rem;
  line-height: 1.429;
}

/* Pe_Dropdown floating panel — shadow ALLOWED here (floating surface).
   The open menu is PORTALED to <body> (pe-dropdown.js), so the card
   ancestor is gone at open time : :has() re-scopes the skin to the pages
   that actually show the card (the level screen and the runner are
   mutually exclusive), keeping every wizard dropdown untouched. */
.pe-public:has(.pe-public__level-card) .pe-dropdown__menu {
  background: var(--portailetu-shell-surface);
  border: 1px solid var(--portailetu-shell-border);
  border-radius: var(--portailetu-radius-md);
  box-shadow: var(--portailetu-shadow-low);
}

.pe-public:has(.pe-public__level-card) .pe-dropdown__option {
  padding: 0.5625rem 0.75rem;
  font-size: 0.875rem;
  line-height: 1.429;
}

.pe-public:has(.pe-public__level-card) .pe-dropdown__option-label {
  font-size: 0.875rem;
  line-height: 1.429;
}

.pe-public:has(.pe-public__level-card) .pe-dropdown__option:hover,
.pe-public:has(.pe-public__level-card) .pe-dropdown__option:focus {
  background: var(--portailetu-shell-surface-muted);
}

.pe-public:has(.pe-public__level-card) .pe-dropdown__option.is-selected {
  background: var(--portailetu-accent-surface);
  color: var(--portailetu-shell-accent);
  font-weight: 500;
  /* Room for the right-side check glyph below. */
  padding-right: 1.75rem;
}

/* The component's simple-mode check ::before is positioned for the admin
   28px LEFT gutter ; the card options drop that gutter (spec padding), so
   the glyph moves to the right edge — same glyph, CSS only. */
.pe-public:has(.pe-public__level-card) .pe-dropdown__menu[role="listbox"]:not([aria-multiselectable="true"]) .pe-dropdown__option.is-selected::before {
  left: auto;
  right: 0.75rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--portailetu-shell-accent);
}

/* 7 · Submit : "Continuer" primary pill + arrow-right glyph, FULL WIDTH
   inside the card (maquette 18a), content centered, the arrow kept at the
   right of the label with the family gap.

   B2 : the SKIN is gone from here. The pill radius, the accent fill, the
   label color, the typography and the color hover are the canonical
   primary's (.portailetu-button-primary, front.css) and are read straight
   from the family — the shell accent tokens this rule used to name resolve
   to the very same values (--portailetu-shell-accent is var(
   --portailetu-primary), --portailetu-shell-accent-hover and
   --portailetu-primary-hover are both #17669a), so the reconciliation is a
   no-op today and only removes the ability to diverge. What stays is pure
   LAYOUT : the full-width block box and its top margin. The veil disarm
   died with the veil itself. */
.pe-public .pe-public__level-card .portailetu-button-primary {
  display: flex;
  width: 100%;
  /* Field-block → submit vertical space DOUBLED (PO) : the form's own row
     gap is 0.5rem (--portailetu-space-2) ; this space-2 top margin adds to
     it, so the level field and the Continuer submit sit 1rem
     (--portailetu-space-4) apart — twice the 0.5rem every other row keeps.
     Scoped to this pair only ; the hint/intro/outro rhythm is untouched. */
  margin-top: var(--portailetu-space-2);
}

/* 8 · Outro : discreet note below the picker block. */
.pe-public__level-outro {
  margin: 0;
  font-size: 0.75rem;
  color: var(--portailetu-shell-muted);
}

/* Below 768px : card full width with the main's side padding (the CTA is
   already full width at every breakpoint above). */
@media (max-width: 768px) {
  .pe-public__level-card {
    max-width: none;
  }

  .pe-public__level-card .portailetu-workflow-level-select {
    align-items: stretch;
  }
}

/* ======================================================================
   Choice page (railless editorial — "Accueil campagnes"). Standard top
   bar + full-width main assembling the page's own title, the_content and
   a resume notice (anonymous only). Left-aligned like the runner main
   (NOT the centered level screen). Tokens only, zero color literals.
   ====================================================================== */

/* Reading column : the shared main / content pair is capped to the choice
   token and horizontally centered on THIS surface only, so the title,
   editorial, cards and resume encart form one comfortable ~960px column
   instead of running against the viewport edges. Scoped by
   :has(> .pe-public__choice-content) — the marker the choice branch alone
   emits — so the runner main and the railless level screen keep their acted
   geometry untouched. The shared main's 32px padding is zeroed on this surface
   so the zone below owns the full breathing budget with no doubling (title
   would otherwise sit 32+48px from the bar). */
.pe-public__main:has(> .pe-public__content > .pe-public__choice-content) {
  padding: 0;
}

.pe-public__content:has(> .pe-public__choice-content) {
  max-width: var(--portailetu-choice-content-max);
  margin-inline: auto;
  padding: 48px 32px;
}

/* Narrow (the choice surface's own restack breakpoint — cards drop to a
   column here too) : tighter zone breathing. */
@media (max-width: 600px) {
  .pe-public__content:has(> .pe-public__choice-content) {
    padding: 24px 16px;
  }
}

/* Page title : the type-scale XL role (charte 1.5), shell-text neutral,
   separated from the editorial flow below. */
.pe-public__choice-title {
  margin: 0 0 var(--portailetu-space-6);
  font-size: var(--portailetu-text-xl-size);
  line-height: var(--portailetu-text-xl-line);
  font-weight: var(--portailetu-text-xl-weight);
  color: var(--portailetu-shell-text);
}

/* Editorial content wrapper : minimal block-typography normalization so the
   core primitives (paragraph, heading, list, image, buttons) read legibly
   inside the theme-agnostic canvas whatever base styles wp_head brings. The
   reading role is text-md (15px / 24px). Scoped to this wrapper so nothing
   leaks to the top bar or the notice. */
.pe-public__choice-content {
  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);
}

.pe-public__choice-content > :first-child {
  margin-top: 0;
}

.pe-public__choice-content > :last-child {
  margin-bottom: 0;
}

.pe-public__choice-content p {
  margin: 0 0 var(--portailetu-space-4);
}

.pe-public__choice-content h2,
.pe-public__choice-content h3,
.pe-public__choice-content h4 {
  margin: var(--portailetu-space-6) 0 var(--portailetu-space-2);
  color: var(--portailetu-shell-text);
  line-height: var(--portailetu-text-lg-line);
}

.pe-public__choice-content h2 {
  font-size: var(--portailetu-text-xl-size);
  font-weight: var(--portailetu-text-xl-weight);
}

.pe-public__choice-content h3 {
  font-size: var(--portailetu-text-lg-size);
  font-weight: var(--portailetu-text-lg-weight);
}

.pe-public__choice-content h4 {
  font-size: var(--portailetu-text-md-size);
  font-weight: var(--portailetu-text-md-weight);
}

.pe-public__choice-content ul,
.pe-public__choice-content ol {
  margin: 0 0 var(--portailetu-space-4);
  padding-left: var(--portailetu-space-6);
}

.pe-public__choice-content li {
  margin-bottom: var(--portailetu-space-1);
}

.pe-public__choice-content a {
  color: var(--portailetu-shell-accent);
}

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

.pe-public__choice-content img {
  max-width: 100%;
  height: auto;
}

/* Editorial core/button blocks are skinned by the canonical front families
   (front.css : the .pe-public co-selectors on .portailetu-button-primary
   and .portailetu-button-secondary). Nothing about them lives here any
   more — a rule at equal specificity in this file would silently win over
   the family, since front.css loads first. */

/* ======================================================================
   Choice cards — one card per visible entry (lot 2.3d). Micro-charte
   "Shell public" §2 values, consumed through the shell tokens (surface,
   border, accent-surface, accent, accent-hover) : zero color literals.
   Each card leads to the campaign's public page via the right-side pill ;
   the whole row is NOT an anchor because a description may carry its own
   <a href> (kses profile), which cannot nest inside another anchor.
   ====================================================================== */
.pe-public__choice-cards {
  list-style: none;
  margin: var(--portailetu-space-6) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--portailetu-space-3);
}

.pe-public__choice-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
   pastille color). */
.pe-public__choice-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-public__choice-card-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

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

.pe-public__choice-card-dates {
  font-size: 12px;
  color: var(--portailetu-shell-muted);
}

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

.pe-public__choice-card-desc p {
  margin: 0;
}

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

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

/* Right-side CTA. The SKIN is the canonical primary pill family
   (front.css : "portailetu-button portailetu-button-primary" in the
   markup) — box, radius, fill, typography and every state live there.
   This class carries COMPOSITION only : how the pill sits in the card
   row. arrow-right glyph like the level card's Continuer CTA. */
.pe-public__choice-card-cta {
  flex: 0 0 auto;
  white-space: nowrap;
}

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

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

/* Resume notice : the neutral accent encart (CD "Shell public" §2 —
   accent-surface wash, accent text + link, 8px radius), under a dedicated
   class so it reads as its own block outside the card context. Separated
   from the editorial flow above. Wording and anonymity condition unchanged. */
.pe-public__resume-notice {
  margin: var(--portailetu-space-6) 0 0;
  padding: 0.625rem 0.875rem;
  background: var(--portailetu-accent-surface);
  border-radius: var(--portailetu-radius-md);
  font-size: var(--portailetu-text-sm-size);
  line-height: var(--portailetu-text-md-line);
  color: var(--portailetu-shell-accent);
}

.pe-public__resume-notice-link {
  color: var(--portailetu-shell-accent);
  font-weight: 500;
  text-decoration: none;
}

.pe-public__resume-notice-link:hover {
  color: var(--portailetu-shell-accent-hover);
  text-decoration: underline;
}
