/*
  Self-hosted (#137): loading from fonts.googleapis.com sent every visitor's IP address to Google
  before any consent could be given, which is exactly the kind of third-party transfer German
  courts have penalized. One variable-weight file covers the whole 400-800 range Manrope ships,
  and the Latin subset is all the UI ever renders (it covers German umlauts and ß too -- both sit
  below U+00FF), so there is nothing to fetch remotely at all.
*/
@font-face {
  font-family: "Manrope";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/fonts/manrope/manrope-latin-variable.woff2") format("woff2");
}

:root {
  color-scheme: light;
  /*
    The FilmSpotter palette (#64). Only the six brand colours are given -- Ink #10161C,
    Charcoal #38414B, Stone #67717C, Warm White #FBFAF7, Coral #E75B45, White #FFFFFF --
    everything below is mixed from them rather than invented.

    Two tokens carry the accent because one cannot do both jobs. Coral on Warm White reaches
    3.36:1, which is enough for a focus ring or a large heading but short of the 4.5:1 that
    body-size text needs. --accent is therefore a darkened Coral of the same hue (8deg) and
    saturation (77%) at 4.52:1, while --brand-coral stays the untouched brand colour for
    rings, marks and borders, where 3:1 applies.
  */
  --bg-top: #ffffff;
  --bg: #fbfaf7;
  --surface: #ffffff;
  --surface-hover: rgba(16, 22, 28, 0.05);
  --text: #10161c;
  --ink: #10161c;
  --muted: #67717c;
  --border: #dadad8;
  --field-border: #67717c;
  --accent: #c7311a;
  --brand-coral: #e75b45;
  --accent-soft: #f9e7e2;
  --button-bg: #10161c;
  --button-text: #fbfaf7;
  --button-shadow: 0 12px 24px rgba(16, 22, 28, 0.22);
  --button-shadow-hover: 0 14px 30px rgba(16, 22, 28, 0.28);
  --danger: #c03b2b;
  --header-bg: rgba(251, 250, 247, 0.9);
  --shadow: 0 20px 50px rgba(16, 22, 28, 0.12);
  --shadow-soft: 0 10px 24px rgba(16, 22, 28, 0.08);
  --aura-1: rgba(231, 91, 69, 0.1);
  --aura-2: rgba(56, 65, 75, 0.08);
  --aura-3: rgba(103, 113, 124, 0.12);
  --input-bg: #ffffff;
  --availability-item-bg: rgba(255, 255, 255, 0.6);
  --availability-item-border: rgba(16, 22, 28, 0.1);
  --mobile-row-bg: #ffffff;
  --mobile-row-border: rgba(16, 22, 28, 0.12);
  --font-body: Manrope, "Segoe UI", system-ui, sans-serif;
  --font-display: Manrope, "Segoe UI", system-ui, sans-serif;
  /*
    TMDB's brand guidelines permit only white, black or their own brand colours for the logo,
    unmodified otherwise (#189) -- so this is not a themed brand token like --accent, just a
    switch between the two permitted neutrals.
  */
  --tmdb-mark: #000000;
}

:root[data-theme="day"] {
  color-scheme: light;
}

:root[data-theme="night"] {
  color-scheme: dark;
  /*
    Ink becomes the ground and Warm White the ink. Coral needs no correction here: on Ink it
    reaches 5.18:1, so the brand colour itself carries the text-level accent. Stone would only
    reach 3.67:1 and is lightened to 55% of its own hue instead.
  */
  --bg-top: #191f25;
  --bg: #10161c;
  --surface: #1c2127;
  --surface-hover: rgba(251, 250, 247, 0.08);
  --text: #fbfaf7;
  --ink: #fbfaf7;
  --muted: #9aa4ae;
  --border: #363a3f;
  --field-border: #67717c;
  --accent: #e75b45;
  --brand-coral: #e75b45;
  --accent-soft: #2a1e21;
  --button-bg: #fbfaf7;
  --button-text: #10161c;
  --button-shadow: 0 12px 24px rgba(0, 0, 0, 0.45);
  --button-shadow-hover: 0 14px 30px rgba(0, 0, 0, 0.55);
  --danger: #d44029;
  --header-bg: rgba(16, 22, 28, 0.82);
  --shadow: 0 24px 50px rgba(0, 0, 0, 0.42);
  --shadow-soft: 0 10px 24px rgba(0, 0, 0, 0.35);
  --aura-1: rgba(231, 91, 69, 0.14);
  --aura-2: rgba(56, 65, 75, 0.2);
  --aura-3: rgba(103, 113, 124, 0.16);
  --input-bg: #151b22;
  --availability-item-bg: rgba(28, 33, 39, 0.8);
  --availability-item-border: rgba(251, 250, 247, 0.12);
  --mobile-row-bg: #1c2127;
  --mobile-row-border: rgba(251, 250, 247, 0.16);
  --tmdb-mark: #ffffff;
}

:root[data-theme="os"] {
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root[data-theme="os"] {
    color-scheme: dark;
    /*
      Ink becomes the ground and Warm White the ink. Coral needs no correction here: on Ink it
      reaches 5.18:1, so the brand colour itself carries the text-level accent. Stone would only
      reach 3.67:1 and is lightened to 55% of its own hue instead.
    */
    --bg-top: #191f25;
    --bg: #10161c;
    --surface: #1c2127;
    --surface-hover: rgba(251, 250, 247, 0.08);
    --text: #fbfaf7;
    --ink: #fbfaf7;
    --muted: #9aa4ae;
    --border: #363a3f;
  --field-border: #67717c;
    --accent: #e75b45;
    --brand-coral: #e75b45;
    --accent-soft: #2a1e21;
    --button-bg: #fbfaf7;
    --button-text: #10161c;
    --button-shadow: 0 12px 24px rgba(0, 0, 0, 0.45);
    --button-shadow-hover: 0 14px 30px rgba(0, 0, 0, 0.55);
    --danger: #d44029;
    --header-bg: rgba(16, 22, 28, 0.82);
    --shadow: 0 24px 50px rgba(0, 0, 0, 0.42);
    --shadow-soft: 0 10px 24px rgba(0, 0, 0, 0.35);
    --aura-1: rgba(231, 91, 69, 0.14);
    --aura-2: rgba(56, 65, 75, 0.2);
    --aura-3: rgba(103, 113, 124, 0.16);
    --input-bg: #151b22;
    --availability-item-bg: rgba(28, 33, 39, 0.8);
    --availability-item-border: rgba(251, 250, 247, 0.12);
    --mobile-row-bg: #1c2127;
    --mobile-row-border: rgba(251, 250, 247, 0.16);
    --tmdb-mark: #ffffff;
  }
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: radial-gradient(circle at top, var(--bg-top) 0%, var(--bg) 60%);
  color: var(--text);
  min-height: 100vh;
  font-family: var(--font-body);
  position: relative;
  overflow-x: hidden;
}

body::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 15% 15%, var(--aura-1), transparent 45%),
    radial-gradient(circle at 85% 20%, var(--aura-2), transparent 55%),
    radial-gradient(circle at 30% 80%, var(--aura-3), transparent 55%);
  pointer-events: none;
  z-index: -1;
}

header {
  background: var(--header-bg);
  border-bottom: 1px solid var(--border);
  padding: 1.1rem 0;
  position: sticky;
  top: 0;
  backdrop-filter: blur(10px);
  z-index: 2;
}

.container {
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 1.5rem;
}

.nav {
  display: flex;
  align-items: center;
  gap: 1rem;
  font-size: 0.95rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  flex-wrap: wrap;
}

.nav a {
  color: var(--text);
  text-decoration: none;
  font-weight: 600;
  padding: 0.3rem 0.6rem;
  border-radius: 999px;
  transition: background 0.2s ease, color 0.2s ease;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
}

.nav a:hover {
  background: var(--accent-soft);
  color: var(--accent);
}

/*
  The brand lockup (#64). Shared by the navigation bar and the two pages that have none, so the
  wordmark reads the same in both places; the markup lives in fragments/brand.

  Note the selectors: .nav a is a class plus a type and therefore outranks a bare .nav-brand, so
  anything that has to beat the navigation link styles is written as .nav a.nav-brand. Getting
  this wrong is silent -- the colour happens to match in both themes, and only the uppercase
  transform would have shown it.
*/
.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--ink);
  text-decoration: none;
  text-transform: none;
  letter-spacing: -0.015em;
  font-family: var(--font-display);
  font-weight: 800;
}

