/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-sm);
  font-weight: 700;
  letter-spacing: -0.005em;
  font-size: var(--fs-base);
  white-space: nowrap;
  transition: transform var(--duration-fast) var(--ease-standard),
    background-color var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard);
}

.btn:active {
  transform: scale(0.97);
}

.btn-primary {
  background: var(--color-accent);
  color: var(--color-accent-contrast);
}

.btn-primary:hover,
.btn-primary:focus-visible {
  background: var(--color-accent-strong);
}

.btn-secondary {
  background: rgba(255, 255, 255, 0.08);
  color: var(--color-text);
  border: 1px solid var(--color-border-strong);
  backdrop-filter: blur(8px);
}

.btn-secondary:hover,
.btn-secondary:focus-visible {
  background: rgba(255, 255, 255, 0.16);
}

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-min);
  height: var(--tap-min);
  flex-shrink: 0;
  border-radius: var(--radius-full);
  color: var(--color-text);
  transition: background-color var(--duration-fast) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard);
}

.icon-btn:active {
  transform: scale(0.88);
}

.icon-btn:hover,
.icon-btn:focus-visible {
  background: var(--color-surface-hover);
}

.icon-btn svg {
  width: 20px;
  height: 20px;
}

/* ---------- Nav ----------
   Absolutely positioned over the top of the hero — NOT fixed or sticky. It sits
   out of flow (so the backdrop still runs edge-to-edge from y=0 behind it) but
   is anchored to the page, so it scrolls away naturally with the hero instead of
   following the viewport. */
.site-nav {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: var(--z-nav);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: clamp(56px, 13vw, 68px);
  padding-inline: var(--container-padding);
  padding-top: env(safe-area-inset-top);
  background: transparent;
}

/* Keeps the bar legible over bright areas of the backdrop. Permanent now that
   the bar no longer sticks around to solidify on scroll. */
.site-nav::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(6, 7, 10, 0.5) 0%, rgba(6, 7, 10, 0) 100%);
}

.site-nav .nav-logo,
.site-nav .icon-btn {
  filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.55));
}

.nav-logo {
  display: flex;
  align-items: center;
  min-height: var(--tap-min);
  gap: var(--space-2);
  font-size: var(--fs-md);
  font-weight: 800;
  letter-spacing: 0.01em;
  color: var(--color-text);
  margin-inline-end: var(--space-2);
}

.nav-logo svg {
  width: clamp(1.5rem, 6vw, 1.75rem);
  height: clamp(1.5rem, 6vw, 1.75rem);
  flex-shrink: 0;
  color: var(--color-accent);
}

.nav-spacer {
  flex: 1;
}

.nav-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* ---------- Search ----------
   Mobile-first: a full-screen search *view* with the field at the top, the way a
   native tab-bar app pushes a search screen. Desktop turns it into a centred
   command-palette panel (see responsive.css). */
.search-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  display: flex;
  align-items: stretch;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-base) var(--ease-standard);
}

.search-overlay.is-open {
  opacity: 1;
  pointer-events: auto;
}

.search-panel {
  position: relative;
  width: 100%;
  display: flex;
  flex-direction: column;
  background: var(--color-bg);
  padding: calc(env(safe-area-inset-top) + var(--space-3)) var(--space-4) 0;
  transform: translateY(8px);
  transition: transform var(--duration-base) var(--ease-standard);
}

.search-overlay.is-open .search-panel {
  transform: translateY(0);
}

.search-input-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-full);
  padding-inline: var(--space-2) var(--space-2);
  background: var(--color-surface);
  flex-shrink: 0;
}

.search-leading-icon {
  display: flex;
  flex-shrink: 0;
}

.search-input-row svg {
  width: 20px;
  height: 20px;
  color: var(--color-text-tertiary);
  flex-shrink: 0;
}

.search-back svg {
  color: var(--color-text);
}

.search-clear {
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-fast) var(--ease-standard);
}

.search-panel.has-query .search-clear {
  opacity: 1;
  pointer-events: auto;
}

.search-input {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: none;
  padding-block: var(--space-3);
  /* 16px minimum stops iOS Safari from zooming the viewport on focus. */
  font-size: 16px;
  color: var(--color-text);
}

.search-input:focus {
  outline: none;
}

.search-input::-webkit-search-cancel-button {
  display: none;
}

.search-input::placeholder {
  color: var(--color-text-tertiary);
}

/* ---------- Recent-search history dropdown ----------
   Positioned against `.search-input-wrap` (not `.search-panel`, which carries
   its own top padding/safe-area inset) so it always opens exactly under the
   pill regardless of that padding — see search.js. Deliberately translucent
   (`color-mix` + blur, same recipe `calendar.css`/`detail.css` scrims use)
   rather than opaque, per the requested "slightly transparent" look. */
.search-input-wrap {
  position: relative;
  flex-shrink: 0;
}

