/* ══════════════════════════════════════════════════════════════════
   CinéAstre — Design System
   Sobre, monochrome, "liquid glass" (verre dépoli translucide façon
   Apple). Un seul jeu de règles pour toutes les pages, du site public
   au panel admin — voir README.md #design-system.
   ══════════════════════════════════════════════════════════════════ */

:root {
  /* Surfaces */
  --surface:      #0a0a0a;
  --elevated:     #111111;
  --base:         #000000;

  /* Text */
  --text:         #f2f2f0;
  --text-dim:     #a3a29c;
  --text-faint:   #6b6a65;

  /* Glass */
  --glass-bg:          rgba(255, 255, 255, 0.055);
  --glass-bg-hover:    rgba(255, 255, 255, 0.10);
  --glass-border:      rgba(255, 255, 255, 0.10);
  --glass-border-hover:rgba(255, 255, 255, 0.20);
  --glass-highlight:   rgba(255, 255, 255, 0.09);
  --glass-blur:        20px;

  /* Radius scale */
  --r-sm: 10px;
  --r-md: 14px;
  --r-lg: 20px;
  --r-xl: 28px;

  color-scheme: dark;
}

html {
  scroll-behavior: smooth;
}

html, body {
  background-color: var(--base);
  color: var(--text);
  overscroll-behavior-y: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

::-webkit-scrollbar { width: 0; background: transparent; }
* { scrollbar-width: none; }

a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
input, textarea, select { font-family: inherit; }

/* Posters, backdrops, TMDB title logos: no browser drag-ghost, no accidental text selection. */
img { -webkit-user-drag: none; user-drag: none; -webkit-touch-callout: none; user-select: none; }

/* ── Liquid glass surfaces ─────────────────────────────────────────
   Real translucency + blur, a soft inner highlight along the top edge
   (the "light catching the glass" cue), and a hairline border. Every
   page — home, watch, admin — pulls from these three classes only. */

.glass-panel {
  background: var(--glass-bg);
  backdrop-filter: blur(var(--glass-blur)) saturate(150%);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(150%);
  border: 1px solid var(--glass-border);
  box-shadow: inset 0 1px 0 var(--glass-highlight), 0 8px 24px -12px rgba(0, 0, 0, 0.5);
}
/* Zero-specificity default radius: any explicit Tailwind rounded-* class on
   the same element (rounded-full pills, circular badges, !rounded-none
   sidebars) wins over this without fighting cascade order. */
:where(.glass-panel) { border-radius: var(--r-lg); }

.glass-button {
  background: var(--glass-bg);
  backdrop-filter: blur(var(--glass-blur)) saturate(150%);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(150%);
  color: var(--text);
  border: 1px solid var(--glass-border);
  box-shadow: inset 0 1px 0 var(--glass-highlight);
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}
.glass-button:hover {
  background: var(--glass-bg-hover);
  border-color: var(--glass-border-hover);
}
.glass-button:active { transform: scale(0.97); }

.glass-input {
  /* background-color, not the background shorthand: the shorthand resets
     background-repeat/-position/-image to their defaults for anything that
     doesn't set them, which silently broke the chevron on <select> elements
     that add their own background-image (tiled instead of a single icon on
     the right) — only whichever stylesheet happened to load last "won". */
  background-color: rgba(255, 255, 255, 0.035);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid var(--glass-border);
  color: var(--text);
  transition: border-color 0.2s ease, background-color 0.2s ease;
}
.glass-input::placeholder { color: var(--text-faint); }
.glass-input:focus {
  border-color: var(--glass-border-hover);
  background-color: rgba(255, 255, 255, 0.06);
  outline: none;
}

/* The mobile search field floats directly over page content (not inside an
   already-opaque .glass-panel like every other .glass-input use), so the
   default 3.5% tint reads as barely-there over a bright poster/backdrop —
   same problem .solid-dropdown documents above, one component up. Matches
   its #0c0c0c so the field and the suggestions dropdown right below it
   read as one continuous solid surface. */
#search-mobile-overlay .glass-input {
  background-color: rgba(12, 12, 12, 0.92);
}
#search-mobile-overlay .glass-input:focus {
  background-color: rgba(12, 12, 12, 0.97);
}

