/* ==========================================================================
   Design tokens (couleurs) -- source unique de vérité pour le thème.
   Reproduit exactement la palette M3 de
   maquettes/.../storiland_narrative_system/DESIGN.md. tailwind.config
   (templates/base.html) référence ces variables via var(--x) au lieu
   de valeurs hexadécimales en dur : changer de thème ne recalcule
   jamais les classes Tailwind générées, seules ces variables changent.
   Jamais de couleur dark ajoutée directement dans un composant --
   toujours ces tokens, cf. claude.md/discussion Me.
   ========================================================================== */

:root {
  --background: #faf7f2;
  --surface: #fef8f3;
  --surface-dim: #ded9d4;
  --surface-bright: #fef8f3;
  --surface-container-lowest: #ffffff;
  --surface-container-low: #f8f3ee;
  --surface-container: #f2ede8;
  --surface-container-high: #ece7e2;
  --surface-container-highest: #e7e2dd;
  --surface-variant: #e7e2dd;
  --on-background: #1d1b19;
  --on-surface: #1d1b19;
  --on-surface-variant: #574145;
  --inverse-surface: #32302d;
  --inverse-on-surface: #f5f0eb;
  --outline: #8b7075;
  --outline-variant: #debfc3;
  --surface-tint: #ad2c54;
  --primary: #ad2c54;
  --on-primary: #ffffff;
  --primary-container: #f7658b;
  --on-primary-container: #650028;
  --inverse-primary: #ffb1c0;
  --primary-fixed: #ffd9df;
  --primary-fixed-dim: #ffb1c0;
  --on-primary-fixed: #3f0016;
  --on-primary-fixed-variant: #8c0e3d;
  --secondary: #755660;
  --on-secondary: #ffffff;
  --secondary-container: #fed5e1;
  --on-secondary-container: #795a64;
  --secondary-fixed: #ffd9e4;
  --secondary-fixed-dim: #e4bcc8;
  --on-secondary-fixed: #2b141d;
  --on-secondary-fixed-variant: #5b3f48;
  --tertiary: #5f5e5e;
  --on-tertiary: #ffffff;
  --tertiary-container: #989796;
  --on-tertiary-container: #30302f;
  --tertiary-fixed: #e5e2e1;
  --tertiary-fixed-dim: #c8c6c5;
  --on-tertiary-fixed: #1b1b1c;
  --on-tertiary-fixed-variant: #474746;
  --error: #ba1a1a;
  --on-error: #ffffff;
  --error-container: #ffdad6;
  --on-error-container: #93000a;
}

/* Dark mode -- palette "chaude" (retour utilisateur, 2026-08-15 sur
   l'écran Mes Rooms : le fond neutre #121212 et la rampe de gris
   étaient jugés "trop noirs", "agressifs", pas assez "chaleureux et
   élégants"). Rebâtie sur une base brune/plum légèrement teintée
   (au lieu d'un gris pur R=G=B) plutôt qu'un simple ajustement de
   luminosité -- même esprit que l'Ivoire chaud du thème clair
   (--background: #fef8f3, jamais blanc pur), transposé côté sombre.
   Les bordures (--outline-variant) sont rapprochées des tons de
   surface pour rester perceptibles sans trancher brutalement. Les
   rôles "fixed" (primary-fixed, on-primary-fixed, etc.) restent
   IDENTIQUES entre les deux thèmes -- non redéfinis ici, ils gardent
   la valeur de :root. --surface-dim reste le point le plus sombre de
   la rampe (rôle M3 "dim", usage rare). Aucun composant ne référence
   une couleur en dur : uniquement ces tokens, cf. claude.md. */
html.dark {
  --background: #1b1714;
  --surface: #1b1714;
  --surface-dim: #100d0b;
  --surface-bright: #2e2822;
  --surface-container-lowest: #221d19;
  --surface-container-low: #282220;
  --surface-container: #2e2826;
  --surface-container-high: #342e2b;
  --surface-container-highest: #3a332f;
  --surface-variant: #3a332f;
  --on-background: #f2ebe4;
  --on-surface: #f2ebe4;
  --on-surface-variant: #cbbdb7;
  --inverse-surface: #e7e2dd;
  --inverse-on-surface: #2e2826;
  --outline: #8a7873;
  --outline-variant: #3d332e;
  --surface-tint: #ffb1c0;
  --primary: #ffb1c0;
  --on-primary: #650028;
  --primary-container: #8c0e3d;
  --on-primary-container: #ffd9df;
  --inverse-primary: #ad2c54;
  --secondary: #e4bcc8;
  --on-secondary: #2b141d;
  --secondary-container: #5b3f48;
  --on-secondary-container: #ffd9e4;
  --tertiary: #c8c6c5;
  --on-tertiary: #1b1b1c;
  --tertiary-container: #474746;
  --on-tertiary-container: #e5e2e1;
  --error: #ffb4ab;
  --on-error: #690005;
  --error-container: #93000a;
  --on-error-container: #ffdad6;
}