.search-history-panel {
  position: absolute;
  inset-inline: 0;
  top: calc(100% + var(--space-2));
  z-index: 4;
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border-strong);
  background: color-mix(in srgb, var(--color-bg-elevated) 86%, transparent);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  box-shadow: var(--shadow-elevated);
  overflow: hidden;
  opacity: 0;
  transform: translateY(-6px);
  pointer-events: none;
  transition: opacity var(--duration-fast) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard);
}

.search-history-panel.is-open {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

/* Capped to exactly 6 rows (`--tap-min` each) — the rest (up to the 20
   `searchHistory.js` stores) is reached by scrolling this region, which is
   independent of the page/results scroll behind it. */
.search-history-scroll {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  max-height: calc(var(--tap-min) * 6);
}

.search-history-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: var(--tap-min);
  padding-inline: var(--space-3);
  text-align: start;
  transition: background-color var(--duration-fast) var(--ease-standard);
}

.search-history-item:hover,
.search-history-item:focus-visible {
  background: var(--color-surface-hover);
}

.search-history-item-icon {
  display: flex;
  flex-shrink: 0;
}

.search-history-item-icon svg {
  width: 16px;
  height: 16px;
  color: var(--color-text-tertiary);
}

.search-history-item-label {
  flex: 1;
  min-width: 0;
  font-size: var(--fs-sm);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.search-history-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-1);
  flex-shrink: 0;
  padding: var(--space-1) var(--space-2);
  border-top: 1px solid var(--color-border);
}

.search-history-footer-btn {
  min-height: 2rem;
  padding-inline: var(--space-3);
  border-radius: var(--radius-full);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--color-text-secondary);
  transition: background-color var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard);
}

.search-history-footer-btn:hover,
.search-history-footer-btn:focus-visible {
  background: var(--color-surface-hover);
}

.search-history-close {
  color: var(--color-accent-strong);
}

.search-results {
  margin-top: var(--space-4);
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding-bottom: calc(env(safe-area-inset-bottom) + var(--space-5));
}

.search-result-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.search-result {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--tap-min);
  padding: var(--space-2);
  border-radius: var(--radius-md);
  transition: background-color var(--duration-fast) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard);
}

.search-result:hover,
.search-result:focus-visible {
  background: var(--color-surface-hover);
}

.search-result:active {
  background: var(--color-surface-hover);
  transform: scale(0.985);
}

.search-result img {
  width: clamp(2.5rem, 11vw, 3.25rem);
  aspect-ratio: 2 / 3;
  height: auto;
  object-fit: cover;
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  flex-shrink: 0;
}

.search-result-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.search-result-title {
  font-size: var(--fs-base);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Same convention as `card.js`'s `.card-fa-title` — pinned RTL/right-aligned
   regardless of the app's own direction, since it's always Persian text.
   Unlike that one, this is shown only when the app's own language is
   Persian (see `search.js#renderResults()`), not whenever the field exists. */
.search-result-fa-title {
  font-size: var(--fs-xs);
  color: var(--color-text-tertiary);
  direction: rtl;
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.search-result-sub {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 1em; /* keeps the row's height stable while the rating enriches in */
}

.search-result-year {
  font-size: var(--fs-xs);
  color: var(--color-text-tertiary);
}

/* Same badge `card.js`'s `.card-rating` uses for every other title card. */
.search-result-rating {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: var(--fs-xs);
  color: var(--color-rating);
  font-weight: 700;
}

.search-result-rating svg {
  width: 12px;
  height: 12px;
}

.search-hint {
  margin-top: var(--space-3);
  font-size: var(--fs-sm);
  color: var(--color-text-tertiary);
  text-align: center;
  padding: var(--space-4) 0;
}

/* ---------- Search: Simple | Advanced mode, filter chips ----------
   Generic label-driven segmented control (Simple/Advanced, and — nested
   inside .search-advanced — Movies/Series), same sliding-thumb pattern as
   Explore's card/list switcher, parameterised over --seg-count instead of
   hardcoded to two icon buttons. Sized like `.ex-mode-btn` (2.25rem, below
   `--tap-min`) rather than a full 44px tap target — an already-established
   exception in this codebase for compact secondary controls, not a new one. */
.search-segmented {
  position: relative;
  display: flex;
  padding: 3px;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  background: var(--color-bg-elevated);
}

.search-panel > .search-segmented {
  margin-top: var(--space-2);
}

.search-segmented-thumb {
  position: absolute;
  top: 3px;
  bottom: 3px;
  left: 3px;
  width: calc((100% - 6px) / var(--seg-count));
  border-radius: var(--radius-full);
  background: linear-gradient(180deg, rgba(215, 163, 92, 0.3), rgba(215, 163, 92, 0.14));
  box-shadow: inset 0 0 0 1px rgba(236, 194, 127, 0.3);
  transition: transform var(--duration-base) var(--ease-standard);
}

.search-segmented-btn {
  position: relative;
  flex: 1;
  min-height: 2.25rem;
  padding-block: 6px;
  border-radius: var(--radius-full);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--color-text-tertiary);
  transition: color var(--duration-base) var(--ease-standard);
}

.search-segmented-btn.is-active {
  color: var(--color-accent-strong);
}

.search-advanced {
  margin-top: var(--space-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.search-advanced[hidden] {
  display: none;
}

/* ---------- Top filter row: Movies/Series (left) + Clear Filters (right) ----------
   Sits at the top of the filter area, above the collapsible chip drawer, so the
   type toggle and the reset action are always visible together on one row. */
.search-top-filters-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

/* Slightly more compact than the Simple/Advanced switch above it — this one
   shares a row with another control instead of owning the full width. */
.search-top-type-toggle {
  flex: 0 1 auto;
  max-width: 11rem;
}

.search-top-type-toggle .search-segmented-btn {
  min-height: 1.9rem;
  padding-block: 4px;
  font-size: var(--fs-xs);
}

.search-top-filters-row .search-clear-filters {
  align-self: center;
  margin-top: 0;
  flex-shrink: 0;
}

/* ---------- Collapsible filter drawer ----------
   Collapsed by default (see search.js) — the count badge is what makes that
   safe, telling a returning user filters are still active without opening
   it. Same grid-template-rows 0fr→1fr technique as the downloads panel's
   episode disclosure (`.dl-ep-region`): animates to the content's real
   height with no JS measuring. */
.search-filters-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  align-self: flex-start;
  min-height: 2rem;
  padding: 5px var(--space-2) 5px var(--space-3);
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--color-text-secondary);
  transition: color var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard);
}

