/* ============================================================
   BATAILLE NAVALE
   ============================================================ */

:root {
    --bs-water: #dceefb;
    --bs-water-dark: #b6ddf7;
    --bs-ship: #6b7a8f;
    --bs-hit: #ff4757;
    --bs-sunk: #2f3542;
    --bs-miss: #8fb8d8;
}

.bs-container {
    background: var(--card-bg);
    border-radius: 1.5rem;
    padding: clamp(0.75rem, 3vw, 1.5rem);
    box-shadow:
        0 20px 60px rgba(0, 0, 0, 0.15),
        0 0 0 1px rgba(255, 255, 255, 0.5) inset;
    backdrop-filter: blur(10px);
}

.bs-score {
    display: block;
    text-align: center;
    font-weight: 700;
    margin-bottom: 0.5rem;
}

.bs-status {
    text-align: center;
    font-weight: 600;
    min-height: 1.75rem;
    margin-bottom: 0.75rem;
}

/* Libellé de la grande grille : « Ta flotte » en placement, « Flotte adverse »
   en bataille. Mêmes proportions que .bs-minimap-title, les deux grilles
   portant chacune son compteur. */
.bs-grid-title {
    display: block;
    text-align: center;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-light);
    margin-bottom: 0.25rem;
}

/* 9 colonnes : la gouttière des numéros de ligne, puis les 8 cases. */
.bs-grid {
    display: grid;
    grid-template-columns: auto repeat(8, 1fr);
    gap: 3px;
    padding: 4px;
    max-width: 420px;
    margin: 0 auto;
    background: linear-gradient(160deg, var(--bs-water), var(--bs-water-dark));
    border-radius: 0.9rem;
}

.bs-head {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.7rem;
    font-weight: 700;
    color: var(--text-light);
    min-width: 1rem;
}

.bs-cell {
    aspect-ratio: 1;
    border: none;
    padding: 0;
    border-radius: 0.25rem;
    background: rgba(255, 255, 255, 0.75);
    box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.12);
}

.bs-cell-target {
    cursor: pointer;
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
}

/* Case non jouable : ni curseur ni indice, mais l'apparence de la grille ne
   change pas — un enfant doit continuer à lire le plateau. */
.bs-cell-target:disabled {
    cursor: default;
}

.bs-cell-target:focus-visible {
    outline: 3px solid var(--primary);
    outline-offset: 2px;
}

.bs-cell-ship { background: var(--bs-ship); }
.bs-cell-sunk { background: var(--bs-sunk); }
.bs-cell-hit  { background: var(--bs-hit); }
.bs-cell-miss { background: var(--bs-miss); }

/* Visée en box-shadow inset et NON en outline : .bs-cell-target:focus-visible
   ci-dessus porte déjà un outline, avec une spécificité (0,2,0) supérieure à
   celle de cette classe (0,1,0) — et la spécificité l'emporte sur l'ordre
   source. Or dans le flux clavier la MÊME case est visée et focalisée : un
   outline ici serait toujours perdu, et l'utilisatrice clavier ne pourrait pas
   distinguer « focalisée » de « focalisée et visée ». Deux propriétés
   différentes coexistent, les deux indices s'affichent ensemble. */
.bs-cell-aimed {
    box-shadow: 0 0 0 3px var(--accent) inset;
}

.bs-aim {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    margin-top: 0.75rem;
    min-height: 44px;
}

.bs-aim-label {
    font-weight: 700;
    font-size: 1.1rem;
    min-width: 2.5rem;
    text-align: center;
}

