/* ============================================================
   PILOTE MILITAIRE — CAISSE IN-APP (ADR 0028 §§4-6, lot B2)
   ------------------------------------------------------------
   Deux surfaces : le DIALOG D'ACHAT (.pm-purchase) et l'ÉTAT
   D'ESPACE (.pm-space-state), la vente ambiante d'un espace non
   possédé.

   Teinte : --filiere* / --accent* hérités de body[data-space]
   (pm-tokens.css). Jamais un hex de filière en dur — l'identité de
   filière est ce qui prouve au candidat qu'il achète bien l'espace
   où il se trouve (invariant d'achat, ADR 0028 §2). Les gris du
   panneau sombre, eux, sont locaux : l'app n'a pas de thème sombre.

   Dosage : la filière est un ACCENT — trait, texte, remplissage de
   bouton — jamais un aplat coloré derrière un paragraphe. Les trois
   teintes sont claires ; du texte de corps dessus tomberait sous le
   contraste minimum.
   ============================================================ */

/* ---------------------------------------------------------- Dialog d'achat */
/* Forme deux panneaux du paywall Concours Armée, transposée le 04/08/2026 :
   à gauche le panneau SOMBRE qui raconte (titre, accroche, note + carrousel
   d'avis, prix affiché, garantie), à droite le panneau clair qui détaille et
   conclut (liste numérotée, rangée de prix, CTA, ligne CGV). Tout le doré de
   CA passe en `--filiere` (teintes CLAIRES, faites pour le fond sombre) ; la
   typo reste la charte PM (Geist, prix en Geist Mono). Le CTA est un aplat
   `--filiere` à texte `--filiere-ink` — le couple prévu par pm-tokens.css. */

.pm-purchase {
    /* `position: fixed` explicite : la feuille UA de `dialog:modal` pose déjà fixed,
       mais `position: relative` ici l'écrasait — la boîte se rendait en haut du
       document et défilait avec la page derrière le backdrop (bug mobile). `inset: 0`
       + `margin: auto` la recentre sans dépendre de la feuille UA ; `max-height`
       ci-dessous continue de faire défiler la grille à l'intérieur, pas la boîte. */
    position: fixed;
    inset: 0;
    margin: auto;
    overscroll-behavior: contain;
    border: none;
    border-radius: var(--radius-lg);
    padding: 0;
    width: 100%;
    max-width: min(920px, calc(100vw - 24px));
    /* Filet de sécurité hauteur : le dialog ne dépasse jamais l'écran ; si le
       contenu reste trop haut, c'est la GRILLE qui défile à l'intérieur des
       coins arrondis. dvh pour la barre mobile. */
    max-height: calc(100vh - 16px);
    max-height: calc(100dvh - 16px);
    background: transparent;
    color: var(--text-primary);
    font-family: var(--font-sans);
    box-shadow: 0 24px 60px rgba(16, 18, 22, 0.35);
}

/* Verrou de scroll de page pendant l'ouverture — voir pm-auth-modal.css, même règle
   posée dans les trois feuilles de dialog. */
html.pm-modal-open {
    overflow: hidden;
}

.pm-purchase::backdrop {
    background: rgba(10, 12, 16, 0.55);
}

.pm-purchase__grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    min-height: 420px;
    max-height: calc(100vh - 16px);
    max-height: calc(100dvh - 16px);
    border-radius: var(--radius-lg);
    overflow-x: hidden;
    overflow-y: auto;
}

@media (max-width: 720px) {
    .pm-purchase__grid {
        grid-template-columns: 1fr;
    }
}

/* ---- Panneau gauche, sombre. Le halo bas reprend celui de CA, teinté filière. */
.pm-purchase__left {
    position: relative;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding: 28px 26px;
    background: #121418;
    color: #f5f5f5;
}

.pm-purchase__left::after {
    content: '';
    position: absolute;
    bottom: -40%;
    left: -20%;
    width: 140%;
    height: 70%;
    background: radial-gradient(ellipse at center, var(--filiere-soft, var(--accent-line)) 0%, transparent 70%);
    opacity: 0.65;
    pointer-events: none;
}

.pm-purchase__intro {
    position: relative;
    z-index: 1;
}

/* Identité de filière : l'espace se nomme AVANT le produit — c'est ce qui rend
   l'invariant d'achat lisible à l'écran. */
.pm-purchase__kicker {
    margin: 0 0 12px;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--filiere, var(--accent));
}

.pm-purchase__title {
    margin: 0 0 14px;
    font-size: clamp(1.35rem, 3.2vw, 1.7rem);
    font-weight: 700;
    line-height: 1.2;
    color: #fff;
}