.brand-mark {
  width: 1.6rem;
  height: 1.6rem;
  flex: 0 0 auto;
  display: block;
}

.brand-word {
  white-space: nowrap;
}

/* Half an icon width kept clear on the side facing the links -- rule 1 of the kit. */
.nav a.nav-brand {
  margin-right: auto;
  padding: 0.2rem 0.8rem 0.2rem 0;
  font-size: 1.15rem;
  color: var(--ink);
  text-transform: none;
}

.nav a.nav-brand:hover,
.nav a.nav-brand:focus-visible {
  background: transparent;
  color: var(--ink);
}

.nav a.nav-brand:focus-visible {
  outline: 2px solid var(--brand-coral);
  outline-offset: 3px;
  border-radius: 10px;
}

/*
  On the sign-in and code pages the brand stands in for the navigation bar. It replaces the old
  .auth-eyebrow, which set text-transform: uppercase -- that turned the wordmark into
  "FILMSPOTTER" and contradicted the kit, where it is cased.
*/
.auth-brand {
  margin: 0 0 0.9rem;
  font-size: 1.3rem;
}

.auth-brand .brand-mark {
  width: 1.85rem;
  height: 1.85rem;
}

/*
  The counters moved out of the bar and onto the list page in #65, so the class is named after
  where it lives now.
*/
.subnav-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.7rem;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  background: var(--accent-soft);
  color: var(--accent);
  border: 1px solid rgba(231, 91, 69, 0.25);
  font-variant-numeric: tabular-nums;
}

.nav-toggle {
  display: none;
  align-items: center;
  gap: 0.6rem;
  background: transparent;
  border: 1px solid var(--field-border);
  color: var(--text);
  padding: 0.35rem 0.75rem;
  border-radius: 999px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
}

.nav-toggle-icon {
  display: inline-flex;
  flex-direction: column;
  gap: 0.22rem;
}

.nav-toggle-icon span {
  display: block;
  width: 18px;
  height: 2px;
  background: var(--text);
  border-radius: 999px;
}

.nav-links {
  display: inline-flex;
  align-items: center;
  gap: 1rem;
}

.nav-logout-form {
  margin: 0;
}

.nav-logout-button {
  box-shadow: none;
  padding: 0.45rem 0.95rem;
}

.nav-logout-button:hover {
  box-shadow: none;
}

/*
  Why 900 and not 720.

  The bar used to collapse at 720px while its contents needed room up to about 1040px, and
  .nav-links is an inline-flex that does not wrap -- so between 721 and 1039 the last entries ran
  off the edge and Settings and Logout were simply unreachable (#65, point 7 of the situation).
  Two changes, because one alone would only move the problem: the entries wrap now, and the
  collapse happens well before the row can fill up.
*/
@media (max-width: 899px) {
  .nav {
    flex-direction: column;
    align-items: stretch;
    gap: 0.5rem;
  }

  .nav-toggle {
    display: inline-flex;
    justify-content: space-between;
  }

  .nav-links {
    display: none;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.35rem;
    padding: 0.4rem 0.2rem 0;
    /* Full width below the row, so opening the menu pushes the page down rather than
       squeezing the brand and the identity that have to stay visible (AK16). */
    flex-basis: 100%;
    order: 4;
  }

  .nav.nav-open .nav-links {
    display: flex;
  }

  /* Brand and identity stay; only the areas collapse (AK16). */
  .nav {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
  }

  .nav-toggle {
    order: 2;
    margin-left: auto;
  }

  .nav-account {
    order: 3;
    margin-left: 0.5rem;
  }

  /* Only the initials fit here; the full identity is in the open menu. */
  .account-name,
  .account-chevron {
    display: none;
  }

  .account-menu {
    right: 0;
    left: auto;
  }

  .nav a {
    width: 100%;
    justify-content: space-between;
  }

  .nav a.nav-brand {
    width: auto;
    justify-content: flex-start;
    margin-right: 0;
  }

  .nav-logout-form,
  .nav-logout-button {
    width: 100%;
  }

  .nav-logout-button {
    justify-content: center;
  }
}

main {
  padding: 2.5rem 0 4rem;
}

h1 {
  margin: 0 0 1rem;
  font-size: clamp(2rem, 3vw, 2.8rem);
  letter-spacing: -0.03em;
  font-family: var(--font-display);
  font-weight: 700;
}

p {
  color: var(--muted);
}

/*
  Links, found during the visual review for #64: the stylesheet had no rule for a bare <a> at
  all. Only .nav a, .settings-list a and .footer-links a were coloured, so every link in page
  content -- the attribution in the footer, "Use classic add" on the add page, a title inside a
  mail preview -- fell through to the browser default: blue, and purple once visited. Beside an
  Ink and Coral palette that reads as an oversight rather than a choice, and AK15 gives links to
  the accent.

  A bare type selector on purpose: it is the weakest thing that can carry a colour, so all four
  scoped rules above and below keep winning and nothing else moves.
*/
a {
  color: var(--accent);
}

.page-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.5rem;
  margin-bottom: 1.75rem;
}

.details-hero {
  display: flex;
  align-items: flex-start;
  gap: 1.5rem;
  flex-wrap: wrap;
  margin-bottom: 1.75rem;
}

.details-poster img {
  width: clamp(140px, 28vw, 220px);
  border-radius: 16px;
  box-shadow: var(--shadow);
  object-fit: cover;
}

.poster-preview-trigger {
  border: 0;
  background: transparent;
  padding: 0;
  cursor: zoom-in;
}

.poster-preview-trigger:focus-visible {
  outline: 2px solid var(--brand-coral);
  outline-offset: 4px;
  border-radius: 18px;
}

.poster-preview-overlay {
  position: fixed;
  inset: 0;
  background: rgba(18, 17, 15, 0.86);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  z-index: 1200;
}

.poster-preview-overlay[hidden] {
  display: none;
}

.poster-preview-overlay img {
  width: min(92vw, 640px);
  max-height: 92vh;
  object-fit: contain;
  border-radius: 18px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
}

.poster-preview-close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  border: 1px solid rgba(255, 255, 255, 0.5);
  background: rgba(30, 29, 26, 0.65);
  color: #fff;
}

body.poster-preview-open {
  overflow: hidden;
}

.details-info {
  min-width: 220px;
  flex: 1;
}

.page-lede {
  margin: 0.35rem 0 0;
  color: var(--muted);
  font-size: 1rem;
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 1.75rem;
  box-shadow: var(--shadow);
  animation: float-in 0.55s ease both;
}

.actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1.5rem;
  animation: float-in 0.5s ease both;
  animation-delay: 0.1s;
}

.page-header .actions {
  margin-bottom: 0;
  animation: none;
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 1rem;
}

.table th,
.table td {
  text-align: left;
  padding: 0.85rem 0.6rem;
  border-bottom: 1px solid var(--border);
}

.table th {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
}

.table td:last-child {
  white-space: nowrap;
}

.settings-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.85rem;
}

.auth-shell {
  max-width: 1040px;
}

.auth-hero {
  max-width: 680px;
  margin-bottom: 1.75rem;
}

/*
  Named areas (#139) rather than plain source order: the login page's grid holds three items --
  the short promise, the card, and the longer hobby-project note -- and only the middle one
  should stay put between the wide and narrow layouts. A fourth caller (settings, etc.) with just
  two cards still works: unmentioned areas resolve to source order, which is what two items need.
*/
.auth-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-areas: "story card" "more card";
  gap: 1.25rem;
  align-items: start;
}

.auth-story {
  grid-area: story;
}

.auth-story-more {
  grid-area: more;
}

.auth-story h1 {
  margin: 1rem 0 0.75rem;
  font-family: var(--font-display);
  font-size: clamp(1.8rem, 1.5rem + 1.2vw, 2.4rem);
}

.auth-story p {
  max-width: 46ch;
}

.auth-grid > .card {
  grid-area: card;
}

/*
  The follow-up steps (verify a code, choose a username, wait for approval) are one card, not the
  three-item composition above -- .auth-grid's reserved story column would just sit empty beside
  them. A plain max-width wrapper instead of a grid with unused areas.
*/
.auth-single {
  max-width: 480px;
}

.auth-inline-form {
  margin-top: 16px;
}

/* A button that reads as the secondary-action link beside it, not as a second primary button. */
.link-button {
  background: none;
  border: none;
  padding: 0;
  color: var(--accent);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
}

.link-button:hover,
.link-button:focus-visible {
  text-decoration: none;
}

.auth-card-header {
  margin-bottom: 1.25rem;
}

