/*
 * Hero title-logo layer.
 *
 * assets/css/app.css is generated Tailwind output and cannot gain new utility
 * classes without a rebuild, so the handful of rules the TMDB title artwork
 * needs live here and are loaded after it. These rules only style the logo
 * element: hero height, background, gradients, animation, dots and buttons are
 * untouched.
 */

.hero-title-logo {
  display: block;
  width: auto;
  height: auto;
  /* Preserve the artwork's own aspect ratio and never upscale past its source. */
  max-width: 480px;
  max-height: 132px;
  object-fit: contain;
  object-position: left center;
  /* Sit on the same left edge as the metadata row and overview above it. */
  margin: 0.25rem 0 1rem;
  filter: drop-shadow(0 2px 10px rgba(0, 0, 0, 0.55));
}

@media (min-width: 640px) {
  .hero-title-logo {
    max-width: 560px;
    max-height: 156px;
  }
}

@media (min-width: 1024px) {
  .hero-title-logo {
    max-width: 600px;
    max-height: 172px;
  }
}

@media (max-width: 639px) {
  .hero-title-logo {
    /* Keep clear of the metadata row and overview on narrow screens. */
    max-width: 78%;
    max-height: 84px;
    margin-bottom: 0.75rem;
  }
}

/* Very short viewports: shrink further so the overview and actions stay visible. */
@media (max-height: 620px) {
  .hero-title-logo {
    max-height: 72px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero-title-logo {
    filter: none;
  }
}

/*
 * Icon-only My List toggle (hero slides and the detail page).
 *
 * The Play and Info controls are px-6 py-3 text-sm, which resolves to a 44px
 * row. The button below is pinned to that same 44px with a fixed flex basis and
 * a 1:1 aspect ratio, so it stays a circle instead of being squeezed into an
 * oval when the action row wraps on tablet or mobile. Tailwind's compiled
 * stylesheet has no h-11/w-11 utilities, which is why the size lives here.
 */
.list-toggle-button {
  box-sizing: border-box;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  aspect-ratio: 1 / 1;
  border-radius: 9999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 0;
  -webkit-tap-highlight-color: transparent;
}

/*
 * An inline SVG sits on the text baseline, which pushes it a few pixels below
 * the true centre. display:block plus line-height:0 on the parent removes that
 * gap so both the plus and the tick are optically centred in the circle.
 */
.list-toggle-button > svg {
  display: block;
  flex: none;
  width: 20px;
  height: 20px;
}

/*
 * Detail page back control.
 *
 * The Tailwind utilities on this wrapper already resolve to top-left, but they
 * are compiled output that a rebuild could drop. The anchor is therefore also
 * pinned here with physical properties: explicit left/top, right:auto, and
 * absolute positioning against the detail hero section. Physical left/right are
 * unaffected by text direction, so an RTL document cannot flip the control to
 * the right edge, and right:auto neutralises any right offset that might be
 * inherited. The values mirror left-4/top-4, sm:left-6 and lg:left-10 exactly,
 * so this adds no visual change of its own.
 */
.detail-back-button {
  position: absolute;
  top: 1rem;
  left: 1rem;
  right: auto;
  z-index: 20;
}

@media (min-width: 640px) {
  .detail-back-button {
    left: 1.5rem;
  }
}

@media (min-width: 1024px) {
  .detail-back-button {
    left: 2.5rem;
  }
}

/*
 * Detail hero layout.
 *
 * Desktop is two columns: the poster keeps its own 220px column on the left and
 * the information block (label, title, metadata, genres, overview, action
 * buttons) takes the wide right column. The arbitrary template
 * `grid-cols-[220px_minmax(0,1fr)]` is not present in the compiled Tailwind
 * output, which only contains utilities that existed at build time, so the
 * template lives here. The column order matches the markup order in
 * render_detail_hero(): poster first, information second.
 *
 * Below 1024px the element stays the single-column flex stack the utilities
 * already describe, and the poster stays hidden, which is exactly the previous
 * mobile behaviour.
 */
@media (min-width: 1024px) {
  .detail-hero-layout {
    display: grid;
    grid-template-columns: 220px minmax(0, 1fr);
    align-items: start;
    column-gap: 2rem;
  }
}

/*
 * Download modal (detail page).
 *
 * The overlay, panel, season sidebar and download rows are built from scratch
 * here rather than from utilities: assets/css/app.css is generated Tailwind
 * output and cannot gain new classes without a rebuild, and this layout needs a
 * scrolling sidebar column next to a scrolling content column, which no
 * utility combination in the compiled sheet describes.
 *
 * It also replaces the old anchored dropdown, so the two rules that used to keep
 * that panel inside the viewport in app.css are gone.
 */
.download-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}

/* Toggled from detail.js. A dedicated state class keeps the open/closed switch
   out of the cascade battle between .hidden and .flex in the compiled sheet. */
.download-modal.is-open {
  display: flex;
}