.pm-purchase__lead {
    margin: 0;
    font-size: 0.95rem;
    line-height: 1.55;
    color: rgba(255, 255, 255, 0.88);
}

/* ---- Preuve sociale : avis RÉELS, servis verbatim par le serveur.
   `pre-line` restitue les retours à la ligne des textes d'origine ; ne pas
   remplacer par `nowrap`, ne pas tronquer par ellipse NI par line-clamp (écart
   voulu vs CA) — un avis coupé est un avis retouché. Les cartes sont empilées
   dans une même cellule de grille : la cellule prend la hauteur de la plus
   GRANDE, aucun saut de mise en page en changeant d'avis. ---- */
.pm-purchase__proof {
    position: relative;
    z-index: 1;
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 12px;
    margin: 22px 0;
}

.pm-purchase__proof[hidden] {
    display: none;
}

.pm-purchase__rating {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 8px;
    margin: 0;
    font-size: 0.82rem;
    color: rgba(255, 255, 255, 0.55);
}

.pm-purchase__rating strong {
    font-family: var(--font-mono);
    font-size: 1rem;
    color: #fff;
}

.pm-purchase__rating-link {
    display: inline-flex;
    align-items: baseline;
    gap: 8px;
    color: inherit;
    text-decoration: none;
    cursor: pointer;
}

.pm-purchase__rating-link:hover > *,
.pm-purchase__rating-link:focus-visible > * {
    text-decoration: underline;
    text-underline-offset: 0.15em;
}

.pm-purchase__carousel {
    display: flex;
    align-items: center;
    gap: 8px;
}