.auth-card-header h2 {
  margin: 0 0 0.35rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.6rem;
}







.settings-list li {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--border);
}

/* A checkbox that submits itself on change (#136) still needs a <form>; this keeps its children
   laid out exactly like the label/control/hint triples the rest of this list already uses. */
.settings-inline-form {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin: 0;
}

.settings-list li:last-child {
  border-bottom: 0;
}

.settings-list a {
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
}

.settings-list a:hover {
  text-decoration: underline;
}

/* Kept as a plain class alongside .movie-row-title rather than folded into it: several E2E
   tests key off this selector to find "a movie's title link" regardless of the row's layout. */
.movie-title-link {
  color: var(--ink);
  text-decoration: none;
  font-weight: 700;
}

.movie-title-link:hover {
  text-decoration: underline;
}

.subscription-add-form {
  max-width: 460px;
}

.provider-actions {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 0.65rem;
  width: 100%;
}

.providers-table td:last-child {
  white-space: normal;
}

.provider-update-form {
  display: grid;
  grid-template-columns: minmax(180px, 1fr) minmax(130px, 160px) auto;
  gap: 0.5rem;
  align-items: center;
  flex: 1;
  min-width: 0;
}

.provider-update-form .table-input {
  width: 100%;
}

.provider-delete-form {
  display: inline-flex;
}

.provider-update-form .button,
.provider-delete-button {
  min-width: 92px;
  display: inline-flex;
  justify-content: center;
}

.table-input {
  width: min(210px, 100%);
  padding: 0.45rem 0.65rem;
  font-size: 0.9rem;
}

.table-select {
  width: auto;
  min-width: 140px;
}




.muted {
  color: var(--muted);
  font-size: 0.9rem;
}

.search-results {
  margin-top: 0.5rem;
  border-radius: 14px;
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: var(--shadow-soft);
  max-height: 16rem;
  overflow-y: auto;
  display: none;
}

.search-results.is-open {
  display: block;
}

.search-result {
  width: 100%;
  border: none;
  background: transparent;
  text-align: left;
  padding: 0.65rem 0.85rem;
  font-size: 0.95rem;
  color: var(--text);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.search-result + .search-result {
  border-top: 1px solid var(--border);
}

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

.search-thumb {
  width: 44px;
  height: 66px;
  border-radius: 8px;
  object-fit: cover;
  background: rgba(230, 224, 215, 0.6);
  border: 1px solid rgba(230, 224, 215, 0.8);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.6rem;
  color: var(--muted);
  flex-shrink: 0;
}

.search-thumb.placeholder {
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.search-text {
  display: inline-block;
}

.is-disabled {
  pointer-events: none;
  opacity: 0.6;
}

.availability-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.1rem;
  height: 2.1rem;
  padding: 0 0.45rem;
  border-radius: 999px;
  font-weight: 600;
  font-size: 0.9rem;
  letter-spacing: 0.01em;
}

.availability-badge-flatrate {
  background: #dff3e8;
  color: #1f6f43;
}

.availability-badge-free {
  background: #baf1d0;
  color: #0f6b3a;
}

.availability-badge-rent {
  background: #fff3cd;
  color: #8a6d1d;
}

.availability-badge-buy {
  background: #f8d6d6;
  color: #9c2f2f;
}

.error {
  background: #fcebea;
  border: 1px solid rgba(161, 47, 42, 0.25);
  color: #a12f2a;
  padding: 0.75rem 1rem;
  border-radius: 12px;
  margin-bottom: 1rem;
}

.form-row {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin-bottom: 1rem;
}

label {
  font-weight: 600;
}

/*
  Found during the visual review for #64: type="email" was never in this list, while
  type="number" is in it and used nowhere. The sign-up form therefore rendered a browser-default
  field next to a styled one -- barely visible on the light theme, and a light grey box on a dark
  card in the night theme, which reads as a defect. Pre-existing, but the new palette is what
  made it obvious.
*/
input[type="text"],
input[type="email"],
input[type="number"],
select {
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--field-border);
  border-radius: 12px;
  font-size: 1rem;
  background: var(--input-bg);
  color: var(--text);
  box-shadow: inset 0 1px 2px rgba(16, 22, 28, 0.06);
}

button,
.button {
  background: var(--button-bg);
  color: var(--button-text);
  border: none;
  padding: 0.6rem 1.2rem;
  border-radius: 999px;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  box-shadow: var(--button-shadow);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

button:hover,
.button:hover {
  transform: translateY(-1px);
  box-shadow: var(--button-shadow-hover);
}

button.danger,
.button.danger {
  background: var(--danger);
  color: #ffffff;
  border: 1px solid transparent;
  box-shadow: 0 12px 24px rgba(192, 53, 43, 0.25);
  padding: 0.55rem 0.9rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
}

button.danger:hover,
.button.danger:hover {
  box-shadow: 0 14px 30px rgba(192, 53, 43, 0.3);
}

.inline-form {
  display: inline;
}

.subscribed-indicator {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  border: 1px solid rgba(231, 91, 69, 0.25);
  vertical-align: middle;
}

.sort-link {
  color: inherit;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-weight: 600;
}

.sort-link:hover {
  text-decoration: underline;
}

.sort-indicator {
  font-size: 0.85rem;
  color: var(--muted);
  min-width: 0.8rem;
  text-align: center;
}


@keyframes float-in {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (max-width: 640px) {
  header {
    padding: 0.9rem 0;
  }

  .container {
    padding: 0 1rem;
  }

  main {
    padding: 2rem 0 3rem;
  }

  .page-header {
    flex-direction: column;
    align-items: flex-start;
    margin-bottom: 1.25rem;
  }

  .auth-grid {
    grid-template-columns: 1fr;
    grid-template-areas: "story" "card" "more";
  }

  .actions {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
  }

  .card {
    padding: 1.1rem;
  }

  .table,
  .table thead,
  .table tbody,
  .table th,
  .table td,
  .table tr {
    display: block;
  }

  .table thead {
    display: none;
  }

  .table tr {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 16px;
    padding: 0.65rem 0.85rem;
    margin-bottom: 0.75rem;
    box-shadow: var(--shadow-soft);
    display: grid;
    grid-template-columns: 1fr auto;
    column-gap: 0.75rem;
  }

  .table td {
    border-bottom: none;
    padding: 0.2rem 0;
  }

  .table td:nth-child(2) {
    color: var(--text);
    font-size: 0.95rem;
    grid-column: 1 / 2;
    grid-row: 1;
  }

  .table td:nth-child(1) {
    grid-column: 2;
    grid-row: 1;
    justify-self: end;
  }

  .table td:nth-child(3),
  .table td:nth-child(4) {
    display: none;
  }

  .table td:last-child {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding-top: 0.4rem;
    grid-column: 1 / -1;
    grid-row: 2;
  }

  .providers-table .provider-actions {
    flex-direction: column;
    align-items: stretch;
    gap: 0.55rem;
  }

  .providers-table .provider-update-form {
    grid-template-columns: 1fr;
  }

  .providers-table .provider-delete-form {
    align-self: flex-end;
  }

  .cache-table tr {
    grid-template-columns: 1fr;
  }

  .cache-table td,
  .cache-table td:nth-child(3),
  .cache-table td:nth-child(4),
  .cache-table td:last-child {
    display: flex;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.35rem 0;
    grid-column: 1 / -1;
    grid-row: auto;
  }

  .cache-table td:nth-child(1),
  .cache-table td:nth-child(2) {
    grid-column: 1 / -1;
    grid-row: auto;
    justify-self: stretch;
  }

  .cache-table td:nth-child(1) {
    order: 0;
  }

  .cache-table td:nth-child(2) {
    order: 1;
  }

  .cache-table td:nth-child(3) {
    order: 2;
  }

  .cache-table td:nth-child(4) {
    order: 3;
  }

  .cache-table td:nth-child(5) {
    order: 4;
  }

  .cache-table td:nth-child(6) {
    order: 5;
  }

  .cache-table td::before {
    content: attr(data-label);
    font-size: 0.7rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted);
  }

  .availability-badge {
    min-width: 1.4rem;
    height: 1.4rem;
    font-size: 0.7rem;
  }

  button,
  .button {
    padding: 0.45rem 0.9rem;
    font-size: 0.85rem;
  }

  button.danger,
  .button.danger {
    padding: 0.4rem 0.7rem;
  }

  .select-checkbox {
    transform: scale(0.9);
  }
}

/* --- Page frame ----------------------------------------------------------
   Everything below belongs to the shared frame rather than to a single page:
   the footer that carries the attribution obligations, the two advertising
   slots, and the tile grid of the home page. */

body {
  display: flex;
  flex-direction: column;
}

main {
  flex: 1;
}

.site-footer {
  border-top: 1px solid var(--border);
  padding: 2.5rem 0 2rem;
  font-size: 0.85rem;
}

/*
  A footer-specific cap instead of the global .container width (#151): this element carries both
  classes, and since this rule comes after .container's in the cascade, its max-width wins at
  equal specificity -- margin and padding still come from .container, unchanged, so the gutter
  matches it exactly rather than introducing a second gutter value.

  min(1440px, 100vw - 3rem) rather than a bare 1440px specifically so the footer never claims more
  than the viewport actually has once its gutter is honoured -- an unguarded 100vw would ignore a
  vertical scrollbar's width and could force horizontal scrolling; subtracting the full gutter
  leaves comfortably more slack than any scrollbar takes.
*/
.footer-inner {
  display: flex;
  flex-direction: column;
  gap: 1.75rem;
  max-width: min(1440px, 100vw - 3rem);
}

/*
  Brand column plus up to three nav columns (#99). A flex row rather than a fixed grid: Account
  is absent for a guest and Legal is absent without any configured URL, and a flex row simply
  gives the space back instead of leaving an empty grid cell.
*/
.footer-top {
  display: flex;
  flex-wrap: wrap;
  gap: 2rem 2.5rem;
}

.footer-brand {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  flex: 1 1 260px;
  max-width: 40ch;
}

.footer-tagline,
.footer-about,
.footer-funding {
  margin: 0;
  color: var(--text);
}

/*
  The nav groups as one flexible unit (#151), so the wider .footer-inner gives them the row's
  remaining width instead of leaving it as dead space after a handful of narrow columns.
  justify-content: space-between spreads two or three groups out across that width; with only one
  group present -- a guest, or legal unconfigured -- the spec defines space-between with a single
  item as identical to flex-start, so that lone group stays put next to the brand column rather
  than drifting to the far edge on its own.
*/
.footer-groups {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 2rem 2.5rem;
  flex: 1 1 400px;
}

.footer-group {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  flex: 0 1 140px;
}

.footer-group-heading {
  margin: 0;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}

/* Links wrap into compact rows rather than a single rigid column, so a narrow group on a wide
   screen and a full-width one on a small screen both stay compact instead of needlessly tall. */
.footer-group-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
}