.download-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.78);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.download-modal-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 40rem;
  /* Two independent scrollers (sidebar and content) need a definite height to
     resolve against, so the panel is capped by the viewport instead of growing
     with the season list. 100dvh follows for mobile browsers whose 100vh
     includes the collapsing URL bar. */
  max-height: calc(100vh - 2rem);
  max-height: calc(100dvh - 2rem);
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 1.25rem;
  background: #0a0a0a;
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.9);
  color: #fff;
}

.download-modal-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.download-modal-heading {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
}

.download-modal-heading-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 2.25rem;
  height: 2.25rem;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 0.75rem;
  background: rgba(255, 255, 255, 0.05);
  color: rgba(255, 255, 255, 0.9);
}

.download-modal-heading-icon > svg {
  display: block;
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
}

.download-modal-heading-text {
  min-width: 0;
}

.download-modal-title {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
}

.download-modal-subtitle {
  margin: 0.125rem 0 0;
  overflow: hidden;
  font-size: 0.75rem;
  color: rgba(255, 255, 255, 0.45);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Sits outside the scrolling body, so it stays reachable at every viewport
   size and at every scroll position inside the panel. */
.download-modal-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.05);
  color: rgba(255, 255, 255, 0.7);
  transition: background-color 0.15s, border-color 0.15s, color 0.15s;
}

.download-modal-close:hover {
  border-color: rgba(255, 255, 255, 0.2);
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
}

.download-modal-body {
  display: flex;
  flex: 1 1 auto;
  min-height: 0;
}

.download-modal-seasons {
  display: flex;
  flex: none;
  flex-direction: column;
  gap: 0.25rem;
  width: 7rem;
  padding: 0.75rem 0.5rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  border-right: 1px solid rgba(255, 255, 255, 0.08);
}

.download-modal-season {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.125rem;
  width: 100%;
  padding: 0.5rem 0.625rem;
  border: 1px solid transparent;
  border-radius: 0.625rem;
  background: transparent;
  color: rgba(255, 255, 255, 0.55);
  text-align: left;
  transition: background-color 0.15s, border-color 0.15s, color 0.15s;
}

.download-modal-season:hover {
  background: rgba(255, 255, 255, 0.06);
  color: #fff;
}

.download-modal-season[aria-selected="true"] {
  border-color: rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
}

.download-modal-season-code {
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.2;
}

.download-modal-season-name {
  overflow: hidden;
  max-width: 100%;
  font-size: 0.6875rem;
  line-height: 1.2;
  color: rgba(255, 255, 255, 0.4);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.download-modal-season[aria-selected="true"] .download-modal-season-name {
  color: rgba(255, 255, 255, 0.6);
}

.download-modal-content {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.download-modal-item {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  width: 100%;
  padding: 0.625rem 0.75rem;
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 0.75rem;
  background: rgba(255, 255, 255, 0.02);
  text-align: left;
  transition: background-color 0.15s, border-color 0.15s;
}

.download-modal-item:hover {
  border-color: rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.07);
}

.download-modal-item-server {
  overflow: hidden;
  font-size: 0.8125rem;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.9);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.download-modal-item-meta {
  overflow: hidden;
  font-size: 0.75rem;
  color: rgba(255, 255, 255, 0.45);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.download-modal-empty {
  padding: 1.5rem 0.75rem;
  font-size: 0.8125rem;
  color: rgba(255, 255, 255, 0.4);
  text-align: center;
}

/*
 * Episode filter.
 *
 * The toolbar is a sibling of the scrolling area rather than a sticky child of
 * it, so the field stays reachable at any scroll position without an offset that
 * would have to cancel the padding underneath and could overlap the first row.
 */
.download-modal-toolbar {
  flex: none;
  padding: 0.75rem 0.75rem 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

.download-modal-scroll {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  padding: 0.75rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.download-modal-search {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0 0.75rem;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.05);
  transition: background-color 0.15s, border-color 0.15s;
}

.download-modal-search:focus-within {
  border-color: rgba(255, 255, 255, 0.35);
  background: rgba(255, 255, 255, 0.08);
}

.download-modal-search-icon {
  display: inline-flex;
  flex: none;
  color: rgba(255, 255, 255, 0.45);
}

.download-modal-search-icon > svg {
  display: block;
  width: 1rem;
  height: 1rem;
}

.download-modal-search input {
  width: 100%;
  min-width: 0;
  padding: 0.5rem 0;
  border: 0;
  background: transparent;
  font-size: 0.8125rem;
  color: #fff;
  outline: none;
}

.download-modal-search input::placeholder {
  color: rgba(255, 255, 255, 0.35);
}

/* app.css already hides the native decoration for every search input; repeating
   it here keeps the single control the filter offers. */
.download-modal-search input::-webkit-search-cancel-button,
.download-modal-search input::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
  display: none;
}

.download-modal-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/*
 * One card per episode, never one per server: the heading names the episode and
 * the row of pills underneath is every configured download server for it.
 */
.download-modal-episode {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.75rem;
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 0.75rem;
  background: rgba(255, 255, 255, 0.02);
  transition: background-color 0.15s, border-color 0.15s;
}

.download-modal-episode:hover {
  border-color: rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.07);
}

/* The filter hides rows by toggling this attribute, so it needs to outrank the
   display:flex above. */
.download-modal-episode[hidden] {
  display: none;
}

.download-modal-episode-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
}

