/* =============================================================
   MNEMO — 09 — APP
   Layout général : stage, feed-body, splash, FAB, empty state, responsive.
   ============================================================= */

/* ------ Splash / Loading ----------------------------------- */

.splash {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  gap: 24px;
}

.splash-logo {
  font-family: 'Archivo', sans-serif;
  font-weight: 900;
  font-size: clamp(48px, 14vw, 88px);
  letter-spacing: -0.04em;
  background: var(--grad-primary);
  background-size: 200% 200%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: gradShift 5s ease-in-out infinite;
  filter: drop-shadow(0 0 24px rgba(var(--ombre-rgb), 0.193));
}

@keyframes gradShift {
  0%, 100% { background-position: 0% 50%; }
  50%      { background-position: 100% 50%; }
}

.spinner {
  width: 36px; height: 36px;
  border: 3px solid transparent;
  border-top-color: var(--pink);
  border-right-color: var(--cyan);
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }


/* ------ STAGE (conteneur principal du feed) ---------------- */

.stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  position: relative;
}

/* --- feed-body : wrapper carousel + timeline --- */
.feed-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Mode vertical : carousel à gauche, timeline à droite */
.stage-vertical .feed-body {
  flex-direction: row;
}


/* ------ Empty state ---------------------------------------- */

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 60px 24px;
  color: var(--text-dim);
  flex: 1;
}
.empty-icon {
  font-size: 64px;
  margin-bottom: 16px;
  opacity: 0.6;
}
.empty-title {
  font-family: 'Archivo', sans-serif;
  font-weight: 700;
  font-size: 20px;
  color: var(--text);
  margin-bottom: 8px;
}
.empty-desc { font-size: 14px; max-width: 300px; margin: 0 auto; line-height: 1.5; }


/* ------ FAB (Floating Action Button) ----------------------- */

.fab {
  position: fixed;
  bottom: calc(188px + env(safe-area-inset-bottom));
  right: 18px;
  z-index: 50;
  width: 60px; height: 60px;
  border-radius: 50%;
  background: var(--grad-primary);
  background-size: 200% 200%;
  box-shadow: 0 12px 36px rgba(var(--ombre-rgb), 0.3), 0 0 40px rgba(var(--ombre-rgb), 0.22);
  display: grid;
  place-items: center;
  color: var(--sur-grad);
  animation: gradShift 4s ease-in-out infinite, fabBob 3s ease-in-out infinite;
  transition: transform 0.2s;
}

.fab:hover { transform: scale(1.08); }
.fab:active { transform: scale(0.94); }

/* Variante "annonce" : dorée pour rappeler le visuel des annonces officielles.
   Réutilise la même animation de bob mais palette dorée/jaune. */
.fab-announcement {
  background: linear-gradient(135deg, var(--jaune) 0%, var(--tampon) 50%, var(--tampon) 100%);
  box-shadow:
    0 12px 36px rgba(var(--ombre-rgb), 0.3),
    0 0 40px rgba(var(--ombre-rgb), 0.22);
  color: var(--encre-2);
}

@keyframes fabBob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-4px); }
}

.fab-secondary {
  position: fixed;
  bottom: 40px;
  left: 24px;
  z-index: 50;
  padding: 12px 18px;
  border-radius: calc(999px * var(--r-k));
  background: rgba(var(--carte-rgb), 0.92);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(var(--bleue-rgb), 0.4);
  box-shadow: var(--glow-cyan);
  color: var(--cyan);
  font-family: 'Space Mono', monospace;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  display: flex;
  align-items: center;
  gap: 8px;
}


/* ------ Responsive : portrait mobile -----------------------
   Cache les flèches de nav, fait dépasser les polaroids voisins
   derrière le polaroid focus. */