.footer-group-links a {
  padding: 0.2rem 0;
}

/*
  Colour alone would repeat the exact gap the redesign closes (AK: links must not depend on muted
  text alone). Every footer link keeps the site's ordinary link colour and its underline; only
  the visited state is tuned so it does not fall back to the browser's default purple.
*/
.site-footer a:visited {
  color: var(--muted);
}

.site-footer a:focus-visible {
  outline: 2px solid var(--brand-coral);
  outline-offset: 3px;
  border-radius: 4px;
}

/* The wordmark keeps its own colour whatever its link state -- the same override the nav bar's
   brand link already needed (AK: nav a.nav-brand), because a "visited" home link should not
   suddenly read as muted while the rest of the identity around it does not. */
.footer-brand .brand,
.footer-brand .brand:visited,
.footer-brand .brand:hover,
.footer-brand .brand:focus-visible {
  color: var(--ink);
}

.footer-brand .brand:focus-visible {
  outline: 2px solid var(--brand-coral);
  outline-offset: 3px;
  border-radius: 10px;
}

@media (max-width: 640px) {
  .footer-inner {
    /* Matches .container's own narrower gutter at this breakpoint (padding: 0 1rem). */
    max-width: min(1440px, 100vw - 2rem);
  }

  .footer-top {
    flex-direction: column;
    gap: 1.75rem;
  }

  .footer-brand,
  .footer-groups,
  .footer-group {
    max-width: none;
    flex: 1 1 auto;
  }

  .footer-groups {
    flex-direction: column;
    justify-content: flex-start;
    gap: 1.75rem;
  }
}

/* A quieter row, set apart from the brand/navigation area above it (AK: separate the
   attribution/metadata row with spacing or a subtle divider). */
.footer-attribution {
  padding-top: 1.5rem;
  border-top: 1px solid var(--border);
}

.footer-attribution p {
  margin: 0;
  max-width: 62ch;
  font-size: 0.8rem;
  color: var(--muted);
}

/* Provider/API names read as one unit (matches .brand-word); wrapping mid-name at
   narrow widths looked like a rendering glitch rather than a line break. */
.footer-attribution a {
  white-space: nowrap;
}

/*
  The TMDB mark, sized to sit inside the attribution line rather than beside it (#189): TMDB
  requires any use of their logo to stay less prominent than the mark that identifies the
  application, i.e. smaller than .brand-mark up in .footer-brand. Height is tied to the
  surrounding text (em) so it scales with it instead of needing its own breakpoints.
*/
.tmdb-mark {
  height: 0.85em;
  width: auto;
  vertical-align: -0.1em;
  fill: var(--tmdb-mark);
}


/* Unconfigured slots are not rendered at all, so this reserves no space until
   advertising is actually switched on. */
.ad-slot {
  display: block;
  padding: 1rem 0;
}

.ad-slot .container {
  min-height: 90px;
}

/* Three across, then two, then one. Fewer than six tiles simply leave the last
   row short instead of stretching. */
/*
  No align-items (#73). Grid stretches cells to the row height by default, and that default is
  what makes every card in a row the same height -- without a single height being written down
  anywhere. `start` was what prevented it.
*/
.tile-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
}

@media (max-width: 900px) {
  .tile-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 640px) {
  .tile-grid {
    grid-template-columns: 1fr;
  }
}

/*
  The card as a column, so the ending can be pushed to the bottom edge.
*/
.featured-card {
  display: flex;
  flex-direction: column;
}

/*
  The poster area has the same geometry in every card, whatever the image brings (#73). With
  height: auto each image decided its own height and everything below it shifted -- that was the
  actual source of the unrest, not the grid.

  The ratio sits on the link and not on the <img>, so it also holds when there is no image and
  only the placeholder is there. 2:3 is the usual film poster format; anything else is cropped
  symmetrically, which the ticket allows outright.
*/
.featured-poster-link {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 2 / 3;
  overflow: hidden;
  border-radius: 12px;
  background: var(--surface-hover);
}

.featured-poster {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/*
  margin-top: auto rather than a fixed height: the ending sits on the bottom edge however tall
  the card turns out, so a two-line title or a fourth provider logo makes the row grow instead of
  cutting anything off.
*/
.featured-action {
  margin-top: auto;
  padding-top: 0.75rem;
}

.featured-title {
  font-size: 1.1rem;
  margin: 0.75rem 0 0.25rem;
}

.featured-providers {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin: 0.5rem 0;
}

.featured-in-library {
  margin-bottom: 0;
}

.featured-provider-icon {
  max-height: 28px;
  border-radius: 6px;
}

.featured-reason {
  font-size: 0.85rem;
  margin: 0 0 0.25rem;
}

.featured-availability {
  margin: 0.3rem 0 0;
  font-size: 0.88rem;
}

/* The poster doubles as the trailer trigger when there is one (#131, mirrors #129) -- a
   <button> wearing the same geometry class as the plain <a> variant, reset back to a bare box so
   default button chrome does not show through it. */
.featured-trailer-trigger {
  position: relative;
  border: 0;
  padding: 0;
  cursor: pointer;
}

.featured-trailer-trigger:focus-visible {
  outline: 2px solid var(--brand-coral);
  outline-offset: 2px;
}

.featured-trailer-trigger .play-overlay {
  width: 2.75rem;
  height: 2.75rem;
}

/* One or more curated home-page sections, each with its own heading (#131). */
.featured-section + .featured-section {
  margin-top: 2.5rem;
}

.featured-section-title {
  margin: 0 0 0.5rem;
}

.featured-section-intro {
  margin: 0 0 1.25rem;
  max-width: 65ch;
}

/* The theatrical spotlight gets fewer, larger columns than the ordinary grid -- there are at
   most two of them, and stretching them across three columns would leave an odd gap. */
.spotlight-grid {
  grid-template-columns: repeat(2, 1fr);
}

@media (max-width: 640px) {
  .spotlight-grid {
    grid-template-columns: 1fr;
  }
}

/* Curation: where a scheduled entry stands relative to its validity window. */
.featured-state {
  display: inline-block;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  border: 1px solid var(--border);
}

.featured-state-active {
  border-color: transparent;
  background: #e6f4ea;
  color: #1f6f43;
}

.featured-state-planned {
  border-color: transparent;
  background: #eef2ff;
  color: #3730a3;
}

.featured-state-expired {
  color: var(--muted);
}

.window-form input[type="datetime-local"] {
  min-width: 12.5rem;
}

/* A plain inline message, e.g. admin/providers.html's "last refresh failed" notice (#176 removed
   the one-off dismissible banner that used to share this class -- .notice itself stays, since
   this admin-page usage has nothing to do with that banner or its now-removed dismiss/hidden
   behaviour). */
.notice {
  padding: 0.85rem 0;
  background: var(--surface-hover);
  border-bottom: 1px solid var(--border);
  font-size: 0.9rem;
}

/* --- Providers: the catalog and the subscriptions -------------------------
   Both pages from #70. Every colour here comes from a token rather than a
   literal, so both themes are covered by construction -- the availability
   pills next door are hardcoded and know no night theme, which is what #72
   exists to fix. */

.catalog-group-header {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  flex-wrap: wrap;
  margin-bottom: 0.75rem;
}

.catalog-group-header h2 {
  margin: 0;
  font-size: 1.25rem;
}

/* The source ships SVG logos on a transparent ground, so they need no frame.
   Capped by height rather than width: they arrive in wildly different aspect
   ratios and a width cap would leave some of them two pixels tall. */
.provider-logo {
  max-height: 28px;
  max-width: 120px;
  width: auto;
  vertical-align: middle;
  margin-right: 0.5rem;
}

.catalog-addons {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.92rem;
}

.catalog-addons th {
  text-align: left;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--border);
}