/* ==========================================================================
   Material Symbols
   ========================================================================== */

.material-symbols-outlined {
  font-family: 'Material Symbols Outlined';
  font-weight: normal;
  font-style: normal;
  font-size: 20px;
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
  display: inline-block;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  -webkit-font-feature-settings: 'liga';
  -webkit-font-smoothing: antialiased;
  font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;
}

/* ==========================================================================
   Global
   ========================================================================== */

body {
  background-color: var(--background);
  -webkit-font-smoothing: antialiased;
}

/* ==========================================================================
   Glassmorphism header / composer
   ========================================================================== */

.glass-blur {
  backdrop-filter: blur(12px);
}

/* ==========================================================================
   Room feed
   ========================================================================== */

.post-border {
  border-bottom: 1px solid var(--outline-variant);
}

.reaction-chip {
  transition: all 0.1s ease;
}

.reaction-chip:active {
  transform: scale(0.95);
}

/* ==========================================================================
   Bottom sheets / modals
   ========================================================================== */

.sheet-enter {
  animation: sheetUp 0.25s ease-out;
}

@keyframes sheetUp {
  from { transform: translateY(100%); }
  to { transform: translateY(0); }
}

/* Modal centré (templates/components/modal.html::modal_dialog) --
   retour utilisateur, 2026-08-15 : certains contenus (ex. formulaire
   de signalement) lisent mieux en boîte centrée qu'en bottom-sheet
   plein écran. Zoom léger + fondu, jamais un glissement depuis le bas
   (réservé aux vrais bottom-sheets). */
.modal-enter {
  animation: modalPop 0.2s ease-out;
}

@keyframes modalPop {
  from { opacity: 0; transform: scale(0.96); }
  to { opacity: 1; transform: scale(1); }
}

.backdrop-enter {
  animation: fadeIn 0.2s ease-out;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* ==========================================================================
   Empty states
   ========================================================================== */

.empty-state-bounce {
  animation: float 6s ease-in-out infinite;
}

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

/* ==========================================================================
   Toasts (StorilandUI.showToast, static/js/ui.js)
   ========================================================================== */

.toast {
  animation: toastIn 0.2s ease-out;
}

.toast.toast-leaving {
  animation: toastOut 0.15s ease-in forwards;
}

@keyframes toastIn {
  from { opacity: 0; transform: translateY(-8px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes toastOut {
  from { opacity: 1; transform: translateY(0); }
  to { opacity: 0; transform: translateY(-8px); }
}

/* ==========================================================================
   Loader initial de page (templates/base.html#page-loader) -- protège
   le tout premier rendu contre le flash de HTML non stylé (2026-08-21).
   CSS brut délibérément, PAS de classes Tailwind : cet élément doit
   s'afficher correctement dès la toute première peinture, avant même
   que le script Tailwind CDN (chargé en defer, cf. base.html) n'ait
   fini de charger -- un élément stylé par Tailwind ne peut pas servir
   de paravent contre l'absence de Tailwind. Visible par défaut dans le
   HTML (aucune classe "opacity-0" posée au départ) ; base.html retire
   l'overlay via .opacity-0/.pointer-events-none une fois Tailwind prêt
   (onload) ou en échec (onerror) -- jamais de blocage indéfini.
   ========================================================================== */

#page-loader {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--background);
  transition: opacity 0.2s;
}

#page-loader.opacity-0 {
  opacity: 0;
  pointer-events: none;
}

.page-loader-spinner {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 9999px;
  border: 3px solid color-mix(in srgb, var(--primary) 20%, transparent);
  border-top-color: var(--primary);
  animation: pageLoaderSpin 0.7s linear infinite;
}

@keyframes pageLoaderSpin {
  to { transform: rotate(360deg); }
}

/* ==========================================================================
   Skeletons (StorilandUI.showSkeleton, static/js/ui.js)
   ========================================================================== */

.skeleton-block {
  background: linear-gradient(90deg, var(--surface-container) 25%, var(--surface-container-highest) 37%, var(--surface-container) 63%);
  background-size: 400% 100%;
  animation: skeletonShimmer 1.4s ease infinite;
}

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

/* ==========================================================================
   Room Feed (static/js/room_feed.js) -- petite transition d'entrée
   quand une Room finit de charger (en-tête + liste des Stories),
   pour mieux situer l'ouverture sans animation lourde.
   ========================================================================== */

.room-feed-enter {
  animation: roomFeedEnter 0.28s ease-out;
}

@keyframes roomFeedEnter {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .room-feed-enter {
    animation: none;
  }
}

/* ==========================================================================
   Lien personnel Messages anonymes -- repère visuel vers l'action
   d'activation quand aucun lien n'est encore actif (aucun lien créé,
   ou lien désactivé) : sans lui, l'endroit où "activer son lien"
   passait inaperçu. Halo fini (3 pulsations), jamais en boucle
   infinie, pour ne pas devenir agaçant une fois l'attention captée.
   Utilisé à trois endroits avec le même langage visuel : la carte de
   /anonymous-messages (static/js/anonymous_messages.js), l'icône
   "Messages" de la navbar (data-anonymous-link-hint) et la bulle
   "Active ton lien" ancrée directement sous cette icône
   (data-anonymous-link-hint-bubble, cf.
   templates/components/navbar.html), toutes deux pilotées depuis
   static/js/ui.js -- retour utilisateur, 2026-09-11 : le premier
   repère (limité à cette page) passait inaperçu tant que l'utilisateur
   n'avait pas déjà pensé à s'y rendre.
   ========================================================================== */

.anon-link-panel-enter {
  animation: anonLinkPanelEnter 0.4s ease-out;
}

@keyframes anonLinkPanelEnter {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

.anon-link-cta-pulse {
  animation: anonLinkCtaPulse 1.6s ease-out 3;
}

@keyframes anonLinkCtaPulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--primary) 45%, transparent); }
  70% { box-shadow: 0 0 0 9px color-mix(in srgb, var(--primary) 0%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--primary) 0%, transparent); }
}