@media (max-width: 680px) and (orientation: portrait) {
  .carousel-nav { display: none; }

  /* JEU DE CARTES — une seule mesure pour toute la pile.
     À 74vw de large on ne lisait qu'une photo par écran. `--carte-w` fixe la
     largeur de référence, les deux types de tuile s'y réfèrent, et le
     recouvrement (cf. 05-carousel.css) fait le reste : on voit désormais la
     carte centrale et ses voisines, inclinées en éventail derrière elle. */
  .stage-horizontal .carousel-track {
    /* 74vw : la largeur qu'avait déjà la carte, mais désormais au bon ratio.
       La portion visible d'une voisine vaut (100 − largeur) / 2 de l'écran et ne
       dépend QUE de cette largeur — le recouvrement ne change que la part cachée
       derrière la carte centrale. À 78vw il ne restait que 43 px de voisine, trop
       peu pour lire une pile ; à 70vw on montait à 58 px mais la carte perdait en
       présence. 74vw laisse ~50 px de chaque côté. */
    --carte-w: 74vw;
    /* (100 − largeur de carte) / 2, pour que la première et la dernière carte
       puissent se centrer sur leur point d'accroche. */
    padding-inline: 13vw;
  }

  .stage-horizontal .polaroid,
  .stage-vertical .carousel-vertical .polaroid {
    /* Largeur EXPLICITE, pas seulement un plafond : avec `height: auto` et une
       largeur laissée à `auto`, l'`aspect-ratio` n'a plus rien à quoi s'ancrer
       et la carte se dimensionne sur son contenu — elle tombait à 194 px.
       Hauteur déduite de la largeur : avec `height: 85%` c'était la hauteur qui
       gagnait, la largeur plafonnée écrasait la carte, et le ratio tombait à
       0.51 au lieu des 0.82 du polaroid — la zone image devenant une bande
       étroite qui recadrait sévèrement les photos. */
    width: var(--carte-w);
    max-width: var(--carte-w);
    height: auto;
    aspect-ratio: 0.82 / 1;
  }

  /* Les annonces sont des stories 9:16, donc plus élancées qu'un polaroid. On
     aligne leur HAUTEUR sur celle des polaroids et on laisse la largeur suivre :
     à taille libre elles montaient à 459 px contre 333 px, et une carte dépassait
     d'un tiers de la pile. Hauteur égale = même main de cartes. */
  .stage-horizontal .ann-card,
  .stage-vertical .carousel-vertical .ann-card {
    height: calc(var(--carte-w) / 0.82);
    width: auto;
    max-width: none;
    aspect-ratio: 9 / 16;
  }

  /* Voisines immédiates : légèrement désaturées, mais NETTES et présentes.
     L'`opacity: 0.65 !important` d'avant écrasait la valeur calculée par le
     carousel — comme le `0.2 !important` des cartes plus lointaines, qui les
     effaçait quelle que soit la rampe. Ces deux règles rendaient le dégradé du
     JS purement décoratif et réduisaient la pile à une seule carte lisible.
     L'opacité est désormais pilotée par `--o`, en continu. */
  .polaroid-peripheral {
    filter: saturate(0.72) brightness(1.05) contrast(0.9);
  }

  /* En mode vertical sur mobile portrait : track plus compact. La largeur des
     cartes est portée par `--carte-w` ci-dessus, en vw et non en % : une
     hauteur exprimée avec un % se résoudrait sur la HAUTEUR du conteneur, pas
     sur sa largeur, et `calc(--carte-w / 0.82)` donnerait n'importe quoi. */
  .stage-vertical .carousel-vertical .carousel-track {
    --carte-w: min(51vw, 290px);
    padding-block: 34vh;
  }
  /* La timeline verticale prend un peu moins de place sur petit écran */
  .stage-vertical .ruler-vertical {
    width: 80px;
  }
  .stage-vertical ~ .fab {
    right: calc(80px + 12px);
  }
}

@media (max-width: 380px) {
  .fab { width: 56px; height: 56px; bottom: 16px; right: 16px; }
  .upload-opts { grid-template-columns: 1fr; }
}