.catalog-addons td {
  padding: 0.55rem 0.6rem;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}

.catalog-addons tr:last-child td {
  border-bottom: none;
}

/* --- The state of a catalog entry ---------------------------------------- */

.catalog-state {
  display: inline-flex;
  align-items: center;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  white-space: nowrap;
}

/* Offered is the state worth spotting, so it gets the accent. */
.catalog-state-promoted {
  background: var(--accent-soft);
  color: var(--accent);
  border: 1px solid var(--brand-coral);
}

/* Not listed by the source is a quiet state, not an alarm: the entry keeps its
   promotion and everybody's subscriptions for its return. */
.catalog-state-inactive {
  background: var(--surface-hover);
  color: var(--muted);
  border: 1px solid var(--border);
}

.catalog-types {
  margin: 0 0 0.75rem;
  font-size: 0.85rem;
}

.catalog-types span:not(:last-child)::after {
  content: ",";
}

.catalog-filter label {
  display: block;
  font-weight: 600;
  margin-bottom: 0.35rem;
}

.catalog-filter input[type="text"] {
  width: 100%;
}

.catalog-filter p {
  margin: 0.5rem 0 0;
  font-size: 0.85rem;
}

/* --- The address on the code page ----------------------------------------
   It is context rather than input since #77, so it needs the weight of a
   label without being one, and the address itself has to be readable as a
   value. Tokens only, so both themes follow. */

.form-label {
  display: block;
  font-weight: 600;
  margin-bottom: 0.35rem;
}

.verify-address {
  margin: 0;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface-hover);
  color: var(--text);
  font-size: 1rem;
  word-break: break-all;
}

/* --- Movie details page (#129) --------------------------------------------
   The redesign around watching decisions: a breadcrumb above the hero, the
   trailer lightbox, grouped "Where to watch" offers, an overflow menu for the
   one destructive action, and a horizontally scrolling cast row. Tokens only,
   so both themes follow without a dedicated dark-mode block. */

.details-breadcrumb {
  margin: 0 0 1rem;
}

.details-breadcrumb a {
  color: var(--muted);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.9rem;
}

.details-breadcrumb a:hover {
  color: var(--accent);
  text-decoration: underline;
}

.details-info h1 {
  margin: 0;
  font-family: var(--font-display);
}

.details-original-title {
  margin: 0.3rem 0 0;
}

.details-meta {
  margin: 0.35rem 0 0;
}

.details-actions {
  margin: 1rem 0;
}

.details-synopsis {
  max-width: 65ch;
  line-height: 1.55;
}

/* The poster becomes the trailer trigger when there is one -- a play badge
   sits centred over it rather than replacing it, so the artwork stays the
   first thing seen. */
.poster-trailer-trigger {
  position: relative;
  display: block;
  border: 0;
  background: transparent;
  padding: 0;
  cursor: pointer;
}

.poster-trailer-trigger:focus-visible {
  outline: 2px solid var(--brand-coral);
  outline-offset: 4px;
  border-radius: 18px;
}

.poster-trailer-trigger img {
  width: clamp(140px, 28vw, 220px);
  border-radius: 16px;
  box-shadow: var(--shadow);
  object-fit: cover;
  display: block;
}

.play-overlay {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 3.25rem;
  height: 3.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: rgba(16, 22, 28, 0.72);
  color: #fff;
  transition: transform 0.2s ease, background 0.2s ease;
}

.poster-trailer-trigger:hover .play-overlay,
.poster-trailer-trigger:focus-visible .play-overlay {
  transform: scale(1.08);
  background: var(--accent);
}

/* The overflow menu: a quiet trigger and a small panel, in the same
   toggled-popover idiom as the global search (fragments/nav.html). */
.overflow-menu {
  position: relative;
}

.overflow-menu-trigger {
  min-width: 2.6rem;
  padding: 0.6rem 0.9rem;
  font-size: 1.1rem;
  line-height: 1;
}

.overflow-menu-panel {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  z-index: 20;
  min-width: 220px;
  padding: 0.5rem;
  border-radius: 14px;
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: var(--shadow);
}

.overflow-menu-panel[hidden] {
  display: none;
}

.overflow-menu-panel form {
  margin: 0;
}

.overflow-menu-panel button.danger {
  width: 100%;
  justify-content: flex-start;
}

/* "Where to watch": grouped offers instead of a table. */
.offer-group + .offer-group {
  margin-top: 1.25rem;
}

.offer-group-title {
  margin: 0 0 0.6rem;
  font-size: 1rem;
}

.offer-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.offer-row {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.85rem;
  border-radius: 999px;
  background: var(--availability-item-bg);
  border: 1px solid var(--availability-item-border);
  box-shadow: var(--shadow-soft);
}

.offer-logo {
  width: 24px;
  height: 24px;
  object-fit: contain;
  border-radius: 6px;
}

.offer-logo-placeholder {
  background: var(--surface-hover);
}

.offer-name {
  font-weight: 600;
}

.unavailable-notice {
  padding: 1.25rem;
  border-radius: 14px;
  background: var(--surface-hover);
}

.unavailable-notice p {
  margin: 0.3rem 0 0;
}

/* The cast row: portrait cards that scroll horizontally rather than a table. */
.cast-row {
  display: flex;
  gap: 1rem;
  overflow-x: auto;
  padding-bottom: 0.35rem;
  -webkit-overflow-scrolling: touch;
}

.cast-card {
  flex: 0 0 auto;
  width: 96px;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.cast-portrait {
  width: 96px;
  height: 144px;
  border-radius: 12px;
  object-fit: cover;
  background: var(--surface-hover);
}

.cast-portrait-placeholder {
  display: block;
}

.cast-name {
  font-weight: 600;
  font-size: 0.85rem;
  line-height: 1.3;
}

.cast-character {
  font-size: 0.8rem;
  line-height: 1.3;
}

.details-directors {
  margin-top: 1.25rem;
}

/* The trailer lightbox: no autoplay until a trigger is activated, and torn
   back down to an empty src on close, which is what stops playback (#129). */
.trailer-lightbox {
  position: fixed;
  inset: 0;
  background: rgba(18, 17, 15, 0.86);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  z-index: 1200;
}

.trailer-lightbox[hidden] {
  display: none;
}

.trailer-lightbox-panel {
  position: relative;
  width: min(92vw, 960px);
}

.trailer-lightbox-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
  overflow: hidden;
  border-radius: 12px;
  background: var(--surface-hover);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
}

.trailer-lightbox-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

.trailer-lightbox-close {
  position: absolute;
  top: -3rem;
  right: 0;
  border: 1px solid rgba(255, 255, 255, 0.5);
  background: rgba(30, 29, 26, 0.65);
  color: #fff;
}