@media (prefers-reduced-motion: reduce) {
  .anon-link-panel-enter,
  .anon-link-cta-pulse {
    animation: none;
  }
}

/* ==========================================================================
   Back Office (templates/components/admin_sidebar.html) -- structure
   uniquement, aucune couleur ici (tout vient des tokens via classes
   Tailwind dans le composant). Desktop : sidebar persistante en flux
   normal. Mobile (<900px) : sidebar en tiroir position fixed, cachée
   hors écran par défaut, ouverte via .admin-sidebar-open (posée par
   static/js/admin.js).
   ========================================================================== */

.admin-shell {
  display: flex;
  min-height: 100vh;
}

.admin-sidebar {
  width: 260px;
}

.admin-main {
  flex: 1;
  min-width: 0;
}

.admin-mobile-menu-button {
  display: none;
  position: fixed;
  top: 16px;
  left: 16px;
  z-index: 50;
  width: 44px;
  height: 44px;
}

@media (max-width: 900px) {
  .admin-shell {
    display: block;
  }

  .admin-sidebar {
    position: fixed;
    inset: 0 25% 0 0;
    width: auto;
    z-index: 60;
    transform: translateX(-100%);
    transition: transform 0.2s ease-out;
    overflow-y: auto;
  }

  .admin-sidebar.admin-sidebar-open {
    transform: translateX(0);
  }

  .admin-sidebar-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 55;
  }

  .admin-sidebar-backdrop.admin-sidebar-open {
    display: block;
  }

  .admin-mobile-menu-button {
    display: flex;
  }

  .admin-main {
    padding-top: 72px;
  }
}

/* ==========================================================================
   Landing page publique (templates/pages/landing.html, route "/") --
   reference design Stitch (maquettes/stitch_storiland_public_landing_page/,
   2026-08-22). Scope volontaire sous .landing : Hanken Grotesk est une
   police propre a CETTE page (le reste de l'app reste en Inter/font-body,
   cf. claude.md -- pas de reorganisation globale pour une seule page).
   Les couleurs/rayons/font-headline (Newsreader) restent les tokens
   globaux existants -- deja tres proches de la reference Stitch, pas
   besoin d'une palette parallele qui creerait une rupture visuelle au
   moment ou le visiteur se connecte et quitte cette page. */

.landing {
  font-family: "Hanken Grotesk", "Inter", sans-serif;
}

.landing .editorial-headline {
  letter-spacing: -0.02em;
  line-height: 0.95;
}

.landing .link-underline {
  position: relative;
  text-decoration: none;
}

.landing .link-underline::after {
  content: "";
  position: absolute;
  width: 100%;
  height: 1px;
  bottom: -2px;
  left: 0;
  background-color: currentColor;
  transform: scaleX(0);
  transform-origin: bottom right;
  transition: transform 0.4s cubic-bezier(0.86, 0, 0.07, 1);
}

.landing .link-underline:hover::after {
  transform: scaleX(1);
  transform-origin: bottom left;
}

.landing .reveal-on-scroll {
  opacity: 0;
  transform: translateY(32px);
  transition: opacity 0.9s cubic-bezier(0.2, 0.8, 0.2, 1), transform 0.9s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.landing .reveal-on-scroll.is-visible {
  opacity: 1;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  .landing .reveal-on-scroll {
    opacity: 1;
    transform: none;
    transition: none;
  }
}