/* iOS Safari auto-zooms the page on focus for any text input/select/textarea
   whose computed font-size is under 16px — most inputs here inherit a
   Tailwind text-sm/text-xs (14px/12px) from wherever they're used (the
   mobile search bar being the one that actually got reported). Forcing 16px
   only below the md breakpoint fixes the zoom without touching desktop
   sizing, where this was never a problem. */
@media (max-width: 767px) {
  input, select, textarea { font-size: 16px; }
}

.glass-card-overlay {
  background: linear-gradient(to top, #000 0%, rgba(0,0,0,0.45) 50%, transparent 100%);
}

/* Looks like frosted glass but with NO backdrop-filter at all — this variant
   is only ever revealed by a hover fade (e.g. the play button on a
   "continuer à regarder" card), and any backdrop-filter, light or heavy,
   forces the browser to resample the blur on every single frame of that
   fade. Dropping from 20px to 8px cut the cost but didn't remove the class
   of problem; a flat dark background with a touch of extra opacity reads
   close enough to "glass" here and costs the same as any other opacity
   transition — the actual fix, not a smaller version of the same one. */
.glass-panel-hover {
  background: rgba(20, 20, 20, 0.72);
  border: 1px solid var(--glass-border);
  box-shadow: inset 0 1px 0 var(--glass-highlight), 0 8px 24px -12px rgba(0, 0, 0, 0.5);
}
:where(.glass-panel-hover) { border-radius: var(--r-lg); }

/* ── Buttons ───────────────────────────────────────────────────────
   One shared shape language: pill radius, three sizes, three
   intents. Used identically on the homepage and inside the admin. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font-weight: 600;
  white-space: nowrap;
  border-radius: 9999px;
  border: 1px solid transparent;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease, transform 0.15s ease, opacity 0.2s ease;
  line-height: 1;
}
.btn:active { transform: scale(0.97); }
.btn:disabled { opacity: 0.45; pointer-events: none; }

.btn-sm { padding: 0.55rem 1.1rem; font-size: 0.75rem; gap: 0.375rem; }
.btn-md { padding: 0.75rem 1.5rem; font-size: 0.8125rem; }
.btn-lg { padding: 0.95rem 2rem; font-size: 0.9375rem; }

.btn-icon { padding: 0.625rem; border-radius: 9999px; }
.btn-icon.btn-sm { padding: 0.45rem; }

.btn-primary { background: #fff; color: #000; }
.btn-primary:hover { background: rgba(255,255,255,0.88); }

.btn-glass {
  background: var(--glass-bg);
  backdrop-filter: blur(var(--glass-blur)) saturate(150%);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(150%);
  color: var(--text);
  border-color: var(--glass-border);
  box-shadow: inset 0 1px 0 var(--glass-highlight);
}
.btn-glass:hover { background: var(--glass-bg-hover); border-color: var(--glass-border-hover); }

.btn-active {
  background: rgba(255,255,255,0.14);
  color: var(--text);
  border-color: var(--glass-border-hover);
}

.btn-ghost { background: transparent; color: var(--text-dim); }
.btn-ghost:hover { color: var(--text); background: rgba(255,255,255,0.05); }

/* ── Solid dropdown panels (profile menu, source picker) ─────────────
   Genuinely opaque — NOT a translucent glass surface. A dropdown sitting
   over a bright/colorful hero image needs a fully solid backing or it reads
   as "still see-through" no matter how high the alpha value; backdrop-blur
   alone doesn't fix that once alpha < 1. Plain solid color, zero ambiguity. */
.solid-dropdown {
  background: #0c0c0c;
  border: 1px solid var(--glass-border);
  border-radius: var(--r-lg);
  box-shadow: 0 20px 50px -12px rgba(0, 0, 0, 0.7);
}

/* ── Navbar liquid bubble ──────────────────────────────────────────
   Slides behind the active/hovered nav link (JS sets left/top/width/height). */
.nav-bubble {
  position: absolute;
  border-radius: 9999px;
  background: var(--glass-bg-hover);
  backdrop-filter: blur(var(--glass-blur)) saturate(150%);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(150%);
  border: 1px solid var(--glass-border-hover);
  box-shadow: inset 0 1px 0 var(--glass-highlight);
  opacity: 0;
  pointer-events: none;
  transition: left 0.18s cubic-bezier(0.4, 0, 0.2, 1), width 0.18s cubic-bezier(0.4, 0, 0.2, 1),
              top 0.15s ease, height 0.15s ease, opacity 0.12s ease;
}

/* ── Navbar search pill ────────────────────────────────────────────
   .ai-search-form wraps the desktop pill and the mobile input (navbar.js)
   and always gets the smooth width/background/border transition below —
   applies whether or not AI search is actually available to this visitor
   (logged out, or opted out in /profile#parametres both just skip the
   .ai-ring modifier further down, they still get the same fluid
   grow/shrink on focus). width/background-color/border-color transition
   hand-written here rather than as separate Tailwind classes
   (transition-[width,...] + duration-300 + ease-[cubic-bezier(...)]) on
   the element itself — confirmed live in prod's compiled CSS that
   transition-[...] bundles its own default 150ms/ease timing, and which of
   that vs. the separate duration-300/ease-[...] rules actually wins is
   down to generation order in the compiled file, not visual intent. The
   pill was transitioning, just on the wrong (default) timing — read as "no
   transition" because it snapped noticeably faster than intended. One
   explicit rule here has no such ambiguity. */
.ai-search-form {
  position: relative;
  transition: width 0.3s cubic-bezier(0.22, 1, 0.36, 1), background-color 0.3s cubic-bezier(0.22, 1, 0.36, 1), border-color 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}

/* ── AI mood search ring (.ai-ring, on top of .ai-search-form) ──────
   Only added by navbar.js when AI search is actually available to this
   visitor. A conic-gradient + mask-composite ring sweeps around the pill's
   edge at all times, slowly (a permanent, ambient cue that this bar also
   does AI mood search) — while a Cinémood request is in flight
   (.ai-search-loading) it speeds way up to read as "searching now".
   Deliberately multicolor (violet → pink → amber → cyan) rather than this
   design system's usual monochrome — the one spot on the whole site meant
   to read as "AI", so it gets to break the palette on purpose. Browsers
   without @property support (older Firefox) just show a static ring
   instead of a spinning one — the --ai-angle custom property falls back to
   a discrete jump instead of an interpolated animation, never a broken one. */
.ai-search-form.ai-ring::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.5px;
  background: conic-gradient(from var(--ai-angle, 0deg),
    transparent 0deg, transparent 225deg,
    #8b5cf6 250deg, #ec4899 280deg, #f59e0b 310deg, #22d3ee 340deg,
    transparent 355deg);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0.55;
  pointer-events: none;
  transition: opacity 0.3s ease;
  animation: ai-search-spin 7s linear infinite;
}
.ai-search-form.ai-ring.ai-search-loading::before {
  opacity: 1;
  animation: ai-search-spin 0.9s linear infinite;
}
@property --ai-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}
@keyframes ai-search-spin { to { --ai-angle: 360deg; } }
@media (prefers-reduced-motion: reduce) {
  /* Blanket reset: collapses every animation/transition to near-instant
     instead of removing them outright, so JS that waits on an
     'animationend' (navbar's mobile-menu close, e.g.) still fires
     correctly — just immediately — rather than needing a second,
     motion-specific code path. Covers the hero's Ken Burns zoom and
     slide-push transitions, the biggest vestibular triggers on the site. */
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .ai-search-form.ai-ring::before, .ai-search-form.ai-ring.ai-search-loading::before { opacity: 0.4; }
}