.pm-purchase__quote-nav {
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    padding: 0;
    border-radius: 50%;
    border: 1px solid rgba(255, 255, 255, 0.18);
    background: rgba(255, 255, 255, 0.05);
    color: rgba(255, 255, 255, 0.8);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.pm-purchase__quote-nav:hover {
    background: rgba(255, 255, 255, 0.12);
    border-color: rgba(255, 255, 255, 0.35);
    color: #fff;
}

.pm-purchase__quote-nav svg {
    display: block;
}

/* Pile de cartes : toutes dans la cellule 1/1, seule l'active est visible. */
.pm-purchase__quotes {
    flex: 1 1 auto;
    min-width: 0;
    display: grid;
}

.pm-purchase__quote {
    grid-area: 1 / 1;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 16px 18px;
    border-radius: 14px;
    border: 1px solid rgba(255, 255, 255, 0.08);
    background: #1b1e24;
    visibility: hidden;
    opacity: 0;
}

.pm-purchase__quote.is-active {
    visibility: visible;
    opacity: 1;
    animation: pmPurchaseQuoteIn 0.28s ease;
}

/* Fondu + glissement horizontal dans le sens du clic (forme du paywall CA) :
   « suivant » entre par la droite, « précédent » par la gauche. --pm-quote-dx est
   posé par renderQuoteState() sur #pm-purchase-quotes AVANT la bascule de
   `is-active` ; 0 (points / 1er affichage) = simple fondu. Tout le mouvement vit
   dans la keyframe : `animation: none` ci-dessous suffit à tout annuler. */
@keyframes pmPurchaseQuoteIn {
    from { opacity: 0; transform: translateX(var(--pm-quote-dx, 0)); }
    to { opacity: 1; transform: translateX(0); }
}

@media (prefers-reduced-motion: reduce) {
    .pm-purchase__quote.is-active {
        animation: none;
    }
}

/* Exception assumée au « tout en --filiere » du bandeau ci-dessus : les étoiles
   de NOTATION d'avis sont jaunes partout (cf. pm-tokens.css « Notation d'avis »). */
.pm-purchase__stars {
    color: var(--avis-etoile);
    font-size: 0.9rem;
    letter-spacing: 2px;
    line-height: 1;
}

.pm-purchase__quote-text {
    margin: 0;
    white-space: pre-line;
    font-size: 0.85rem;
    line-height: 1.5;
    color: rgba(255, 255, 255, 0.9);
    /* Parité paywall CA : 3 lignes, ni plus (clamp — un texte long est coupé avec …)
       ni moins (réserve). Sans plafond, les cartes étant empilées en grille, le
       conteneur prenait la hauteur du PLUS LONG avis de la filière, cartes masquées
       comprises — un avis-fleuve donnait un « énorme padding » à tous les autres. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    min-height: calc(0.85rem * 1.5 * 3);
}

.pm-purchase__quote-foot {
    display: flex;
    align-items: center;
    gap: 10px;
}

.pm-purchase__quote-avatar {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 0.85rem;
    background: var(--filiere-soft, var(--accent-line));
    color: var(--filiere, var(--accent));
}

.pm-purchase__quote-id {
    display: flex;
    flex-direction: column;
    line-height: 1.3;
    min-width: 0;
}

.pm-purchase__quote-author {
    font-weight: 600;
    font-size: 0.82rem;
    color: #fff;
}

.pm-purchase__quote-date {
    font-size: 0.72rem;
    color: rgba(255, 255, 255, 0.45);
}

.pm-purchase__dots {
    display: flex;
    justify-content: center;
    gap: 6px;
}

.pm-purchase__dot {
    width: 7px;
    height: 7px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.25);
    cursor: pointer;
    transition: background 0.15s, transform 0.15s;
}

.pm-purchase__dot:hover {
    background: rgba(255, 255, 255, 0.45);
}

.pm-purchase__dot.is-active {
    background: var(--filiere, var(--accent));
    transform: scale(1.15);
}

.pm-purchase__provenance {
    margin: 0;
    font-size: 0.72rem;
    line-height: 1.45;
    text-align: center;
    color: rgba(255, 255, 255, 0.45);
}

/* ---- Bas du panneau sombre : le prix AFFICHÉ (le transactionnel vit à droite). */
.pm-purchase__price-block {
    position: relative;
    z-index: 1;
}

.pm-purchase__price-label {
    margin: 0 0 6px;
    font-size: 0.65rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.45);
}

.pm-purchase__price {
    margin: 0;
    font-family: var(--font-mono);
    font-size: clamp(2.2rem, 5vw, 2.8rem);
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.01em;
    color: #fff;
}

.pm-purchase__guarantee {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 14px 0 0;
    font-size: 0.8rem;
    color: rgba(255, 255, 255, 0.5);
}

.pm-purchase__guarantee svg {
    flex-shrink: 0;
    opacity: 0.75;
}

.pm-purchase__price-note {
    margin: 10px 0 0;
    font-size: 0.82rem;
    line-height: 1.5;
    color: var(--filiere, var(--accent));
}

.pm-purchase__price-note[hidden] {
    display: none;
}

/* Fusion mobile des deux lignes du bas de panneau sombre (libellé prix +
   garantie) — construite dans le markup (pm-purchase.js), masquée ici sur
   desktop où les deux éléments d'origine restent affichés séparément. */
.pm-purchase__meta-mobile {
    display: none;
}

/* ---- Panneau droit, clair : la liste numérotée, la rangée de prix, le CTA. */
.pm-purchase__right {
    display: flex;
    flex-direction: column;
    padding: 26px 24px 22px;
    background: var(--surface-card);
    color: var(--text-primary);
}

.pm-purchase__includes {
    list-style: none;
    margin: 0 0 18px;
    padding: 0;
    flex: 1;
}

.pm-purchase__include {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 4px 14px;
    padding: 11px 0;
    border-bottom: 1px solid var(--border-subtle);
}

.pm-purchase__include:first-child {
    padding-top: 0;
}

.pm-purchase__include-num {
    font-family: var(--font-mono);
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-muted);
    line-height: 1.4;
}

.pm-purchase__include-body {
    min-width: 0;
}

.pm-purchase__include-label {
    margin: 0 0 3px;
    font-weight: 600;
    font-size: 0.9rem;
}

.pm-purchase__include-desc {
    margin: 0;
    font-size: 0.8rem;
    line-height: 1.45;
    color: var(--text-secondary);
}

/* Le socle DÉJÀ actif se distingue (ADR 0028 §4-5) : badge sur l'item CSO,
   numéro teinté — un accent, pas un aplat. */
.pm-purchase__include-note {
    margin-left: 8px;
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--filiere-strong, var(--accent-strong));
    white-space: nowrap;
}

.pm-purchase__include--already .pm-purchase__include-num {
    color: var(--filiere-strong, var(--accent-strong));
}

/* Les rangées de prix — l'équivalent du choix de plan CA, réduit à la seule
   offre réelle : paiement unique. Non cliquables, c'est une information. Au
   prix de complément, la rangée du socle DÉJÀ débloqué précède celle du
   produit : la soustraction rendue visible, là où le montant se lit. */
.pm-purchase__plans {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin: 0 0 14px;
}

.pm-purchase__plan {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 10px;
    padding: 11px 12px;
    border: 2px solid var(--filiere-line, var(--accent-line));
    border-radius: 10px;
    background: var(--filiere-soft, transparent);
}