.trailer-lightbox-title {
  margin: 0 0 0.6rem;
  color: #fff;
  font-weight: 600;
  text-align: center;
}

.trailer-lightbox-fallback {
  margin: 0.75rem 0 0;
  text-align: center;
  color: rgba(255, 255, 255, 0.75);
  font-size: 0.85rem;
}

.trailer-lightbox-fallback a {
  color: #fff;
}

body.trailer-lightbox-open {
  overflow: hidden;
}

@media (max-width: 640px) {
  .trailer-lightbox-close {
    top: 1rem;
    right: 1rem;
  }
}

/* ---------------------------------------------------------------------------
   The admin section (#82)

   Every colour here comes from a token, without exception. That is the same
   rule the provider page follows since #70 and the reason the palette
   correction in #64 could be made in one place: a literal in a component is a
   colour that the night theme never learns about.
   --------------------------------------------------------------------------- */

/* Reachable by a screen reader, invisible on screen. Used for table headings
   that would be noise as visible text but are needed to read the table. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The one extra navigation entry. Set apart with a boundary rather than a
   colour: the bar is already tight between 721 and 1039 px, and a border costs
   no width the way a background pill does. 3:1 against the header is what
   WCAG 1.4.11 asks of a control boundary, which --field-border carries. */
.nav a.nav-admin {
  border: 1px solid var(--field-border);
  border-radius: 999px;
  padding: 0.2rem 0.7rem;
}

.nav a.nav-admin[aria-current="page"] {
  border-color: var(--accent);
  color: var(--accent);
}

.admin-group {
  margin-bottom: 2rem;
}

.admin-build-info {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.5rem;
  margin: 0 0 2rem;
  padding: 0.75rem 1rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
}

.admin-build-info div {
  display: flex;
  gap: 0.45rem;
}

.admin-build-info dt {
  color: var(--muted);
  font-weight: 600;
}

.admin-build-info dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.admin-group h2 {
  margin: 0 0 0.75rem;
  font-size: 1rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.admin-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1rem;
}

/* A whole card is the link, so the target is the card and not four words in
   it. text-decoration is dropped here and carried by the heading on hover, or
   every line of the description would be underlined. */
a.admin-card {
  display: block;
  padding: 1.25rem;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
  box-shadow: var(--shadow-soft);
}

a.admin-card:hover {
  background: var(--surface-hover);
  border-color: var(--field-border);
}

a.admin-card h3 {
  margin: 0 0 0.4rem;
  font-size: 1.1rem;
}

a.admin-card:hover h3 {
  text-decoration: underline;
}

a.admin-card p {
  margin: 0;
  color: var(--muted);
  font-size: 0.95rem;
  line-height: 1.5;
}

.runtime-settings {
  display: grid;
  gap: 1rem;
}

.runtime-setting {
  display: grid;
  gap: 1rem;
  grid-template-columns: minmax(16rem, 1fr) minmax(18rem, 1fr);
  align-items: end;
}

.runtime-setting h3,
.runtime-setting p {
  margin-top: 0;
}

.runtime-setting-form {
  display: flex;
  gap: 0.75rem;
  align-items: center;
}

.runtime-setting-form input,
.runtime-setting-form select {
  min-width: 0;
  width: 100%;
}

/* The counterpart to .error: something worked. Kept as quiet as the error is
   loud, because a confirmation is read once and then in the way. */
.admin-confirmation {
  margin: 0 0 1rem;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--field-border);
  border-left: 3px solid var(--accent);
  border-radius: 8px;
  background: var(--surface-hover);
  color: var(--text);
}

/*
  One table style for the admin section. It arrived with the token list in #82 and the user list
  in #94 needs exactly the same thing, so the selector is shared rather than the declarations
  copied -- .token-table stays as a name because tests and markup use it.
*/
.token-table,
.data-table {
  width: 100%;
  border-collapse: collapse;
  /* Timestamps in columns line up only with tabular figures. */
  font-variant-numeric: tabular-nums;
}

.token-table th,
.token-table td,
.data-table th,
.data-table td {
  padding: 0.6rem 0.5rem;
  text-align: left;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}

.token-table th,
.data-table th {
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.token-status {
  font-size: 0.8rem;
  letter-spacing: 0.04em;
}

/* A table cell holding more than one action button (#166): wraps onto a second line on a narrow
   screen instead of forcing the table wider or letting the buttons overlap the next column. */
.row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

/* A revoked row stays legible rather than being greyed into unreadability:
   it is history, and history that cannot be read is not history. */
.token-table tr.token-revoked .token-status {
  color: var(--muted);
}

.token-reveal {
  border-color: var(--accent);
}

.token-value-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin: 0 0 0.75rem;
}

.token-value {
  flex: 1 1 20rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--field-border);
  border-radius: 8px;
  background: var(--input-bg);
  color: var(--text);
  /* It is a secret with no line breaks of its own; wrapping beats a scrollbar
     when the job is to read it once and copy it. */
  word-break: break-all;
  font-size: 0.95rem;
}

/* Secondary action next to a destructive-sounding one. Outline rather than
   fill, so "Revoke" does not look like the primary thing to do on the page. */
.button.button-quiet {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--field-border);
  box-shadow: none;
}

.button.button-quiet:hover {
  background: var(--surface-hover);
  border-color: var(--accent);
}

/* --- Curated movie collections (#80) ------------------------------------- */
.collection-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.25rem;
}

a.collection-card {
  display: flex;
  flex-direction: column;
  color: var(--text);
  text-decoration: none;
}

a.collection-card:hover h2 {
  text-decoration: underline;
}

.collection-cover {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 10px;
  margin-bottom: 0.8rem;
}

.collection-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.25rem;
  align-items: center;
  margin-bottom: 1.5rem;
}

.collection-hero:has(> img) {
  grid-template-columns: minmax(220px, 0.8fr) minmax(0, 1.2fr);
}

.collection-hero > img {
  width: 100%;
  max-height: 320px;
  object-fit: cover;
  border-radius: 14px;
}

.collection-progress,
.recommendation-success {
  font-weight: 700;
  color: var(--accent);
}

.recommendation-header {
  max-width: 70ch;
  margin-bottom: 1.5rem;
}

.recommendation-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  margin-top: 1.25rem;
}

.recommendation-actions form {
  margin: 0;
}

.collection-editor {
  display: grid;
  gap: 1rem;
}

.collection-editor-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1rem;
}

.collection-editor textarea,
.collection-editor input[type="url"] {
  width: 100%;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--field-border);
  border-radius: 12px;
  font: inherit;
  background: var(--input-bg);
  color: var(--text);
}

.collection-danger-zone,
.danger-zone {
  border-color: var(--danger);
}

.danger-zone h2 {
  margin-top: 0;
}

/* The acknowledgement checkbox on the final, irreversible delete-account step (#140): its own
   row rather than folded into .form-row, since a lone checkbox reads oddly with a stacked label
   above an input meant for text. */
.acknowledge-row {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  margin: 1rem 0;
}

.acknowledge-row input[type="checkbox"] {
  margin-top: 0.2rem;
}

@media (max-width: 700px) {
  .collection-hero:has(> img) {
    grid-template-columns: 1fr;
  }
}

/* ---------------------------------------------------------------------------
   Navigation: areas, identity, and the views of one list (#65)

   Every colour comes from a token. Active states never rely on colour alone --
   WCAG 1.4.1 -- so each one carries a surface and an underline as well.
   --------------------------------------------------------------------------- */

/* The entries wrap instead of running off the edge. Between 721 and 1039 px
   they used to do the latter, which is what made Settings and Logout
   unreachable at 760, 800 and 900 px. */
.nav-links {
  flex-wrap: wrap;
}

/* The identity sits at the far right, pushed there rather than positioned, so
   it stays out of the way of however many areas there are. */
.nav-account {
  position: relative;
  margin-left: auto;
}

/* --- The active area ------------------------------------------------------ */

.nav a.nav-link[aria-current="page"] {
  background: var(--accent-soft);
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 0.25em;
}

.nav a.nav-brand[aria-current="page"] {
  color: var(--accent);
}

/* --- The account disclosure ---------------------------------------------- */

.account-trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.25rem 0.6rem 0.25rem 0.3rem;
  /* A boundary rather than a fill: this is not a navigation entry and must not
     read like one (AK5). --field-border carries the 3:1 that WCAG 1.4.11 asks
     of a control boundary. */
  border: 1px solid var(--field-border);
  border-radius: 999px;
  background: transparent;
  color: var(--text);
  font: inherit;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-size: 0.8rem;
  font-weight: 700;
  cursor: pointer;
}