.search-filters-toggle:hover,
.search-filters-toggle:focus-visible {
  color: var(--color-text);
  border-color: var(--color-border-strong);
}

.search-filters-toggle.is-open {
  color: var(--color-accent-strong);
  border-color: rgba(236, 194, 127, 0.4);
}

.search-filters-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.125rem;
  height: 1.125rem;
  padding-inline: 4px;
  border-radius: var(--radius-full);
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  font-size: 10px;
  font-weight: 800;
}

.search-filters-count[hidden] {
  display: none;
}

.search-filters-chevron {
  display: flex;
  color: var(--color-text-tertiary);
  transition: transform var(--duration-base) var(--ease-standard),
    color var(--duration-base) var(--ease-standard);
}

.search-filters-chevron svg {
  width: 15px;
  height: 15px;
}

.search-filters-toggle.is-open .search-filters-chevron {
  transform: rotate(90deg);
  color: var(--color-accent-strong);
}

.search-filters-region {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--duration-base) var(--ease-standard);
}

.search-filters-region.is-open {
  grid-template-rows: 1fr;
}

.search-filters-clip {
  overflow: hidden;
  min-height: 0;
}

/* Its own bounded scroll region, independent of .search-results below —
   five filter groups can still get tall even inside the drawer, and without
   this they'd push results down to almost nothing on a short viewport
   instead of just scrolling internally. */
.search-filters {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-height: 30vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: var(--space-2) 2px var(--space-1);
}

.search-filter-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.search-filter-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-tertiary);
}