/* Rangée du socle inclus : le gabarit d'une rangée de plan, éteint — c'est un
   acquis, pas un choix. */
.pm-purchase__plan--included {
    border-color: var(--border-subtle);
    background: transparent;
    padding-top: 8px;
    padding-bottom: 8px;
}

.pm-purchase__plan--included .pm-purchase__plan-label {
    font-weight: 600;
    color: var(--text-secondary);
}

.pm-purchase__plan-included-note {
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--filiere-strong, var(--accent-strong));
    white-space: nowrap;
}

.pm-purchase__plan-main {
    min-width: 0;
}

.pm-purchase__plan-label {
    font-weight: 700;
    font-size: 0.88rem;
}

.pm-purchase__plan-meta {
    margin-left: 6px;
    font-size: 0.78rem;
    color: var(--text-secondary);
}

.pm-purchase__plan-price {
    font-family: var(--font-mono);
    font-weight: 600;
    font-size: 0.95rem;
    white-space: nowrap;
}

.pm-purchase__error {
    margin: 0 0 14px;
    padding: 10px 12px;
    border: 1px solid var(--status-danger);
    border-radius: var(--radius-sm);
    background: var(--status-danger-bg);
    color: var(--status-danger);
    font-size: 0.85rem;
    line-height: 1.45;
}

.pm-purchase__error[hidden] {
    display: none;
}

/* CTA : l'aplat de filière à texte encre — le couple `--filiere` /
   `--filiere-ink` prévu par pm-tokens.css (les trois teintes sont claires). */
.pm-purchase__cta {
    display: block;
    width: 100%;
    min-height: 48px;
    border: none;
    border-radius: var(--radius-md);
    padding: 13px 18px;
    background: var(--filiere, var(--accent));
    color: var(--filiere-ink, #1f2937);
    font-family: inherit;
    font-size: 0.95rem;
    font-weight: 700;
    cursor: pointer;
    transition: filter 0.15s var(--ease-out), transform 0.1s var(--ease-out);
}

.pm-purchase__cta:hover:not(:disabled) {
    filter: brightness(0.95);
}

.pm-purchase__cta:active:not(:disabled) {
    transform: scale(0.99);
}

.pm-purchase__cta:disabled {
    opacity: 0.6;
    cursor: progress;
}

.pm-purchase__cta:focus-visible {
    outline: 2px solid var(--filiere-strong, var(--accent-strong));
    outline-offset: 2px;
}

.pm-purchase__legal {
    margin: 12px 0 0;
    font-size: 0.75rem;
    line-height: 1.5;
    color: var(--text-muted);
    text-align: center;
}

.pm-purchase__legal a {
    color: var(--text-secondary);
    text-decoration: underline;
}

/* Croix : posée sur la colonne CLAIRE de droite (desktop) → croix sombre. */
.pm-purchase__close {
    position: absolute;
    top: 10px;
    right: 10px;
    z-index: 2;
    width: 36px;
    height: 36px;
    border: none;
    border-radius: var(--radius-sm);
    background: rgba(16, 18, 22, 0.06);
    color: var(--text-primary);
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}

.pm-purchase__close:hover {
    background: rgba(16, 18, 22, 0.12);
}

/* Écran court OU étroit : le dialog se resserre pour tenir sans scroll ; en
   dernier recours la grille défile à l'intérieur (jamais de scrollbar
   fenêtre). Combinée avec le seuil mobile (720px) — même compaction, pas de
   duplication des paddings/tailles. */
@media (max-width: 720px), (max-height: 860px) {
    .pm-purchase__grid {
        min-height: 0;
    }
    .pm-purchase__left {
        padding: 18px 22px;
    }
    .pm-purchase__right {
        padding: 18px 22px 16px;
    }
    .pm-purchase__title {
        margin-bottom: 10px;
        font-size: clamp(1.25rem, 3vw, 1.5rem);
    }
    .pm-purchase__lead {
        font-size: 0.9rem;
        line-height: 1.45;
    }
    .pm-purchase__proof {
        margin: 12px 0;
        gap: 10px;
    }
    .pm-purchase__quote {
        padding: 12px 14px;
        gap: 8px;
    }
    .pm-purchase__price {
        font-size: clamp(1.9rem, 4.5vw, 2.4rem);
    }
    .pm-purchase__guarantee {
        margin-top: 10px;
    }
    .pm-purchase__includes {
        margin-bottom: 12px;
    }
    .pm-purchase__include {
        padding: 8px 0;
    }
    .pm-purchase__cta {
        padding: 12px 18px;
    }
}

/* Mobile STRICT (pas de repli sur `max-height`, contrairement au bloc
   ci-dessus) : ces trois réductions changent l'INFORMATION affichée, pas
   seulement l'espacement — elles ne doivent jamais s'appliquer à un desktop
   simplement bas (fenêtre courte, largeur normale). */
@media (max-width: 720px) {
    /* Panneau sombre : la ligne prix + la garantie fusionnent en une seule
       ligne compacte (`.pm-purchase__meta-mobile`, construite dans le JS) ;
       les deux éléments d'origine s'effacent. */
    .pm-purchase__price-label,
    .pm-purchase__guarantee {
        display: none;
    }
    .pm-purchase__meta-mobile {
        display: flex;
        align-items: center;
        gap: 6px;
        margin: 10px 0 0;
        font-size: 0.78rem;
        line-height: 1.3;
        color: rgba(255, 255, 255, 0.6);
    }
    .pm-purchase__meta-mobile svg {
        flex-shrink: 0;
        opacity: 0.75;
    }

    /* Liste des points : numéro + titre seulement, interligne serré ; les
       descriptions sont masquées plutôt que retirées du markup (JS partagé
       avec le desktop). */
    .pm-purchase__include {
        grid-template-columns: auto 1fr;
        gap: 2px 10px;
        padding: 7px 0;
    }
    .pm-purchase__include-label {
        margin: 0;
    }
    .pm-purchase__include-desc {
        display: none;
    }

    /* Test sans catégories (décision Bastien 15/08) : toute la liste
       numérotée disparaît, titres compris — pas seulement les descriptions
       ci-dessus. Sélecteur par id : bat la spécificité de la règle de base
       `.pm-purchase__includes` (flex:1) sans dépendre de l'ordre en cascade. */
    #pm-purchase-includes {
        display: none;
    }

    /* Le CTA hérite alors du padding-top du panneau (26px) mais se
       retrouvait collé au bord bas, juste au-dessus du texte légal ; un peu
       d'air des deux côtés pour qu'il respire seul dans le panneau clair. */
    .pm-purchase__right {
        padding-top: 32px;
        padding-bottom: 26px;
    }

    /* Rangée(s) de plan : information redondante avec le prix déjà affiché
       dans le panneau sombre — masquée, SAUF cas upgrade où elle explique le
       socle déjà débloqué et le prix de complément (data-tier posé par
       open() dans pm-purchase.js). */
    #pm-purchase-plan {
        display: none;
    }
    #pm-purchase-dialog[data-tier="upgrade"] #pm-purchase-plan {
        display: flex;
    }

    /* En 1 colonne la croix est sur le panneau SOMBRE (en haut) → croix claire.
       Placé APRÈS la règle de base `.pm-purchase__close` (même spécificité) :
       un override plus haut dans le fichier perdait la cascade et rendait la
       croix sombre sur fond sombre, quasi invisible (relecture du 15/08). */
    .pm-purchase__close {
        background: rgba(255, 255, 255, 0.18);
        color: #fff;
    }
}