.bs-fire-btn {
    min-height: 44px;
    padding: 0.5rem 1.25rem;
    font-size: 1.05rem;
    font-weight: 700;
    color: white;
    background: linear-gradient(135deg, var(--c4-red, #ff4757), #ff6b81);
    border: none;
    border-radius: 0.75rem;
    cursor: pointer;
}

.bs-fire-btn:disabled {
    opacity: 0.5;
    cursor: default;
}

.bs-minimap-wrap {
    text-align: center;
    margin-top: 0.75rem;
}

.bs-minimap-title {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-light);
}

.bs-minimap {
    display: grid;
    grid-template-columns: repeat(8, 1fr);
    gap: 2px;
    max-width: 140px;
    margin: 0.25rem auto 0;
}

.bs-mini-cell {
    aspect-ratio: 1;
    border-radius: 2px;
    background: rgba(255, 255, 255, 0.7);
}

.bs-mini-ship { background: var(--bs-ship); }
.bs-mini-hit  { background: var(--bs-hit); }
.bs-mini-sunk { background: var(--bs-sunk); }
.bs-mini-miss { background: var(--bs-miss); }

.bs-actions {
    margin-top: 1rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
}

.bs-shuffle-btn,
.bs-ready-btn {
    min-height: 44px;
    padding: 0.5rem 1.25rem;
    font-size: 1rem;
    font-weight: 600;
    border: 2px solid var(--primary);
    border-radius: 0.75rem;
    background: var(--card-bg);
    color: var(--text);
    cursor: pointer;
}

.bs-rematch-status {
    min-height: 1.25rem;
    text-align: center;
    color: var(--text-light);
    font-size: 0.95rem;
    font-weight: 600;
}

/* Sur écran étroit, les 8 colonnes se disputent la largeur : on rend à la
   grille les pixels des paddings horizontaux plutôt que de rétrécir les cases.
   Le gap reste à 3px — en dessous, la grille cesse de se lire. */
@media (max-width: 480px) {
    .bs-container {
        padding-left: 0.5rem;
        padding-right: 0.5rem;
    }

    /* Le score encadre le compte de manches par les deux prénoms. À 320 px il
       passait à deux lignes dès « Joséphine » face à « Anne-Charlotte »
       (mesuré : 285,8 px pour 272 px disponibles) ; 0.9rem le ramène à 260,6 px,
       sur une seule ligne. Deux prénoms au maxlength de 15 caractères débordent
       de toute façon (305,8 px à 0.9rem) : ils passent à deux lignes centrées,
       ce qui reste lisible et ne fait jamais défiler la page horizontalement. */
    .bs-score {
        font-size: 0.9rem;
    }
}

/* ============================================================
   HOVER — pointeur fin uniquement (jamais sur tactile)
   ============================================================ */
@media (hover: hover) and (pointer: fine) {
    .bs-entry-btn:hover {
        border-color: var(--primary);
        transform: translateX(5px) scale(1.02);
    }

    /* :not(:disabled) : aucun indice sur une case déjà tirée ou hors tour. */
    .bs-cell-target:not(:disabled):hover {
        background: rgba(255, 255, 255, 1);
        box-shadow: 0 0 0 2px var(--accent) inset;
    }

    .bs-fire-btn:not(:disabled):hover {
        transform: translateY(-2px);
    }

    .bs-shuffle-btn:hover,
    .bs-ready-btn:hover {
        background: var(--primary-light);
    }
}

/* ============================================================
   NAUFRAGE — l'emoji porte l'effet, la case n'est que le socle
   Trois crans monotones, pour qu'ils s'apprennent sans explication :
     raté   = rien de plus qu'avant, le seul sursaut de la case
     touché = 💥 sobre, qui tient dans la case
     coulé  = 💥 qui déborde franchement + ☠️
   Aucun emoji nouveau n'entre dans l'appli : 💥 vient de l'écran de jonction,
   ☠️ de l'annonce « ☠️ Tu as coulé le Torpilleur ! » que bsAnnounceSunk pose au
   même instant. La case voit donc le symbole que la ligne de statut écrit.
   ============================================================ */

/* Socle, posé sur les deux grilles. Il garde le sursaut d'avant à l'identique et
   ajoute le contexte dont la couche emoji a besoin.

   position: relative n'est PAS cosmétique. `.bs-container` porte un
   backdrop-filter, ce qui en fait le bloc conteneur des descendants absolus :
   mesuré, sans cette ligne le pseudo-élément atterrit au centre du conteneur et
   non sur sa case. Le retirer « parce que ça ne sert à rien » casse tout l'effet
   sans lever la moindre erreur.

   z-index fait passer la case au-dessus de ses voisines, qui sont en static :
   sans lui, l'emoji qui déborde passerait SOUS les cases suivantes dans l'ordre
   du DOM.

   container-type: size est ce qui dimensionne l'emoji, et c'est LE piège du
   sujet. Le font-size d'une case ne suit pas sa largeur : mesuré, une case de la
   grille de tir est un <button> à 13,33 px de police — les boutons n'héritent pas
   de celle de la page — là où une case de mini-carte est un <div> à 16 px. Un
   `em` donnerait donc deux tailles différentes sur les deux grilles, et aucune
   des deux ne bougerait entre un écran de 320 px et un desktop. Les unités de
   conteneur mesurent l'emoji sur LA CASE : un seul nombre vaut de la case de
   46,5 px du desktop à celle de 14,96 px de la mini-carte, sans media query.
   Mesuré : la mise en page ne bouge pas (grille toujours à 420 px, case toujours
   à 46,5 px) et rien ne rogne — `contain: size` ne clippe pas, seul
   `contain: paint` le ferait. */
.bs-cell-boom {
    position: relative;
    z-index: 2;
    container-type: size;
    animation: bsBoom 0.4s ease-out;
}

/* La couche emoji. Aucune de ces déclarations ne pose `content` : sans content,
   aucune boîte de pseudo-élément n'est générée, donc un RATÉ — qui ne reçoit que
   la classe de socle — garde exactement le rendu d'avant.

   opacity: 0 est l'état de REPOS, et c'est là que tient toute la sûreté du
   dispositif. Une animation sans `animation-fill-mode` revient à son état de base
   en se terminant, or la classe SURVIT jusqu'au prochain snapshot : état de base
   invisible ⇒ rien ne reste collé sur la case. Corollaire pour la suite : toute
   addition à cette couche doit poser son invisibilité DANS LA RÈGLE DE BASE, pas
   seulement au 100 % de son keyframe.

   Le centrage passe par left/top 50 % + translate(-50%, -50%) et non par
   inset: 0 + place-items: center : la seconde forme décale le glyphe dès qu'il
   dépasse sa boîte, ce que fait tout l'effet ici.

   pointer-events: none est fonctionnel : le glyphe déborde sur des cases
   VOISINES qui, elles, sont cliquables. */
.bs-cell-boom::before,
.bs-cell-boom::after {
    position: absolute;
    left: 50%;
    top: 50%;
    line-height: 1;
    pointer-events: none;
    opacity: 0;
    /* Halo blanc : le glyphe passe sur --bs-hit (#ff4757), dont le rouge mange
       celui de l'explosion. drop-shadow suit l'alpha du glyphe, là où text-shadow
       n'est pas fiable sur un emoji en couleur. */
    filter: drop-shadow(0 0 3px rgba(255, 255, 255, 0.95));
}

/* ⚠️ Un cran intermédiaire pour le simple touché a été essayé puis RETIRÉ, et il
   ne faut pas le réintroduire sans relire ceci. Une étincelle de 0,8 × la case
   reste entièrement DANS la case — or une case touchée a déjà `--bs-hit` en fond,
   soit un rouge plein (#ff4757). Le glyphe 💥 étant lui-même rouge-orange, le
   résultat mesuré à l'écran est une tache pâle illisible, et non une explosion.
   Deux correctifs essayés et rejetés : agrandir à 1,18 × la case n'a presque rien
   changé (le glyphe déborde trop peu), et un halo blanc en deux passes n'a fait
   aucune différence visible. Aller plus loin en taille aurait effacé la
   distinction avec le coulé.
   Le coulé, lui, échappe au problème pour une raison précise : à 1,7 × la case il
   DÉBORDE, donc les pointes de l'étoile et la tête de mort atterrissent sur l'eau
   pâle des cases voisines, où elles se détachent. C'est le débordement qui rend
   l'emoji lisible, pas sa taille en soi.
   Le touché reste donc distingué du raté par la COULEUR de la case, comme avant,
   et l'emoji est réservé au seul événement que l'utilisateur voulait marquer. */

/* La case d'impact passe au-dessus des cases de l'onde, qui restent à 2. */
.bs-boom-sunk {
    z-index: 3;
}

/* .bs-boom-wave ne porte QUE l'explosion : c'est ce qui interdit
   structurellement à la tête de mort d'atterrir sur une case d'onde.
   La double déclaration de `content` n'est pas une coquille : la forme à deux
   valeurs donne un texte alternatif VIDE aux lecteurs d'écran qui restituent le
   contenu généré (vérifiée retenue par le moteur), et la déclaration simple qui la
   précède est le repli de ceux qui ne parsent pas la barre oblique — sans elle, la
   déclaration invalide tomberait et il n'y aurait plus d'emoji du tout. */
.bs-boom-sunk::before,
.bs-boom-wave::before {
    content: '💥';
    content: '💥' / '';
    font-size: 170cqh;
    animation: bsBlast 0.62s linear;
}

.bs-boom-sunk::after {
    content: '☠️';
    content: '☠️' / '';
    font-size: 90cqh;
    animation: bsSkull 0.5s ease-out 0.16s;
}

/* MÊME emoji, direction INVERSE selon le sens du coulage. Sur la grille de tir la
   tête de mort MONTE : je viens de gagner quelque chose, c'est le « + points »
   d'un jeu vidéo. Sur ma mini-carte elle DESCEND et sombre sous la case : je
   viens de perdre un bateau. La grammaire est la même des deux côtés — un emoji,
   un mouvement — seule la direction dit qui a gagné. Un ☠️ qui monte en fanfare
   sur ma propre flotte serait à contresens. */
.bs-boom-mine::after {
    animation-name: bsSkullSink;
}

/* L'onde le long de MON bateau : un cran par case d'écart depuis l'impact. Trois
   suffisent, le plus grand bateau (Porte-avions) faisant 4 cases.
   ⚠️ animation-delay n'est PAS neutralisé par le reset prefers-reduced-motion,
   qui ne touche que animation-duration, -iteration-count et transition-duration.
   C'est sans conséquence ici précisément parce qu'aucun animation-fill-mode n'est
   utilisé : pendant le délai le pseudo est à son état de base, invisible. Un
   `fill-mode: backwards` le rendrait à son 0 % et est donc proscrit. */
.bs-wave-1::before { animation-delay: 0.08s; }
.bs-wave-2::before { animation-delay: 0.16s; }
.bs-wave-3::before { animation-delay: 0.24s; }

/* ------------------------------------------------------------
   KEYFRAMES — chacune se termine au 100 % sur l'état de REPOS EXACT : emoji
   transparent, case à l'échelle 1, aucune transformation résiduelle. C'est ce qui
   rend le reset prefers-reduced-motion de style.css correct : il force
   animation-duration à 0,01 ms SANS poser animation: none, donc le navigateur
   saute au 100 % puis revient à la base — et les deux sont l'état de repos.
   ------------------------------------------------------------ */

/* INCHANGÉ — le sursaut de la case, socle des trois crans. */
@keyframes bsBoom {
    0%   { transform: scale(1); }
    45%  { transform: scale(1.35); }
    100% { transform: scale(1); }
}

/* `linear` et non ease-out : les pourcentages valent alors des millisecondes, et
   les deux battements tombent où la lecture des keyframes le dit. Le sens est
   complet au pic, à 280 ms — ce qui compte, parce qu'un rendu peut couper
   l'animation en vol (les deux grilles sont reconstruites à chaque snapshot). */
@keyframes bsBlast {
    0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.30) rotate(-20deg); }
    18%  { opacity: 1; transform: translate(-50%, -50%) scale(1.00) rotate(6deg); }
    30%  { opacity: 1; transform: translate(-50%, -50%) scale(0.88) rotate(0deg); }
    45%  { opacity: 1; transform: translate(-50%, -50%) scale(1.18) rotate(-4deg); }
    100% { opacity: 0; transform: translate(-50%, -50%) scale(1.45) rotate(0deg); }
}

@keyframes bsSkull {
    0%   { opacity: 0; transform: translate(-50%, -40%)  scale(0.5); }
    35%  { opacity: 1; transform: translate(-50%, -110%) scale(1); }
    100% { opacity: 0; transform: translate(-50%, -190%) scale(1); }
}

@keyframes bsSkullSink {
    0%   { opacity: 0; transform: translate(-50%, -60%) scale(0.5); }
    35%  { opacity: 1; transform: translate(-50%, 10%)  scale(1); }
    100% { opacity: 0; transform: translate(-50%, 90%)  scale(0.9); }
}