.download-modal-episode-code {
  font-size: 0.8125rem;
  font-weight: 700;
  color: #fff;
}

.download-modal-episode-meta {
  font-size: 0.75rem;
  color: rgba(255, 255, 255, 0.45);
}

/* Servers sit on one line and wrap onto the next once they run out of room,
   which is what keeps a three-server episode readable on a phone. */
.download-modal-episode-servers {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
}

.download-modal-server {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  max-width: 100%;
  padding: 0.375rem 0.75rem;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.05);
  font-size: 0.75rem;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.8);
  transition: background-color 0.15s, border-color 0.15s, color 0.15s;
}

.download-modal-server > span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.download-modal-server:hover {
  border-color: rgba(255, 255, 255, 0.25);
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
}

.download-modal-server-icon {
  display: inline-flex;
  flex: none;
  color: rgba(255, 255, 255, 0.5);
}

.download-modal-server-icon > svg {
  display: block;
  width: 0.875rem;
  height: 0.875rem;
}

.download-modal-server:hover .download-modal-server-icon {
  color: rgba(255, 255, 255, 0.85);
}

.download-modal :focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.7);
  outline-offset: 2px;
}

/* Below the sm breakpoint the two columns stack: the seasons become a single
   horizontally scrolling row above the list, which keeps the panel narrow
   enough that nothing can overflow sideways. */
@media (max-width: 639px) {
  .download-modal {
    padding: 0.75rem;
  }

  .download-modal-panel {
    max-height: calc(100vh - 1.5rem);
    max-height: calc(100dvh - 1.5rem);
    border-radius: 1rem;
  }

  .download-modal-body {
    flex-direction: column;
  }

  .download-modal-seasons {
    flex-direction: row;
    gap: 0.375rem;
    width: auto;
    padding: 0.625rem 0.75rem;
    overflow-x: auto;
    overflow-y: hidden;
    border-right: 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
    scrollbar-width: none;
  }

  .download-modal-seasons::-webkit-scrollbar {
    display: none;
  }

  .download-modal-season {
    flex: none;
    flex-direction: row;
    align-items: baseline;
    gap: 0.375rem;
    width: auto;
    padding: 0.375rem 0.625rem;
  }

  .download-modal-season-name {
    max-width: 9rem;
  }
}

/*
 * Ad layer clamp.
 *
 * The ad network builds its own containers at runtime, including full-viewport
 * fixed click layers with an inline z-index that is far above anything the site
 * uses. assets/css/app.css is generated Tailwind output, so it cannot gain a
 * utility for this and the clamp lives here instead.
 *
 * The selectors match on an inline position in the style attribute, which no
 * element in this project ever sets: the markup only ever uses the `fixed`
 * utility class plus inline pointer-events or padding, so these signatures can
 * only come from a node the ad script injected. Both spellings are listed
 * because a style attribute serialises differently depending on how it was
 * written - assigning element.style.position renders `position: fixed`, while a
 * raw style string can render `position:fixed` - and an attribute selector is a
 * literal substring test with no normalisation. `--rdata` is the network's own
 * marker on the layer that wraps the page.
 *
 * `!important` is required and sufficient. The network assigns z-index as an
 * inline declaration, and an important author declaration outranks an inline
 * one that is not itself important.
 *
 * 10 sits above ordinary page content, so the ad stays visible, and below every
 * interface layer: the detail back button (20), the dropdowns (30 and 40), the
 * header buttons and the floating navigation (50), the player (40 and 50), the
 * player top controls (100) and the modals (100).
 */
[style*="position: fixed"],
[style*="position:fixed"],
[style*="--rdata"] {
  z-index: 10 !important;
}

/*
 * The fade-in wrapper stops being a stacking context once it has played.
 *
 * Every page body sits inside .animate-fade-in. Its keyframes end on
 * `opacity: 1; transform: none`, which is already the element's natural state,
 * but the `both` fill in the compiled sheet keeps the transform alive as an
 * identity matrix afterwards. A transform of anything other than none makes the
 * wrapper a stacking context and a containing block, so it and everything inside
 * it are painted at its own level instead of the page's: the z-20 on the detail
 * action row and on the hero content would be trapped inside it and the clamped
 * ad layer would still cover Play, Download and My List.
 *
 * `backwards` behaves exactly like `both` for this animation, which has no delay
 * and whose final keyframe equals the natural state, so nothing changes visually
 * and no content flashes: the element simply returns to having no transform once
 * the fade has finished, which lets the promoted controls win against the clamp.
 */
.animate-fade-in {
  animation-fill-mode: backwards;
}