/* ------------------------------------------------------- État d'espace */
/* Bloc PERMANENT d'un espace non possédé (ADR 0028 §4). Même largeur que le
   bandeau d'état — il parle lui aussi de l'accès au produit, pas du contenu de
   la vue, et ne s'aligne donc pas sur la carte. Placé SOUS le bandeau : celui-ci
   informe (solde d'essais), celui-ci propose. */

/* ⚠️ `#pmSpaceState` est frère de `.view-panel` (dashboard.html:302), PAS enfant
   — il n'est donc PAS plafonné par `--dash-page-max` (1400px, dashboard.css:154)
   comme le reste du contenu. Préexistant, assumé : le bandeau reste pleine
   largeur du `.dash-main` même sur les très grands écrans. */

/* Restylée le 15/08/2026 dans l'esprit des bandes CTA de la vitrine PM (ADR 0055,
   variante B du prototype docs/prototypes/2026-08-15-cta-etat-espace/) : panneau navy
   dégradé, lignes horizontales fines, coins en L, kicker tracké, filigrane par filière.
   Seul endroit de l'app qui pose son propre fond sombre — `color-scheme: light` de
   pm-tokens.css reste inchangé partout ailleurs. */

.pm-space-state:not([hidden]) {
    position: relative;
    overflow: hidden;
    width: 100%;
    margin: 0 0 20px;
    border-radius: 20px;
    background: radial-gradient(120% 140% at 85% 0%, var(--pm-navy-700), var(--pm-navy-950) 62%);
    padding: clamp(1.5rem, 3vw, 2.5rem);
    color: var(--pm-text-on-ink);
    /* Toute la bannière ouvre la vente au clic (décision Bastien du 15/08) : le
       curseur le signale sur l'ensemble, le bouton garde en plus son propre hover
       ci-dessous pour rester lisible comme LE contrôle. */
    cursor: pointer;
    /* Les 3 dispositions (empilé / rangée / 3 zones + filigrane) répondent à la
       largeur du BANDEAU, pas du viewport — la sidebar réelle (`--dash-sidebar-width:
       260px`, dashboard.css:19) fausse le second sur dashboard.html. @container
       remplace les anciens @media de largeur de viewport (constat Bastien du 15/08). */
    container-type: inline-size;
    container-name: pm-space-state;
    /* Largeur de la zone filigrane, en unité `cqw` (toujours relative au CONTENT-BOX
       du conteneur de requête, quel que soit l'élément qui la consomme et son propre
       box model) : réutilisée telle quelle par `__wm` (width) et `__inner`
       (padding-right), qui vivaient avant sur deux référents différents
       (padding-box vs content-box) — écart corrigé le 15/08. Pas de plancher : la
       zone n'existe que dans le bloc 3-zones (seuil 810px de bandeau, plus bas),
       où 32cqw vaut déjà ≥259px — un plancher plus bas serait inatteignable. */
    --pm-space-wm-zone: min(32cqw, 520px);
}