/* ============================================================
   INSTALL PROMPT — bannière "Installer l'app" en bas
   ============================================================ */

.install-prompt {
  position: fixed;
  left: 16px;
  right: 16px;
  bottom: calc(env(safe-area-inset-bottom) + 16px);
  z-index: 950; /* sous les toasts (1000) mais au-dessus du reste */
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  background: linear-gradient(135deg, rgba(var(--papier-rgb), 0.95), rgba(var(--carte-rgb), 0.95));
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid rgba(var(--tampon-rgb), 0.3);
  border-radius: calc(16px * var(--r-k));
  box-shadow:
    0 12px 36px rgba(var(--ombre-rgb), 0.275),
    0 0 0 1px rgba(var(--ombre-rgb), 0.022) inset;
  /* Anim d'entrée : slide depuis le bas */
  animation: installPromptIn 0.45s cubic-bezier(0.16, 1, 0.3, 1);
  max-width: 460px;
  margin: 0 auto;
}

@keyframes installPromptIn {
  from { transform: translateY(140%); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}

.install-prompt-icon {
  font-size: 32px;
  flex-shrink: 0;
  filter: drop-shadow(0 2px 6px rgba(var(--ombre-rgb), 0.22));
}

.install-prompt-body {
  flex: 1;
  min-width: 0;
}

.install-prompt-title {
  font-size: 14px;
  font-weight: 800;
  color: var(--text);
  margin-bottom: 2px;
  letter-spacing: -0.01em;
}

.install-prompt-desc {
  font-size: 12px;
  color: rgba(var(--encre-rgb), 0.7);
  line-height: 1.4;
}

.install-prompt-desc strong {
  color: var(--text);
  font-weight: 700;
}

.install-prompt-icon-inline {
  display: inline-block;
  font-size: 13px;
  vertical-align: -1px;
  margin: 0 1px;
}

.install-prompt-cta {
  flex-shrink: 0;
  font-size: 13px;
  padding: 8px 16px;
}

.install-prompt-close {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(var(--encre-rgb), 0.1);
  color: rgba(var(--encre-rgb), 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.install-prompt-close:hover {
  background: rgba(var(--encre-rgb), 0.14);
  color: var(--text);
}

/* ============================================================
   ANN CARD — gestion peripheral/focused dans le carousel (Phase 4d.4)
   ============================================================ */

/* Atténuation des cartes annonce voisines (cohérent avec .polaroid-peripheral).
   L'`opacity: 0.7` fixe qui vivait ici gagnait sur `opacity: var(--o)` de
   `.ann-card` — spécificité supérieure ET fichier chargé plus tard : une carte
   d'annonce s'affichait donc au même voile qu'elle soit la voisine immédiate ou
   la dixième. L'opacité revient au carousel, qui la calcule en continu ; seule
   la désaturation reste ici, elle ne dépend que de l'état focalisé ou non. */
.ann-card:not(.ann-card-focused) {
  filter: saturate(0.72) brightness(1.04) contrast(0.9);
}
/* Pas de `z-index` : c'est le carousel qui empile les cartes par proximité du
   centre, en style inline. La valeur écrite ici était de toute façon inerte. */
.ann-card-focused {
  filter: none;
}

/* ---- Bandeau "charger plus ancien" (pagination feed) ------ */
.load-older-banner {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  gap: 8px;
  width: calc(100% - 2 * clamp(8px, 4vw, 18px));
  margin: 8px clamp(8px, 4vw, 18px) 0;
  padding: 10px 14px;
  background: rgba(var(--encre-rgb), 0.063);
  border: 1px solid rgba(var(--encre-rgb), 0.1);
  border-radius: calc(10px * var(--r-k));
  color: var(--text-mute);
  font-size: 13px; font-weight: 600;
  cursor: pointer;
}
.load-older-banner:hover:not(:disabled) {
  background: rgba(var(--encre-rgb), 0.112);
  color: var(--text);
}
.load-older-banner:disabled { cursor: default; opacity: 0.8; }
.load-older-spinner {
  width: 14px; height: 14px;
  border: 2px solid rgba(var(--encre-rgb), 0.2);
  border-top-color: var(--pink);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ============================================================
   PLAQUE PAPIER — texte des annonces posé sur média
   ============================================================
   Principe de la direction « Pellicule » : tout texte posé sur un média repose
   sur une plaque papier, jamais sur une ombre portée nue. Une ombre ne garantit
   rien — sur une photo claire ou chargée, du texte clair devient illisible
   quelle que soit l'ombre. La plaque, elle, garantit le contraste puisqu'elle
   fournit son propre fond.

   Les quatre déclarations qui suivent doivent rester IDENTIQUES : la carte de
   feed, la story, la vue détail et l'aperçu de l'éditeur partagent la même
   géométrie, faute de quoi la promesse WYSIWYG est de nouveau fausse. Le
   padding est exprimé en em pour suivre la taille de police du bloc.

   POURQUOI CE BLOC EST ICI et pas dans 01-utility.css : les quatre wrappers de
   bloc déclarent leur propre `text-shadow` (et `background: transparent` pour
   l'aperçu) dans 04-photo-tile.css, 07-modals.css et 08-moderator.css, qui
   chargent tous APRÈS 01-utility.css. À spécificité égale, c'est l'ordre de
   chargement qui tranche : la plaque y perdait son fond et gardait l'ombre de
   texte qu'elle est censée remplacer. 09-app.css étant la dernière feuille
   chargée, la règle s'applique sans recourir à un doublement de classe ni à
   `!important`.

   Ne s'applique qu'aux blocs de type texte. Les countdowns ont leurs 6 styles
   visuels propres, et les sondages leur propre habillage de plaque (.poll-opt). */
.ann-card-block-text,
.stories-block-text,
.detail-ann-block-text,
.ann-preview-block-text {
  background: var(--papier);
  padding: 0.14em 0.3em;
  border-radius: calc(2px * var(--r-k));
  box-shadow: 0 2px 8px rgba(var(--ombre-rgb), 0.22);
  /* La plaque rend l'ombre de texte inutile — et sur papier elle salissait. */
  text-shadow: none;
  /* Empêche la plaque de s'étirer sur toute la largeur quand le texte est court. */
  width: fit-content;
  max-width: 85%;
}

/* La couleur du texte n'est pas déclarée ici : elle vient du style inline posé
   sur le bloc (l'encre choisie par le modérateur), qui l'emporterait de toute
   façon sur cette feuille. */

/* ------ Chrome escamotable ---------------------------------- */
/* Objectif de la direction : au plus deux strates permanentes au-dessus du
   contenu, et une seule pendant la lecture. Mesuré avant : Header 69 px +
   FilterBar 63 px = 132 px, soit 16 % de l'écran en permanence sur un mobile.

   La FilterBar s'escamote vers le haut quand on descend dans le feed et revient
   dès qu'on remonte ou qu'on atteint le haut de la liste (cf.
   handleScrollDirection dans 09-app.js). Le Header, lui, reste toujours
   visible : il porte la cloche de notifications et le sélecteur de journée, que
   masquer coûterait plus qu'il ne rapporte.

   `grid-template-rows` plutôt qu'un simple translate : on veut que le contenu
   récupère réellement la place, pas qu'il glisse sous une barre invisible. */
.stage .filterbar-wrap {
  transition: max-height 0.24s cubic-bezier(0.2, 0.8, 0.2, 1),
              opacity 0.18s ease,
              transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1);
  max-height: 140px;
  overflow: hidden;
}
.stage-chrome-hidden .filterbar-wrap {
  max-height: 0;
  opacity: 0;
  transform: translateY(-8px);
  pointer-events: none;
}