.search-filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* Multi-select toggle chip — same gold-gradient active treatment as
   Explore's secondary (in-panel) sub-tab pills (`.ex-subtabbar .ex-tab.
   is-active`), reused here for visual consistency even though the
   interaction is different (independently toggleable, not exclusive).
   Deliberately smaller than that pill (1.75rem vs. --tap-min) — there can be
   two dozen of these on screen at once, and each is a secondary refinement
   action, not a primary navigation target. */
.search-chip {
  min-height: 1.75rem;
  padding: 4px var(--space-2);
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  background: var(--color-bg-elevated);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--color-text-secondary);
  transition: background-color var(--duration-base) var(--ease-standard),
    border-color var(--duration-base) var(--ease-standard),
    color var(--duration-base) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard);
}

.search-chip:hover,
.search-chip:focus-visible {
  border-color: var(--color-border-strong);
  color: var(--color-text);
}

.search-chip:active {
  transform: scale(0.96);
}

.search-chip.is-active {
  color: var(--color-accent-strong);
  background: linear-gradient(180deg, rgba(215, 163, 92, 0.28) 0%, rgba(215, 163, 92, 0.12) 100%);
  border-color: rgba(236, 194, 127, 0.55);
  box-shadow: 0 6px 18px rgba(215, 163, 92, 0.14);
}

.search-clear-filters {
  align-self: flex-start;
  min-height: 1.75rem;
  padding-inline: 2px;
  margin-top: 2px;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--color-accent-strong);
}

.search-clear-filters:hover,
.search-clear-filters:focus-visible {
  text-decoration: underline;
}

/* ---------- Hero carousel ----------
   An inset, rounded "card" hero (premium-streaming style) rather than a
   full-bleed banner: small side inset, generous radius, and a compact height so
   the first content row peeks below the fold. The top bar overlays its top
   edge, so each slide carries its own top scrim for chrome legibility. */
.hero-carousel {
  position: relative;
  margin: calc(env(safe-area-inset-top) + var(--hero-inset)) var(--hero-inset) 0;
  border-radius: var(--hero-radius);
  overflow: hidden;
  background: var(--color-bg-elevated);
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.45);
  /* Fluid height: compact on phones, capped so it never dominates a desktop. */
  height: clamp(17rem, 46vw + 6rem, 30rem);
}

.hero-track {
  display: flex;
  height: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}

.hero-track::-webkit-scrollbar {
  display: none;
}

.hero-slide {
  position: relative;
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
}

.hero-slide-media {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.hero-slide-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Backdrops put the subject slightly above centre; bias the crop upward so
     faces survive the tighter aspect ratios on phones. */
  object-position: center 32%;
  opacity: 0;
  filter: brightness(1.12) saturate(1.06);
  transition: opacity var(--duration-slow) var(--ease-standard);
}

.hero-slide-media img.is-loaded {
  opacity: 1;
}

/* Fallback when a title has no landscape backdrop: a 2:3 poster in a wide box
   would crop to a sliver, so bias to the upper portion and let it blur out. */
.hero-slide-media img.is-poster {
  object-position: center 22%;
}

/* Vertical scrim: dark under the top bar and under the copy, clear in between. */
.hero-slide-media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(6, 7, 10, 0.62) 0%,
    rgba(6, 7, 10, 0.08) 26%,
    rgba(6, 7, 10, 0.18) 46%,
    rgba(6, 7, 10, 0.86) 88%,
    rgba(6, 7, 10, 0.96) 100%
  );
}

/* Side scrim anchors the left-aligned copy on wider slides. */
.hero-slide-media::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(90deg, rgba(6, 7, 10, 0.55) 0%, rgba(6, 7, 10, 0.12) 52%, rgba(6, 7, 10, 0) 82%);
}

.hero-slide-content {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: 34rem;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4) clamp(var(--space-4), 4vw, var(--space-6))
    calc(var(--space-5) + var(--hero-dots-space));
}

.hero-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-xs);
  font-weight: 700;
  text-transform: none;
  letter-spacing: 0;
  color: var(--color-accent-strong);
}

.hero-badge-type {
  padding: 4px var(--space-3);
  border-radius: var(--radius-sm);
  font-size: 11px;
  font-weight: 700;
  background: rgba(215, 163, 92, 0.16);
  border: 1px solid rgba(236, 194, 127, 0.3);
}

.hero-title {
  font-size: var(--fs-2xl);
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: 1.08;
  text-shadow: 0 2px 18px rgba(0, 0, 0, 0.55);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* `dir="rtl"` keeps Persian glyph ordering correct, but the block itself must
   stay left-aligned with the rest of the copy — otherwise it drifts to the right
   edge of the slide, over the bright, un-scrimmed part of the backdrop. */
.hero-fa-title {
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--color-text-secondary);
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.6);
}

.hero-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  font-size: var(--fs-sm);
  color: var(--color-text-secondary);
}

.hero-rating {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--color-rating);
  font-weight: 700;
}

.hero-rating svg {
  width: 15px;
  height: 15px;
}

.hero-genre-text {
  text-transform: capitalize;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

.hero-actions svg {
  width: 18px;
  height: 18px;
}

/* The play glyph sits inside its own thin ring — `currentColor` keeps this
   correct in every theme's own accent automatically. */
.hero-actions .btn-primary svg {
  width: 22px;
  height: 22px;
  padding: 4px;
  box-sizing: border-box;
  border: 1.5px solid currentColor;
  border-radius: var(--radius-full);
}

.hero-actions .btn {
  padding-inline: clamp(var(--space-4), 4vw, var(--space-5));
}

/* ---------- Hero dots ---------- */
.hero-dots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: var(--space-3);
  z-index: 3;
  display: flex;
  justify-content: center;
  align-items: center;
  /* Wide enough that neighbouring dots' enlarged hit areas (::after below)
     don't overlap and steal each other's taps. */
  gap: var(--space-3);
  pointer-events: none;
}

.hero-dot {
  pointer-events: auto;
  position: relative;
  width: var(--space-2);
  height: var(--space-2);
  border-radius: var(--radius-full);
  background: rgba(255, 255, 255, 0.32);
  transition: width var(--duration-base) var(--ease-standard),
    background-color var(--duration-base) var(--ease-standard);
}

/* Enlarges the hit area without visual bulk: 24px wide (the WCAG 2.5.8 minimum)
   and a full 44px tall, since vertical space next to the dots is unused. Wider
   than 24px would make neighbouring dots' targets overlap. */
.hero-dot::after {
  content: '';
  position: absolute;
  inset: -18px -6px;
}

.hero-dot.is-active {
  width: var(--space-5);
  background: var(--color-accent-strong);
}

/* ---------- Hero states ---------- */
.hero-carousel.is-loading .hero-track,
.hero-skeleton {
  width: 100%;
  height: 100%;
}

.hero-state {
  flex: 0 0 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-5);
  text-align: center;
  color: var(--color-text-secondary);
  font-size: var(--fs-sm);
}

.hero-state-icon svg {
  width: 30px;
  height: 30px;
  color: var(--color-text-tertiary);
}

/* ---------- Carousel rows ----------
   `.row-dots` is the rail's page-position indicator (see carousel.js's
   `attachDots`). Emitted for every theme when a caller passes `dots: true`;
   every theme now renders it under the cards. */
.row-dots {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding-top: var(--space-3);
}

.row-dot {
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
  background: rgba(255, 255, 255, 0.32);
  transition: width var(--duration-base) var(--ease-standard),
    background-color var(--duration-base) var(--ease-standard);
}

.row-dot.is-active {
  width: 1.35rem;
  background: var(--color-accent-strong);
}

.row-wrap {
  position: relative;
}

.row {
  display: flex;
  gap: var(--space-3);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scroll-padding-inline: var(--container-padding);
  padding-inline: var(--container-padding);
  padding-block: var(--space-1) var(--space-2);
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}

.row::-webkit-scrollbar {
  display: none;
}

.row-arrow {
  position: absolute;
  top: 0;
  bottom: var(--space-2);
  width: 48px;
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 2;
  background: linear-gradient(90deg, var(--color-bg) 20%, transparent);
  color: var(--color-text);
  transition: opacity var(--duration-fast) var(--ease-standard);
}

.row-arrow.next {
  inset-inline-end: 0;
  background: linear-gradient(270deg, var(--color-bg) 20%, transparent);
}

.row-arrow.prev {
  inset-inline-start: 0;
}

@media (hover: hover) and (pointer: fine) and (min-width: 860px) {
  .row-wrap:hover .row-arrow,
  .row-arrow:focus-visible {
    display: flex;
  }
}

/* ---------- Cards ---------- */

/* `createTitleCard()` returns this wrapper rather than the `.card` button
   itself: the watchlist heart overlaying the poster has to be a *sibling* of
   the button, since interactive elements can't nest (see card.js, and
   Explore's own `.ex-tile-wrap` precedent). The wrapper therefore owns the
   rail mechanics — width, `flex: 0 0 auto`, scroll-snap — and `.card` just
   fills it. Cards built without a heart (calendar, continue-watching,
   collection, skeletons) stay bare `.card`s and keep those same mechanics
   from `.card` below, so nothing else in the app had to change. */
.card-tile {
  position: relative;
  flex: 0 0 auto;
  width: var(--card-width);
  scroll-snap-align: start;
  display: flex;
}

.card-tile > .card {
  width: 100%;
}

/* Emitted for every theme (see card.js); every theme now shows it, each in
   its own accent color via the shared tokens below (aligned with Theme 3/
   "Ember"'s icon-usage approach). */
.card-fav {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  z-index: 2;
  top: var(--space-2);
  inset-inline-start: var(--space-2);
  width: 1.9rem;
  height: 1.9rem;
  border-radius: var(--radius-full);
  background: rgba(0, 0, 0, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.2);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #fff;
  transition: color var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard);
}

.card-fav svg {
  width: 15px;
  height: 15px;
}

/* Enlarges the hit area to ~44px without visual bulk. */
.card-fav::after {
  content: '';
  position: absolute;
  inset: -8px;
  border-radius: inherit;
}

.card-fav:hover,
.card-fav:focus-visible {
  border-color: var(--color-border-strong);
}

.card-fav:active {
  transform: scale(0.88);
}

.card-fav.is-saved {
  color: var(--color-accent-2, var(--color-danger));
  border-color: var(--color-border-strong);
}

/* The on-poster rating chip, as opposed to `.card-rating` in the body meta
   line. Same story: emitted always; every theme now shows it here instead
   (see the `.card-body .card-meta .card-rating` rule below, which hides the
   meta-line copy so the rating isn't stated twice). */
.card-poster-rating {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  position: absolute;
  z-index: 2;
  top: var(--space-2);
  inset-inline-end: var(--space-2);
  padding: 3px var(--space-2);
  border-radius: var(--radius-full);
  background: rgba(0, 0, 0, 0.72);
  border: 1px solid var(--color-border);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  font-size: 11px;
  font-weight: 800;
  color: var(--color-rating);
  /* A bare number next to a star glyph is weak-direction text — without this
     it reorders under RTL. Same fix as gotcha #42's quota/duration strings. */
  direction: ltr;
  unicode-bidi: embed;
}

.card-poster-rating-star {
  display: inline-flex;
  width: 11px;
  height: 11px;
  color: var(--color-rating);
}

.card-poster-rating-star svg {
  width: 100%;
  height: 100%;
}

.card {
  flex: 0 0 auto;
  width: var(--card-width);
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  border-radius: var(--radius-md);
  transition: transform var(--duration-base) var(--ease-standard);
}

.card:hover,
.card:focus-visible {
  transform: translateY(-6px);
}

.card:active {
  transform: scale(0.97);
}

.card-poster {
  position: relative;
  aspect-ratio: 2 / 3;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-card);
  transition: box-shadow var(--duration-base) var(--ease-standard),
    border-color var(--duration-base) var(--ease-standard);
}

.card:hover .card-poster,
.card:focus-visible .card-poster {
  box-shadow: var(--shadow-card-hover);
  border-color: var(--color-border-strong);
}

.card-poster img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity var(--duration-base) var(--ease-standard),
    transform var(--duration-slow) var(--ease-standard);
}

.card-poster img.is-loaded {
  opacity: 1;
}

.card:hover .card-poster img,
.card:focus-visible .card-poster img {
  transform: scale(1.06);
}

/* The rail/tab context a card sits in already establishes its type, so the
   poster carries the rating badge (`.card-poster-rating` above) in this
   corner instead — matching Theme 3/"Ember"'s icon-usage approach. */
.card-type-badge {
  display: none;
}

.card-schedule-badge {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: var(--space-2);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--color-text);
  background: linear-gradient(0deg, rgba(0, 0, 0, 0.85), transparent);
}

/* Cards stretch to the tallest in the row; letting the body grow and pinning the
   meta line to its bottom keeps ratings/years aligned across a row even when
   only some titles carry a Persian subtitle. */
.card-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-inline: 2px;
}

.card-title {
  font-size: var(--fs-base);
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.card-fa-title {
  font-size: var(--fs-xs);
  color: var(--color-text-tertiary);
  direction: rtl;
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.card-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: auto;
  padding-top: 2px;
  font-size: var(--fs-xs);
  color: var(--color-text-secondary);
}

/* Rating already sits on the poster (`.card-poster-rating` above), so the
   meta line carries year (and, for the calendar rail, the network) alone. */
.card-body .card-meta .card-rating {
  display: none;
}

.card-rating {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  color: var(--color-rating);
  font-weight: 700;
}

.card-rating svg {
  width: 12px;
  height: 12px;
}

.progress-track {
  position: absolute;
  left: var(--space-2);
  right: var(--space-2);
  bottom: var(--space-2);
  height: 4px;
  border-radius: var(--radius-full);
  background: rgba(255, 255, 255, 0.28);
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--color-accent-strong);
  box-shadow: 0 0 6px rgba(236, 194, 127, 0.6);
}

/* ---------- Continue Watching card (horizontal) ----------
   Home's Continue Watching rail reuses the title page's horizontal "Last
   Watched" card layout (poster + name/meta/progress body — see detail.css's
   .lw-* comment) instead of the vertical .card shape every other Home rail
   uses, per explicit product request. `.card-cw` keeps .card's rail
   mechanics (flex: 0 0 auto, scroll-snap-align — both inherited, only
   flex-direction is overridden here) rather than reusing .lw-* directly,
   since .lw-card is sized for one full-width block on the title page, not a
   multi-item rail. */
.card-cw {
  flex-direction: row;
  align-items: stretch;
  width: var(--card-width-wide);
  gap: var(--space-3);
  padding: var(--space-2);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  text-align: start;
}

.card-cw:hover,
.card-cw:focus-visible {
  background: var(--color-surface-hover);
  border-color: var(--color-border-strong);
}

.card-cw-poster {
  position: relative;
  flex-shrink: 0;
  width: 4.5rem;
  aspect-ratio: 2 / 3;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--color-bg-elevated);
}

.card-cw-poster img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity var(--duration-base) var(--ease-standard);
}

.card-cw-poster img.is-loaded {
  opacity: 1;
}

.card-cw-play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(4, 5, 7, 0.32);
  color: #fff;
}

.card-cw-play svg {
  width: 18px;
  height: 18px;
}

.card-cw-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
}

.card-cw-title {
  font-size: var(--fs-sm);
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.card-cw-meta {
  display: flex;
  gap: var(--space-2);
  font-size: var(--fs-xs);
  color: var(--color-text-secondary);
  font-variant-numeric: tabular-nums;
}

/* Same bidi fix as detail.css's .lw-time (front/CLAUDE.md gotcha #33/#42's
   family): "mm:ss / mm:ss" has no strong-direction character for the bidi
   algorithm to anchor on inside RTL flow, so pin it LTR explicitly. */
.card-cw-time {
  direction: ltr;
  unicode-bidi: embed;
}

.card-cw-progress-track {
  margin-top: var(--space-2);
  height: 4px;
  border-radius: var(--radius-full);
  background: var(--color-bg-elevated);
  overflow: hidden;
}

.card-cw-progress-fill {
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--color-accent-strong);
}

/* Collection card */
/* Collections show their portrait poster, so the card matches the standard
   title-card footprint (2:3 via .card-poster) rather than the old landscape
   backdrop box — a poster cropped to 16:9 lost most of the artwork. */
.card-collection {
  width: var(--card-width);
}

.card-collection-title {
  position: absolute;
  inset: auto 0 0 0;
  padding: var(--space-3);
  font-size: var(--fs-base);
  font-weight: 700;
  background: linear-gradient(0deg, rgba(0, 0, 0, 0.85), transparent);
}

/* ---------- Skeletons ---------- */
.skeleton {
  position: relative;
  background: var(--color-surface);
  overflow: hidden;
}

.skeleton::after {
  content: '';
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.06), transparent);
  animation: shimmer 1.6s infinite;
}

@keyframes shimmer {
  100% {
    transform: translateX(100%);
  }
}

.skeleton-card .card-poster {
  background: var(--color-surface);
}

.skeleton-line {
  height: 10px;
  border-radius: 4px;
  background: var(--color-surface);
}

.skeleton-line.w-70 {
  width: 70%;
}

.skeleton-line.w-40 {
  width: 40%;
}

/* Generic block skeleton — a full-width placeholder rect for card/row-shaped
 * content (Subscription's cards, Devices' rows) that doesn't need the finer
 * poster/line breakdown above. Shared here per the "shared utility class
 * lives in components.css" convention. */
.skeleton-block {
  height: 6rem;
  border-radius: var(--radius-lg);
  margin-bottom: var(--space-3);
}

/* ---------- Row states (error / empty) ---------- */
.row-state {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-inline: var(--container-padding);
  padding: var(--space-4) var(--space-5);
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-md);
  background: rgba(255, 255, 255, 0.02);
  color: var(--color-text-secondary);
  font-size: var(--fs-sm);
}

.row-state svg {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  color: var(--color-text-tertiary);
}

.row-state-retry {
  margin-inline-start: auto;
  color: var(--color-accent-strong);
  font-weight: 600;
  white-space: nowrap;
}


/* ---------- Bottom navigation ----------
   A wide floating glass dock. It spans most of the screen width on phones (with
   a sensible cap on larger screens) so the tap targets are generous and the bar
   reads as primary app chrome rather than a small floating widget. */
.bottom-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: var(--z-nav);
  display: flex;
  justify-content: center;
  padding: 0 var(--space-4) calc(var(--space-3) + env(safe-area-inset-bottom));
  pointer-events: none;
}

/* Fades the content scrolling underneath so the dock always has contrast. */
.bottom-nav::before {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: calc(100% + var(--space-6));
  background: linear-gradient(180deg, rgba(6, 7, 10, 0) 0%, rgba(6, 7, 10, 0.75) 55%, var(--color-bg) 100%);
  pointer-events: none;
}

.bottom-nav-inner {
  position: relative;
  pointer-events: auto;
  width: 100%;
  max-width: 27rem;
  display: flex;
  align-items: center;
  justify-content: space-around;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  background: linear-gradient(180deg, rgba(30, 34, 44, 0.82) 0%, rgba(14, 17, 23, 0.88) 100%);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--radius-full);
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  box-shadow:
    0 18px 48px rgba(0, 0, 0, 0.62),
    0 2px 8px rgba(0, 0, 0, 0.4),
    inset 0 1px 0 rgba(255, 255, 255, 0.09);
}

.bottom-nav-item {
  position: relative;
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-width: var(--tap-min);
  height: 3.4rem;
  border-radius: var(--radius-full);
  color: var(--color-text-tertiary);
  transition: color var(--duration-base) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard);
}

/* Accent pill behind the active tab — scales in rather than popping. */
.bottom-nav-item::before {
  content: '';
  position: absolute;
  inset: 4px 0;
  border-radius: var(--radius-full);
  background: linear-gradient(180deg, rgba(215, 163, 92, 0.26) 0%, rgba(215, 163, 92, 0.12) 100%);
  box-shadow: inset 0 0 0 1px rgba(236, 194, 127, 0.28);
  opacity: 0;
  transform: scale(0.7);
  transition: opacity var(--duration-base) var(--ease-standard),
    transform var(--duration-base) var(--ease-standard);
}

.bottom-nav-item.is-active::before {
  opacity: 1;
  transform: scale(1);
}

.bottom-nav-item .bn-icon {
  position: relative;
  width: 22px;
  height: 22px;
  transition: transform var(--duration-base) var(--ease-standard);
}

.bottom-nav-item .bn-icon svg {
  width: 100%;
  height: 100%;
}

/* Present in the DOM for every theme (see bottomNav.js); every theme now
   keeps a label under every icon (aligned with Theme 3/"Ember"'s approach,
   which Theme 2/"Nova" already adopted independently). */
.bottom-nav-item .bn-label {
  display: block;
  position: relative;
  font-size: 10px;
  font-weight: 700;
  line-height: 1.2;
}

.bottom-nav-item:hover,
.bottom-nav-item:focus-visible {
  color: var(--color-text-secondary);
}

.bottom-nav-item:active {
  transform: scale(0.92);
}

.bottom-nav-item.is-active {
  color: var(--color-accent-strong);
}

.bottom-nav-item.is-active .bn-icon {
  transform: translateY(-1px) scale(1.04);
  filter: drop-shadow(0 2px 10px rgba(215, 163, 92, 0.6));
}

/* ---------- Toast ---------- */
.mm-toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--bottom-nav-clearance) - var(--space-4));
  transform: translate(-50%, 8px);
  z-index: var(--z-toast);
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border-strong);
  color: var(--color-text);
  font-size: var(--fs-sm);
  font-weight: 600;
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-full);
  box-shadow: var(--shadow-elevated);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-base) var(--ease-standard),
    transform var(--duration-base) var(--ease-standard);
}

.mm-toast.is-visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* ---------- Home: tabbed filter rails ----------
   Pill tabs over a content rail (Trending, Movies/Series by Country, Movies/
   Series by Genre) — same tablist interaction as the Airing Calendar's day
   tabs, sized for text labels instead of day chips. */
.home-tabs {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scroll-padding-inline: var(--container-padding);
  padding-inline: var(--container-padding);
  padding-block: var(--space-1) var(--space-2);
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}

.home-tabs::-webkit-scrollbar {
  display: none;
}

.home-tab {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  scroll-snap-align: start;
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-4);
  gap: 6px;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  background: var(--color-bg-elevated);
  font-size: var(--fs-sm);
  font-weight: 700;
  white-space: nowrap;
  color: var(--color-text-secondary);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  transition: background-color var(--duration-base) var(--ease-standard),
    border-color var(--duration-base) var(--ease-standard),
    color var(--duration-base) var(--ease-standard),
    box-shadow var(--duration-base) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard);
}

/* Emitted for every theme (see home.js); every theme now shows it, each in
   its own accent color (aligned with Theme 3/"Ember"'s icon-usage approach). */
.home-tab-icon {
  display: inline-flex;
  width: 15px;
  height: 15px;
  color: var(--color-accent-strong);
}

.home-tab-icon svg {
  width: 100%;
  height: 100%;
}

.home-tab:hover,
.home-tab:focus-visible {
  border-color: var(--color-border-strong);
  color: var(--color-text);
}

.home-tab:active {
  transform: scale(0.96);
}

.home-tab.is-active {
  background: linear-gradient(180deg, rgba(215, 163, 92, 0.28) 0%, rgba(215, 163, 92, 0.12) 100%);
  border-color: rgba(236, 194, 127, 0.55);
  color: var(--color-accent-strong);
  box-shadow: 0 6px 18px rgba(215, 163, 92, 0.14);
}

/* ---------- Confirm dialog (confirmDialog.js) — a real popup for a single yes/no
   confirmation before a destructive action, distinct from the inline row-swap confirm
   pattern (devices.js, downloadsManager.js's Remove button) some destructive actions
   still use. Centered at every viewport size — this is a short yes/no alert, not a
   multi-option sheet like shareSheet.js/downloadActionSheet.js, so it doesn't need
   their bottom-sheet-on-mobile treatment. ---------- */
.cf-layer {
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-overlay) + 3);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  pointer-events: none;
}

.cf-scrim {
  position: absolute;
  inset: 0;
  background: rgba(4, 5, 7, 0.62);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  opacity: 0;
  transition: opacity var(--duration-base) var(--ease-standard);
}

.cf-panel {
  position: relative;
  width: 100%;
  max-width: 24rem;
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-lg);
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5);
  padding: var(--space-5);
  opacity: 0;
  transform: translate3d(0, 12px, 0) scale(0.98);
  transition: opacity var(--duration-base) var(--ease-standard),
    transform var(--duration-base) var(--ease-standard);
}

.cf-layer.is-open {
  pointer-events: auto;
}

.cf-layer.is-open .cf-scrim {
  opacity: 1;
}

.cf-layer.is-open .cf-panel {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
}

.cf-title {
  font-size: var(--fs-md);
  font-weight: 800;
  letter-spacing: -0.01em;
  margin-bottom: var(--space-2);
}

.cf-title.is-hidden {
  display: none;
}

.cf-message {
  font-size: var(--fs-base);
  color: var(--color-text-secondary);
  line-height: 1.5;
}

.cf-actions {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-5);
}

.cf-btn {
  flex: 1;
  min-height: var(--tap-min);
}