.pm-space-state__lines {
    position: absolute;
    inset: 0;
    opacity: 0.35;
    background-image: repeating-linear-gradient(
        to bottom,
        var(--pm-border-subtle-ink) 0,
        var(--pm-border-subtle-ink) 1px,
        transparent 1px,
        transparent 28px
    );
    pointer-events: none;
}

.pm-space-state__corner {
    position: absolute;
    width: 16px;
    height: 16px;
    pointer-events: none;
}
.pm-space-state__corner span {
    position: absolute;
    background: color-mix(in oklch, var(--filiere, var(--accent)) 46%, transparent);
}
.pm-space-state__corner .h { width: 16px; height: 1.5px; }
.pm-space-state__corner .v { width: 1.5px; height: 16px; }
.pm-space-state__corner--tl { top: 14px; left: 14px; }
.pm-space-state__corner--tl .h, .pm-space-state__corner--tl .v { top: 0; left: 0; }
.pm-space-state__corner--tr { top: 14px; right: 14px; }
.pm-space-state__corner--tr .h, .pm-space-state__corner--tr .v { top: 0; right: 0; }
.pm-space-state__corner--bl { bottom: 14px; left: 14px; }
.pm-space-state__corner--bl .h, .pm-space-state__corner--bl .v { bottom: 0; left: 0; }
.pm-space-state__corner--br { bottom: 14px; right: 14px; }
.pm-space-state__corner--br .h, .pm-space-state__corner--br .v { bottom: 0; right: 0; }

/* Filigrane — remis aux coefficients vitrine PM le 15/08 (trop marqué en app à ×2).
   Valeurs = opacité finale (base 0.2 × boost) de docs/audits/2026-08-15-vitrine-pm-cta-panel-style-inventaire.md
   §filigrane : alat/tigre boost 1.87 → 0.374, eopn/rafale-c boost 1.77 → 0.354,
   eopan/rafale-m boost 2.19 → 0.438. Zone réservée à droite (--pm-space-wm-zone,
   cf. @container plus bas) : dessin dimensionné par sa LARGEUR (jamais par la
   hauteur du bandeau, ni par les deux axes à la fois — un `max-height` en plus du
   `width: 100%` déformait le ratio, bug constaté le 15/08). Masqué en dessous
   (rangée/empilé, place manquante). */