/* ── AI mood results frame (.ai-mood-frame, search.js's aiMode heading) ──
   Same conic-gradient ring as .ai-ring above (shares its --ai-angle
   property and ai-search-spin keyframe), reused on the /search?ai=1
   results heading — the search bar's own ring is out of view once you've
   scrolled into the results, so this carries the "this came from an AI
   mood search" cue down the page instead of it disappearing after submit. */
.ai-mood-frame {
  position: relative;
  border-radius: var(--r-lg);
}
.ai-mood-frame::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.5px;
  background: conic-gradient(from var(--ai-angle, 0deg),
    transparent 0deg, transparent 225deg,
    #8b5cf6 250deg, #ec4899 280deg, #f59e0b 310deg, #22d3ee 340deg,
    transparent 355deg);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0.6;
  pointer-events: none;
  animation: ai-search-spin 7s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .ai-mood-frame::before { opacity: 0.4; }
}

/* ── Toggle switch ─────────────────────────────────────────────────
   Plain pill switch, no library — used on the profile page's settings
   (e.g. "Recherche IA"). White thumb on a dim track when off, inverted
   (dark thumb on a bright track) when on, so the two states read clearly
   against this design system's dark surfaces without needing an accent
   color. */
.toggle-switch { position: relative; display: inline-flex; flex-shrink: 0; cursor: pointer; }
.toggle-switch input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.toggle-track {
  width: 44px; height: 26px; border-radius: 9999px;
  background: rgba(255, 255, 255, 0.1); border: 1px solid var(--glass-border);
  transition: background-color 0.2s ease, border-color 0.2s ease;
  position: relative; pointer-events: none;
}
.toggle-thumb {
  position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%;
  background: #fff; transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.2s ease;
}
.toggle-switch input:checked + .toggle-track { background: rgba(255, 255, 255, 0.92); border-color: transparent; }
.toggle-switch input:checked + .toggle-track .toggle-thumb { transform: translateX(18px); background: #000; }
.toggle-switch input:focus-visible + .toggle-track { outline: 2px solid rgba(255, 255, 255, 0.5); outline-offset: 2px; }
.toggle-switch input:disabled { cursor: not-allowed; }
.toggle-switch input:disabled + .toggle-track { opacity: 0.5; }

/* ── Text ──────────────────────────────────────────────────────── */
.text-balance { text-wrap: balance; }

/* ── Layout ────────────────────────────────────────────────────── */
.loader-container {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  background-color: var(--base);
}

.hide-scrollbar::-webkit-scrollbar { display: none; }
.hide-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }

/* Fades the right edge of a horizontally-scrolling row (tabs, filter pills) so a
   cut-off button reads as "more to scroll" instead of looking clipped/broken —
   image carousels already get this for free from the poster itself being cut off. */
.scroll-fade-x { mask-image: linear-gradient(to right, black calc(100% - 28px), transparent 100%); -webkit-mask-image: linear-gradient(to right, black calc(100% - 28px), transparent 100%); }

/* ── Misc app-wide helpers (no build step, so no @apply available) ─── */
.line-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.line-clamp-3 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }

@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.animate-fade-in { animation: fadeIn 0.3s ease-out; }

/* ── Home hero carousel ────────────────────────────────────────────
   Slow "Ken Burns" drift on the active backdrop + a fade/rise for the
   title block on every slide change. The hero's whole markup is rebuilt
   on every slide change (home.js), so these all use `animation` (plays on
   insertion) rather than `transition` (needs a persisted element with a
   changing value, which a full rebuild never gives it). */
@keyframes heroKenBurns { from { transform: scale(1.0); } to { transform: scale(1.08); } }
.hero-kenburns { transition: transform 1.2s ease-out; }
.hero-kenburns-active { animation: heroKenBurns 8s ease-out forwards; }

@keyframes heroTextIn { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }
.hero-text-in { animation: heroTextIn 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; }

/* Directional "push" slide transition: the incoming backdrop slides in from
   the side autoplay/navigation is heading toward while the outgoing one gets
   pushed out the other side, instead of a plain crossfade. Direction is
   decided in JS (home.js's goToSlide) from which way is the shorter path
   around the loop. Both halves share one duration so they always meet. */