.account-trigger:hover {
  background: var(--surface-hover);
}

.account-trigger.is-current {
  border-color: var(--accent);
  color: var(--accent);
}

.account-initials {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
}

.account-chevron {
  font-size: 0.7rem;
}

/* The unread-updates bubble (#136). Text carries the count; color is never the only signal --
   the trigger's aria-label states it in words too. */
.account-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.2rem;
  height: 1.2rem;
  padding: 0 0.3rem;
  border-radius: 999px;
  background: var(--accent);
  color: var(--surface);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: normal;
  text-transform: none;
}

.account-menu-updates {
  display: flex;
  align-items: center;
  justify-content: space-between !important;
}

.account-menu {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  min-width: 13rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  box-shadow: var(--shadow);
  /* Above the sticky header's own stacking context. */
  z-index: 3;
  text-transform: none;
  letter-spacing: normal;
}

/*
  Without this the menu is never closed.

  The [hidden] attribute hides an element through the user agent's own stylesheet, and any class
  rule setting display beats it -- so .account-menu { display: flex } above quietly overrode it and
  the disclosure stood permanently open. A collision worth naming, because the markup and the
  script both looked right.
*/
.account-menu[hidden] {
  display: none;
}

.account-identity {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  margin: 0 0 0.35rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--border);
  font-size: 0.9rem;
}

.account-identity .muted {
  font-size: 0.8rem;
  /* An address is long and must not stretch the menu. */
  word-break: break-all;
}

.nav .account-menu a {
  width: 100%;
  justify-content: flex-start;
  text-transform: none;
  letter-spacing: normal;
}

.nav .account-menu a[aria-current="page"] {
  background: var(--accent-soft);
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 0.25em;
}

.account-menu .nav-logout-form {
  width: 100%;
}

.account-menu .nav-logout-button {
  width: 100%;
  justify-content: center;
}

/* --- The views of My Movies ---------------------------------------------- */

.subnav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 1.5rem;
}

.subnav-item {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.04em;
}

.subnav-item:hover {
  border-color: var(--field-border);
  background: var(--surface-hover);
}

.subnav-item.is-current {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 0.25em;
}

@media (max-width: 640px) {
  .runtime-setting {
    grid-template-columns: 1fr;
  }

  /* Two by two rather than a row that scrolls sideways (AK17). */
  .subnav {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
  }

  .subnav-item {
    justify-content: space-between;
  }
}

/* ---------------------------------------------------------------------------
   The user list (#94)
   --------------------------------------------------------------------------- */

/* A blocked row stays readable. It is the row an operator came to look at, and
   greying it into illegibility would hide what they are checking. */
.data-table tr.row-blocked .row-status {
  color: var(--muted);
}

.row-status {
  font-size: 0.8rem;
  letter-spacing: 0.04em;
}

/* ---------------------------------------------------------------------------
   Public catalogue search (#95)
   --------------------------------------------------------------------------- */

/* The search belongs beside the brand. The areas take the remaining space and
   the identity still owns the far edge. */
.nav a.nav-brand {
  margin-right: 0;
}

.nav-search {
  position: relative;
  width: clamp(12rem, 24vw, 19rem);
  text-transform: none;
  letter-spacing: normal;
}

.nav-search-toggle {
  display: none;
  width: 2.35rem;
  height: 2.35rem;
  padding: 0.55rem;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--field-border);
  border-radius: 999px;
  background: transparent;
  color: var(--text);
  cursor: pointer;
}

.nav-search-toggle svg,
.nav-search-form button svg {
  width: 1.1rem;
  height: 1.1rem;
}

.nav-search-form,
.nav-search-field {
  margin: 0;
}

.nav-search-field {
  position: relative;
}

.nav-search-field input {
  width: 100%;
  height: 2.5rem;
  padding: 0.45rem 2.6rem 0.45rem 0.85rem;
  border-radius: 999px;
  font-size: 0.9rem;
}

.nav-search-form button {
  position: absolute;
  top: 50%;
  right: 0.35rem;
  width: 2rem;
  height: 2rem;
  padding: 0.45rem;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--text);
  box-shadow: none;
}

.nav-search-form button:hover {
  background: var(--surface-hover);
  box-shadow: none;
}

.catalog-search-popover {
  position: absolute;
  top: calc(100% + 0.5rem);
  left: 0;
  width: min(26rem, calc(100vw - 2rem));
  max-height: min(34rem, calc(100vh - 7rem));
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  box-shadow: var(--shadow);
  z-index: 4;
}

.catalog-search-popover[hidden],
.catalog-source-results[hidden] {
  display: none;
}

.catalog-search-group h2,
.catalog-source-results h2 {
  margin: 0;
  padding: 0.7rem 0.85rem 0.45rem;
  color: var(--muted);
  font-size: 0.72rem;
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.catalog-search-result {
  width: 100%;
  min-width: 0;
  padding: 0.6rem 0.85rem;
  display: flex;
  align-items: center;
  gap: 0.7rem;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--text);
  font: inherit;
  text-align: left;
  text-decoration: none;
  text-transform: none;
  letter-spacing: normal;
  box-shadow: none;
  cursor: pointer;
}

.nav a.catalog-search-result {
  padding: 0.6rem 0.85rem;
  border-radius: 0;
  font-weight: 400;
}

.catalog-search-result:hover,
.catalog-search-result:focus-visible,
.catalog-search-result.is-active,
.nav a.catalog-search-result:hover {
  background: var(--surface-hover);
  color: var(--text);
  box-shadow: none;
}

.catalog-search-poster {
  width: 2.4rem;
  height: 3.6rem;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  object-fit: cover;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface-hover);
}