.pm-space-state__wm {
    display: none;
    overflow: hidden;
    pointer-events: none;
    user-select: none;
    /* Fondu confiné à SA colonne (bord gauche seulement) : plus besoin de suivre le
       texte en px absolus, le filigrane ne peut plus déborder dessus — il vit dans sa
       propre piste de grille. */
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 48px);
    mask-image: linear-gradient(90deg, transparent 0, #000 48px);
}
.pm-space-state__wm-item {
    /* Dimensionné par la largeur seule ; centré verticalement par rapport au
       bandeau via `top: 50%` + `translate` (retour Bastien du 15/08) — le rognage
       haut/bas reste symétrique grâce à l'`overflow: hidden` du bandeau, quelle que
       soit la hauteur réelle de l'image (aspect-ratio × largeur de la zone). */
    position: absolute;
    top: 50%;
    right: 0;
    translate: 0 -50%;
    width: 100%;
    height: auto;
    background: var(--filiere, var(--accent));
}
.pm-space-state__wm-item[data-wm="alat"] {
    aspect-ratio: 1200 / 655;
    -webkit-mask: url('../img/filigranes/tigre-blueprint.webp') center / 100% 100% no-repeat;
    mask: url('../img/filigranes/tigre-blueprint.webp') center / 100% 100% no-repeat;
    opacity: 0.374;
}
.pm-space-state__wm-item[data-wm="eopn"] {
    /* Le dessin du rafale-c remplit son canevas 1200×655 bien plus que le tigre (alat) ou
       le rafale-m (eopan) : à hauteur/aspect-ratio identiques il paraît nettement plus gros
       à l'écran (constat captures docs/prototypes/2026-08-15-cta-etat-espace/app4-*.png).
       Coefficient de taille réduit pour ramener l'emprise visuelle au même gabarit — lui
       seul, alat/eopan restent à largeur pleine (cf. .pm-space-state__wm-item ci-dessus).
       Ancré à GAUCHE (`left: 0`, pas `right: 0` hérité de la base) : le fondu de `__wm`
       part du bord gauche de LA ZONE (`mask-image`, 48px) — à 82% de largeur avec un
       ancrage à droite, le bord gauche du dessin tombait ~87px après le bord de zone,
       hors du fondu, d'où une coupe franche au lieu d'un fondu (constat du 15/08). Le
       vide restant tombe désormais du côté droit (bord de page), sans fondu requis. */
    left: 0;
    right: auto;
    width: 82%;
    aspect-ratio: 1200 / 655;
    -webkit-mask: url('../img/filigranes/rafale-c-blueprint.webp') center / 100% 100% no-repeat;
    mask: url('../img/filigranes/rafale-c-blueprint.webp') center / 100% 100% no-repeat;
    opacity: 0.354;
}
.pm-space-state__wm-item[data-wm="eopan"] {
    aspect-ratio: 1200 / 798;
    -webkit-mask: url('../img/filigranes/rafale-m-blueprint.webp') center / 100% 100% no-repeat;
    mask: url('../img/filigranes/rafale-m-blueprint.webp') center / 100% 100% no-repeat;
    opacity: 0.438;
}

.pm-space-state__inner {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    flex-wrap: wrap;
}

.pm-space-state__text {
    flex: 1 1 320px;
    min-width: 0;
    /* ~600px, pas 64ch : le lead est en 0.92rem alors que `ch` se calcule sur la
       taille de police de l'ÉLÉMENT lui-même (1rem ici) — 64ch valait 679px réels,
       bien plus large que le rendu visé (constat du 15/08). */
    max-width: 600px;
}

.pm-space-state__kicker {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    font-size: 11.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.18em;
    color: var(--filiere, var(--accent));
}
.pm-space-state__filet {
    width: 22px;
    height: 2px;
    background: currentColor;
    flex: none;
}

.pm-space-state__heading {
    margin: 14px 0 0;
    font-family: var(--font-sans);
    font-size: clamp(1.25rem, 1rem + 1vw, 1.6rem);
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: -0.01em;
    color: var(--pm-text-on-ink);
}

.pm-space-state__lead {
    display: block;
    margin-top: 10px;
    font-size: 0.92rem;
    line-height: 1.55;
    color: var(--pm-text-on-ink-secondary);
}

.pm-space-state__lead strong {
    color: var(--pm-text-on-ink);
    font-weight: 600;
}

.pm-space-state__actions {
    position: relative;
    /* Base = empilé, pleine largeur (le `width: 100%` du CTA n'a d'effet que si SON
       conteneur a lui-même une largeur définie — sinon la largeur pourcentage
       retombe sur la taille du contenu, cercle vicieux constaté le 15/08). Annulé
       dans les blocs ≥ rangée (500px+), où `.pm-space-state__actions` reprend une
       largeur naturelle à côté du texte. */
    flex: 0 0 auto;
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
}