@keyframes heroPushInFromRight { from { transform: translateX(100%); } to { transform: translateX(0%); } }
@keyframes heroPushInFromLeft  { from { transform: translateX(-100%); } to { transform: translateX(0%); } }
@keyframes heroPushOutToLeft   { from { transform: translateX(0%); } to { transform: translateX(-100%); } }
@keyframes heroPushOutToRight  { from { transform: translateX(0%); } to { transform: translateX(100%); } }
.hero-push-in-right  { animation: heroPushInFromRight 1400ms cubic-bezier(0.22, 1, 0.36, 1) both; }
.hero-push-in-left   { animation: heroPushInFromLeft  1400ms cubic-bezier(0.22, 1, 0.36, 1) both; }
.hero-push-out-left  { animation: heroPushOutToLeft   1400ms cubic-bezier(0.22, 1, 0.36, 1) both; }
.hero-push-out-right { animation: heroPushOutToRight  1400ms cubic-bezier(0.22, 1, 0.36, 1) both; }

/* Segmented progress-bar slide indicator: the active segment fills left-to-right
   over the autoplay duration (JS sets animation-duration to match) instead of a
   static dot, so the indicator itself shows "time until next slide". */
@keyframes heroSegFill { from { width: 0%; } to { width: 100%; } }
.hero-seg-fill { animation-name: heroSegFill; animation-timing-function: linear; animation-fill-mode: forwards; }

/* ── Dropdown open transition ──────────────────────────────────────
   Shared by every popover-style menu (profile menu, source picker, ...).
   Relies on display:none resetting animation state: toggling the paired
   `hidden` class off replays this each time the menu opens, no JS needed. */
@keyframes dropdownIn { from { opacity: 0; transform: translateY(-6px) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); } }
.dropdown-in { animation: dropdownIn 0.18s cubic-bezier(0.16, 1, 0.3, 1) both; }

/* Reverse of the above, for the few popovers that need an actual close
   animation instead of an instant `hidden` (the mobile search overlay) —
   paired with an `animationend` listener in JS that swaps in `hidden` only
   once this has actually finished, rather than a setTimeout guessing at
   the duration. */
@keyframes dropdownOut { from { opacity: 1; transform: translateY(0) scale(1); } to { opacity: 0; transform: translateY(-6px) scale(0.98); } }
.dropdown-out { animation: dropdownOut 0.15s cubic-bezier(0.4, 0, 1, 1) both; }

/* Preview modal (movie-card click) — backdrop fades, panel scales up from
   just below full size. Same animationend-driven close pattern as
   .dropdown-out above: JS waits for previewPanelOut to actually finish
   before removing the overlay, instead of guessing a timeout. */
@keyframes previewBackdropIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes previewBackdropOut { from { opacity: 1; } to { opacity: 0; } }
@keyframes previewPanelIn { from { opacity: 0; transform: scale(0.94) translateY(12px); } to { opacity: 1; transform: scale(1) translateY(0); } }
@keyframes previewPanelOut { from { opacity: 1; transform: scale(1) translateY(0); } to { opacity: 0; transform: scale(0.96) translateY(8px); } }
.preview-backdrop-in { animation: previewBackdropIn 0.2s ease-out both; }
.preview-backdrop-out { animation: previewBackdropOut 0.18s ease-in both; }
.preview-panel-in { animation: previewPanelIn 0.25s cubic-bezier(0.16, 1, 0.3, 1) both; }
.preview-panel-out { animation: previewPanelOut 0.18s cubic-bezier(0.4, 0, 1, 1) both; }

/* Skeleton loading blocks — a page's real layout, filled with a soft
   shimmering placeholder, shown while its data is still in flight instead
   of a bare spinner. Feels faster even at identical load time (previews
   the eventual shape instead of blocking on a blank/generic state) — same
   reasoning as every major streaming/social app's own loading state. */
@keyframes skeletonShimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
.skeleton-block {
  background: linear-gradient(100deg, rgba(255,255,255,0.04) 30%, rgba(255,255,255,0.09) 50%, rgba(255,255,255,0.04) 70%);
  background-size: 200% 100%;
  animation: skeletonShimmer 1.6s ease-in-out infinite;
}

/* ── Episode groups (watch page) ─────────────────────────────────────
   Native <details>/<summary> — collapsed groups of 20 episodes instead of
   dumping every episode of a long season on screen at once. */
.episode-group summary { list-style: none; }
.episode-group summary::-webkit-details-marker { display: none; }
.episode-group[open] .episode-group-chevron { transform: rotate(180deg); }