.catalog-search-poster-empty {
  color: var(--muted);
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.catalog-search-result-text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.catalog-search-result-text strong {
  overflow-wrap: anywhere;
}

.catalog-search-result-text span,
.catalog-search-status,
.catalog-bulk-import-status,
.catalog-search-signin {
  color: var(--muted);
  font-size: 0.82rem;
}

.catalog-search-status,
.catalog-bulk-import-status,
.catalog-search-signin {
  margin: 0;
  padding: 0.7rem 0.85rem;
}

.catalog-search-source {
  border-top: 1px solid var(--border);
}

.catalog-source-trigger {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.65rem;
  padding: 0.75rem 0.85rem;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-weight: 700;
  box-shadow: none;
}

.catalog-source-trigger:hover:not(:disabled) {
  background: var(--accent-soft);
  box-shadow: none;
}

.catalog-source-trigger:disabled {
  color: var(--muted);
  cursor: not-allowed;
}

.catalog-source-results {
  border-top: 1px solid var(--border);
}

/*
  The checkbox sits beside a result's own button, not inside it (#184, AK1): two independent
  elements for two independent behaviours, so the click-to-open handler on the button needs no
  change at all. The row carries the left padding the button used to have on its own, so the row's
  edge still lines up with every other row in this list.
*/
.catalog-source-result-row {
  display: flex;
  align-items: stretch;
  position: relative;
}

.catalog-source-result-row .catalog-search-result {
  padding-left: 0;
}

.catalog-source-select {
  flex: 0 0 auto;
  align-self: center;
  width: 1.1rem;
  height: 1.1rem;
  margin: 0 0.15rem 0 0.85rem;
  accent-color: var(--accent);
  cursor: pointer;
}

.catalog-source-select:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

/* AK6: recognizable rather than removed once imported -- greyed out, with a small checkmark. */
.catalog-source-result-row.is-imported {
  opacity: 0.55;
}

.catalog-source-imported-badge {
  position: absolute;
  top: 50%;
  right: 0.85rem;
  transform: translateY(-50%);
  color: var(--accent);
  font-weight: 700;
}

.catalog-bulk-import {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.75rem 0.85rem;
  border-top: 1px solid var(--border);
}

.catalog-bulk-import[hidden] {
  display: none;
}

.catalog-bulk-import-trigger {
  align-self: flex-start;
  padding: 0.5rem 0.9rem;
  border: 0;
  border-radius: 8px;
  background: var(--accent-soft);
  color: var(--accent);
  font: inherit;
  font-weight: 700;
  box-shadow: none;
  cursor: pointer;
}

.catalog-bulk-import-trigger:hover:not(:disabled) {
  background: var(--accent);
  color: var(--button-text);
}

.catalog-bulk-import-trigger:disabled {
  color: var(--muted);
  cursor: not-allowed;
}

.catalog-search-message p {
  margin: 0;
}

.catalog-search-page-results {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.catalog-search-page-result {
  min-height: 8rem;
  display: flex;
  gap: 1rem;
  padding: 0.9rem;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  color: var(--text);
  text-decoration: none;
  box-shadow: var(--shadow-soft);
}

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

.catalog-search-page-result img,
.catalog-search-page-poster-empty {
  width: 4rem;
  height: 6rem;
  flex: 0 0 auto;
  object-fit: cover;
  border-radius: 8px;
  background: var(--surface-hover);
}

.catalog-search-page-poster-empty {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  font-size: 0.7rem;
  text-align: center;
}

.catalog-search-page-copy {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.catalog-search-page-copy strong {
  overflow-wrap: anywhere;
}

.catalog-search-page-copy span,
.catalog-search-more {
  color: var(--muted);
}

.catalog-search-more {
  margin: 1rem 0 0;
}

.catalog-search-page-source {
  margin-top: 1.5rem;
}

.catalog-search-page-source h2 {
  margin-top: 0;
  font-size: 1.2rem;
}

.catalog-search-page-source .catalog-source-trigger {
  width: auto;
  border: 1px solid var(--field-border);
  border-radius: 999px;
}

/* Addresses are long and must not stretch the table past the page. */
.data-table .cell-wrap {
  word-break: break-all;
}

.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  margin-top: 1.5rem;
}

@media (max-width: 899px) {
  .nav-search {
    display: contents;
  }

  .nav-search-toggle {
    display: inline-flex;
    order: 1;
    margin-left: auto;
  }

  .nav-search-panel {
    display: none;
    position: relative;
    order: 5;
    flex-basis: 100%;
    width: 100%;
  }

  .nav.search-open .nav-search-panel {
    display: block;
  }

  .catalog-search-popover {
    width: 100%;
  }

  .nav-toggle {
    margin-left: 0;
  }

  .nav-account {
    margin-left: 0.5rem;
  }

  .nav-links {
    order: 6;
  }

  /* Eight columns do not fit a phone, and a table that scrolls the page sideways
     is worse than one that scrolls inside its own box. */
  .data-table {
    display: block;
    overflow-x: auto;
  }
}

@media (max-width: 640px) {
  .nav-toggle-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }

  .nav-toggle {
    width: 2.35rem;
    height: 2.35rem;
    padding: 0;
    justify-content: center;
  }

  .catalog-search-page-results {
    grid-template-columns: 1fr;
  }
}

/* --- "My providers" (#130) --------------------------------------------------
   Subscription coverage instead of a flat subscribe/blacklist table: a score,
   each selected provider's contribution, and best-match candidates ranked by
   what they would actually add. Provider logos sit on a fixed white chip
   regardless of theme -- see the template comment for why. */

.providers-breadcrumb {
  margin: 0 0 1rem;
}

.providers-breadcrumb a {
  color: var(--muted);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.9rem;
}

.providers-breadcrumb a:hover {
  color: var(--accent);
  text-decoration: underline;
}

.success {
  background: #e6f4ea;
  border: 1px solid rgba(31, 111, 67, 0.25);
  color: #1f6f43;
  padding: 0.75rem 1rem;
  border-radius: 12px;
  margin-bottom: 1rem;
}

.coverage-summary {
  margin-bottom: 1.5rem;
}

.coverage-headline {
  margin: 0 0 0.75rem;
  font-size: 1.15rem;
}

.progress-bar {
  height: 10px;
  border-radius: 999px;
  background: var(--surface-hover);
  overflow: hidden;
}

.progress-bar-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 999px;
  transition: width 0.3s ease;
}

.coverage-full,
.coverage-none,
.coverage-free {
  margin: 0.75rem 0 0;
}

.coverage-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.25rem;
  margin-top: 1rem;
  font-size: 0.9rem;
}

.provider-section {
  margin-bottom: 2rem;
}

.provider-section h2 {
  margin-bottom: 0.25rem;
}

.provider-search {
  width: min(360px, 100%);
  margin: 0.75rem 0 1.25rem;
  padding: 0.6rem 0.85rem;
  border: 1px solid var(--field-border);
  border-radius: 12px;
  font-size: 0.95rem;
  background: var(--input-bg);
  color: var(--text);
}

.provider-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1rem;
}

.provider-card {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1.1rem;
  border-radius: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-soft);
}

.provider-card-header {
  display: flex;
  align-items: center;
  gap: 0.65rem;
}

.provider-logo-chip {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 10px;
  background: #ffffff;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.provider-logo-chip img {
  max-width: 30px;
  max-height: 24px;
  object-fit: contain;
}

.provider-card-name {
  font-weight: 700;
  font-size: 1.05rem;
}

.provider-card-stat {
  margin: 0;
  font-size: 0.88rem;
}

.provider-selected-toggle {
  color: #1f6f43;
  background: #e6f4ea;
  box-shadow: none;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.provider-selected-toggle:hover {
  background: #d7ecdd;
}

.provider-selected-toggle .subscribed-indicator {
  width: 20px;
  height: 20px;
  background: transparent;
  border: none;
  color: inherit;
}

@media (max-width: 640px) {
  .provider-grid {
    grid-template-columns: 1fr;
  }
}

/* --- "My Movies" list (#128) ------------------------------------------------
   Rows replace the table: a poster, title and readable availability carry the
   watching decision, and management (checkboxes, bulk removal) sits behind an
   explicit "Manage list" toggle instead of being permanently on screen. Tokens
   only, so both themes follow without a dedicated dark-mode block. */

.movie-list-sort {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin: 0 0 1rem;
  font-size: 0.9rem;
}

.movie-list {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.movie-list-manage-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.85rem 1.1rem;
  border-radius: 14px;
  background: var(--surface-hover);
}

.movie-list-manage-bar[hidden] {
  display: none;
}

.movie-list-select-all {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 600;
  font-size: 0.9rem;
}

.movie-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.85rem 1.1rem;
  border-radius: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-soft);
}

.movie-row-select {
  display: none;
  flex: 0 0 auto;
}

/* An unread update (#136). The left accent plus the dot repeat the same signal two ways, so
   neither is the only carrier of it -- the accessible label on the dot says it in words too. */
.update-row-unread {
  border-left: 3px solid var(--accent);
}

.update-unread-label {
  color: var(--accent);
  margin-left: 0.4rem;
}

.update-row-offers,
.update-row-time {
  font-size: 0.85rem;
}

.movie-list-managing .movie-row-select {
  display: inline-flex;
}

.movie-row-poster {
  flex: 0 0 auto;
  display: block;
  width: 56px;
  aspect-ratio: 2 / 3;
  border-radius: 8px;
  overflow: hidden;
  background: var(--surface-hover);
}

.movie-row-poster img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.movie-row-poster-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
}

.movie-row-poster-placeholder svg {
  width: 45%;
  height: 45%;
}

.movie-row-info {
  flex: 1 1 auto;
  min-width: 0;
}

.movie-row-title {
  display: block;
  font-size: 1.05rem;
}

.movie-row-availability {
  margin: 0.3rem 0 0;
  font-size: 0.88rem;
}

.movie-row-actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.movie-row-promo {
  border-style: dashed;
  background: var(--accent-soft);
  align-items: flex-start;
  flex-direction: column;
  gap: 0.3rem;
}

.movie-row-promo-label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--accent);
  font-weight: 700;
}

.movie-row-promo-headline {
  font-weight: 600;
  color: var(--ink);
}

/* The overflow menu, in the same toggled-popover idiom as the global search
   (fragments/nav.html) -- one instance per row here, unlike the single one on
   the detail page. */
.overflow-menu {
  position: relative;
}

.overflow-menu-trigger {
  min-width: 2.6rem;
  padding: 0.6rem 0.9rem;
  font-size: 1.1rem;
  line-height: 1;
}

.overflow-menu-panel {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  z-index: 20;
  min-width: 220px;
  padding: 0.5rem;
  border-radius: 14px;
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: var(--shadow);
}

.overflow-menu-panel[hidden] {
  display: none;
}

.overflow-menu-panel form {
  margin: 0;
}

.overflow-menu-panel button.danger {
  width: 100%;
  justify-content: flex-start;
}

@media (max-width: 640px) {
  .movie-row {
    flex-wrap: wrap;
  }

  .movie-row-info {
    order: 1;
    flex-basis: calc(100% - 56px - 1rem);
  }

  .movie-row-actions {
    order: 2;
    flex-basis: 100%;
    justify-content: flex-end;
  }
}