.pm-space-state__cta {
    min-height: 44px;
    padding: 12px 22px;
    border: 1.5px solid var(--pm-white, #fff);
    border-radius: 6px;
    background: #fff;
    color: var(--pm-navy-950);
    font-family: inherit;
    font-size: 0.92rem;
    font-weight: 700;
    cursor: pointer;
    white-space: nowrap;
    transition: filter 0.15s var(--ease-out);
    /* Base = empilé (< ~500px de bandeau) : pleine largeur, pas de vide à droite du
       bouton. Annulé dans les blocs ≥ rangée (500px+) où le bouton reprend sa
       largeur naturelle. */
    width: 100%;
    text-align: center;
}

.pm-space-state__cta:hover {
    filter: brightness(0.96);
}

.pm-space-state__cta:focus-visible {
    outline: 2px solid #fff;
    outline-offset: 2px;
}

.pm-space-state__terms {
    display: inline-block;
    font-size: 0.75rem;
    color: var(--pm-text-on-ink);
    padding: 4px 8px;
    border-radius: 4px;
    background: rgba(9, 20, 32, 0.75);
}

/* Les 3 dispositions répondent à la largeur du BANDEAU (container query), pas du
   viewport : la sidebar réelle (260px, dashboard.css:19) fausse un seuil en vw — un
   panneau large de ~600px de bandeau peut vivre sur un viewport de 1000 comme de
   2560px, la disposition doit s'enclencher pareil dans les deux cas. Base ci-dessus
   = empilé (< ~500px de bandeau, comportement mobile inchangé). */

/* ~500-810px de bandeau (content-box) : rangée sans filigrane (pas de place pour
   lui — en dessous du seuil 3-zones ci-dessous). */
@container pm-space-state (min-width: 500px) {
    .pm-space-state__actions {
        width: auto;
    }
    .pm-space-state__cta {
        width: auto;
    }
}

/* ≥810px de bandeau en content-box : 3 zones — texte | action | filigrane. Seuil
   mesuré le 15/08 (Playwright, `getBoundingClientRect`/`getComputedStyle`, pas
   deviné), pas une valeur ronde arbitraire : sur dashboard.html la sidebar
   (`--dash-sidebar-width: 260px`, dashboard.css:19-162) passe d'une colonne fixe à
   une barre empilée EN LARGEUR PLEINE en dessous de son propre seuil
   (`@media (max-width: 900px)`, dashboard.css:2760/2879) — donc au ras de ce
   seuil, un viewport de 900px donne un bandeau plus LARGE (content-box mesuré
   ~799px, sans sidebar) qu'un viewport de 1024px (content-box ~632px, sidebar en
   colonne). Un seuil à 760px déclenchait le filigrane de façon non monotone
   (visible ~857-900px de viewport, masqué ~901-1199px, revisible ≥1200px) et
   écrasait le texte à 284px sur tablette. 810px reste au-dessus du maximum
   atteignable SANS sidebar (799px mesuré à 900px de viewport) : le filigrane ne
   s'active donc plus qu'AVEC sidebar, à partir de ~1203px de viewport — dans la
   plage ~1200-1230 validée par Bastien.
   ⚠️ Une container query ne peut PAS reposer ses styles sur l'élément CONTENEUR
   lui-même (dépendance circulaire interdite par la spec — `display: grid` sur
   `.pm-space-state` à l'intérieur de son propre `@container` n'a AUCUN effet,
   testé en vrai le 15/08 : les enfants recevaient bien leurs `grid-column`, le
   panneau restait en `display: block`, tout s'empilait). Seuls des DESCENDANTS de
   `.pm-space-state` peuvent être stylés par sa container query — d'où un flex sur
   `__inner` (texte 1fr + action auto) avec un `padding-right` qui RÉSERVE la même
   largeur que la zone filigrane (`--pm-space-wm-zone`, définie en `cqw` donc déjà
   au même référent aux deux endroits, cf. déclaration sur `.pm-space-state`), et
   `__wm` posé en `position: absolute` sur cette zone réservée. Le bouton ne peut
   plus jamais empiéter dessus ni laisser de vide : c'est la colonne texte qui
   absorbe l'espace en trop (le lead peut passer sur 3 lignes avant de sacrifier le
   filigrane). Au-delà de ~1140px de bandeau, le texte plafonne à 600px
   (`.pm-space-state__text`, `max-width`) : le vide qui se creuse alors entre texte
   et bouton est VOULU (retour Bastien du 15/08 — le bandeau est pleine largeur,
   l'action reste collée au filigrane plutôt que de suivre le texte). */
@container pm-space-state (min-width: 810px) {
    .pm-space-state__wm {
        display: block;
        position: absolute;
        inset: 0 0 0 auto;
        width: var(--pm-space-wm-zone);
    }
    .pm-space-state__inner {
        flex-wrap: nowrap;
        padding-right: calc(var(--pm-space-wm-zone) + 28px);
    }
    .pm-space-state__text {
        flex: 1 1 auto;
        max-width: 600px;
    }
    .pm-space-state__actions {
        flex: 0 0 auto;
        margin-left: auto;
    }
}
