@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700;900&family=Lato:wght@400;700&family=Baloo+2:wght@600;700&display=swap');

:root {
    --bg: #f7f6f3;
    --card-bg: #ffffff;
    --border: #e2e0da;
    --text: #2b2b2b;
    --text-muted: #6b6b6b;
    --accent: #c0392b;
    --brand-purple: #67277b;
    --header-bg: var(--brand-purple);
    --header-text: #f7f6f3;
    --row-hover: #f1e7f4;
}

* {
    box-sizing: border-box;
}

[hidden] { display: none !important; }

body {
    margin: 0;
    padding: 24px 16px 48px;
    background: var(--bg);
    color: var(--text);
    font-family: 'Source Sans Pro', 'Lato', -apple-system, sans-serif;
}

.page {
    max-width: 728px; /* was 1040px -- ~30% narrower so each card reads as a taller, more poster-forward column */
    margin: 0 auto;
}

header.masthead {
    position: sticky;
    top: 0;
    z-index: 20;
    background: var(--bg);
    margin-bottom: 24px;
    text-align: center;
}

header.masthead .logo {
    display: inline-block;
    height: 48px;
    width: auto;
    max-width: 100%;
    margin: 0 0 8px;
}

.controls {
    display: flex;
    justify-content: center;
    gap: 20px;
    flex-wrap: wrap;
    margin-top: 16px;
}

.controls label {
    font-size: 0.9rem;
    color: var(--text-muted);
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.controls select {
    font-family: inherit;
    font-size: 0.9rem;
    padding: 5px 8px;
    border-radius: 6px;
    border: 1px solid var(--border);
    background: var(--card-bg);
    color: var(--text);
}

.filter {
    position: relative;
    display: inline-block;
}

.clear-filters-btn {
    font-family: inherit;
    font-size: 0.9rem;
    padding: 5px 10px;
    border-radius: 6px;
    border: 1px solid var(--accent);
    background: var(--card-bg);
    color: var(--accent);
    cursor: pointer;
}

.clear-filters-btn:hover {
    background: var(--accent);
    color: #fff;
}

.cell-filter-btn {
    font-family: inherit;
    font-size: inherit;
    color: inherit;
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: var(--border);
    text-underline-offset: 2px;
    text-align: left;
}

.cell-filter-btn:hover {
    color: var(--accent);
    text-decoration-color: var(--accent);
}

.watch-logo-btn {
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    border-radius: 4px;
    padding: 2px;
}

.score-cell {
    width: 60px;
    max-width: 60px;
}

.score-bars {
    display: flex;
    align-items: flex-end;
    gap: 1px;
    height: 16px;
}

.score-bar {
    width: 4px;
    height: 100%;
    border-radius: 2px;
    background: var(--brand-purple);
    flex: none;
}

/* Bars past the title's actual score, up to the max-possible count, render
   in a pale gray-purple so every score reads as a fixed 10-bar meter (how
   full is this one) rather than a bar of variable length. */
.score-bar-empty {
    background: rgba(103, 39, 123, 0.15);
}

.watch-logo-btn:hover {
    background: var(--row-hover);
}

.filter-toggle {
    font-family: inherit;
    font-size: 0.9rem;
    padding: 5px 10px;
    border-radius: 6px;
    border: 1px solid var(--border);
    background: var(--card-bg);
    color: var(--text);
    cursor: pointer;
}

.filter-toggle:hover {
    border-color: #c9c6bd;
}

.filter-toggle.has-selection {
    border-color: #ffffff;
    box-shadow: 0 0 0 2px var(--brand-purple);
}

.filter-count {
    color: var(--brand-purple);
    font-weight: 600;
}

.active-filter-tokens {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 12px;
    min-height: 25px;
}

.filter-token {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: var(--brand-purple);
    color: #ffffff;
    font-size: 0.82rem;
    font-weight: 600;
    padding: 4px 6px 4px 10px;
    border-radius: 999px;
}

.filter-token-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    border: none;
    background: rgba(255, 255, 255, 0.25);
    color: #ffffff;
    font-size: 0.8rem;
    line-height: 1;
    cursor: pointer;
    padding: 0;
}

.filter-token-remove:hover {
    background: rgba(255, 255, 255, 0.45);
}

.filter-panel {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 10;
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
    padding: 8px;
    min-width: 200px;
    max-height: 260px;
    overflow-y: auto;
    text-align: left;
}

.filter-panel label {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 6px;
    border-radius: 5px;
    font-size: 0.88rem;
    color: var(--text);
    cursor: pointer;
    white-space: nowrap;
}

.filter-panel label:hover {
    background: var(--bg);
}

.filter-panel .filter-actions {
    display: flex;
    justify-content: space-between;
    padding: 4px 6px 8px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 4px;
}

.filter-panel .filter-actions button {
    font-family: inherit;
    font-size: 0.78rem;
    color: var(--accent);
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
}

header.masthead p#subtitle {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.95rem;
}

/* ---- table chrome: the wrap doesn't draw a card anymore -- each row is
   its own floating card (see table.top10 tbody tr below), same technique
   at every width, just bigger on desktop. ---------------------------- */
.table-wrap {
    background: transparent;
    border: none;
    border-radius: 0;
    overflow: visible;
    box-shadow: none;
}

table.top10 {
    width: 100%;
    border-collapse: collapse;
}

/* No column-header row at any width -- a stack of cards has nothing for a
   header to line up with. */
table.top10 thead tr { display: none; }

table.top10 th,
table.top10 td {
    text-align: left;
    font-size: 0.92rem;
    vertical-align: middle;
}

table.top10 tbody td { padding: 0; } /* spacing comes from the row grid, not per-cell padding */

/* ---- the row itself: a self-contained card, poster on the left,
   everything else stacked in a column on the right -------------------
     columns:  30% (poster)  |  1fr (content)
     rows:     title+rank | genre | cast | weeks-in-top10 | release date |
               (flexible spacer) | score+stream
   Deliberately taller than a table row ever was -- min-height plus a big
   poster column means the image reads as a genuinely large, pronounced
   piece of art, at the cost of fewer titles fitting on screen at once. The
   poster column is narrower than the card itself is wide (30% vs. the
   card's own ~30%-narrower-than-before width) so a real ~2:3 poster fills
   its column without much letterboxing, now that the card is taller. */
table.top10 tbody tr {
    display: grid;
    grid-template-columns: 30% 1fr; /* was 38% -- narrower to suit the taller, narrower card */
    /* trailing 1fr row is unused by any text content, but the poster
       (grid-row: 1 / -1, below) spans through it too, so it claims all the
       leftover height once min-height forces the card taller than its
       (short, tightly-packed) text content needs. */
    grid-template-rows: auto auto auto auto auto 1fr auto;
    column-gap: 16px;
    row-gap: 3px;
    align-items: start;
    min-height: 168px; /* was 140px -- ~20% taller to better showcase the poster */
    padding: 10px;
    position: relative;
    background: var(--card-bg);
    border-radius: 16px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 10px 28px rgba(0, 0, 0, 0.07);
    margin-bottom: 10px;
}
table.top10 tbody tr:last-child { margin-bottom: 0; }
table.top10 tbody tr td { border-bottom: none; }

table.top10 tbody tr:not(.detail-row):hover {
    background: var(--row-hover);
}

/* A card whose detail row is open loses its own bottom radius/margin so it
   sits flush against the detail panel below -- the two rows together read
   as one continuous card. The detail panel (not this row) carries the
   bottom radius, shadow continuation, and the margin down to the next
   card. */
table.top10 tbody tr.row-expanded {
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
    margin-bottom: 0;
}

/* The generic "table.top10 tbody tr" card rule above (display: grid,
   min-height, padding, card background/shadow/radius) has no :not()
   exclusion, so without this reset it would also turn the plain detail
   <tr> into an empty second card -- .detail-row needs a higher-specificity
   rule (an extra class beats none) to opt back out into a bare table row;
   the actual visible "card" for the detail content is .detail-panel. */
table.top10 tbody tr.detail-row {
    display: table-row;
    min-height: 0;
    padding: 0;
    background: transparent;
    border-radius: 0;
    box-shadow: none;
    margin-bottom: 0;
    position: static;
}

/* rank: large, extra-bold purple numeral pinned to the card's top-right
   corner, next to the title -- the title reserves room for it via
   padding-right below. table.top10 .rank (not plain .rank) so this beats
   the generic "table.top10 td { font-size }" rule on specificity. */
table.top10 .rank {
    position: absolute;
    top: 12px;
    right: 12px;
    width: auto;
    color: var(--brand-purple);
    font-weight: 900;
    font-size: 2.2rem;
    line-height: 1;
}

/* .title-cell and .title-cell-inner are collapsed with display: contents
   so the poster link and title link become direct grid items of the <tr>
   and can be placed independently -- no HTML/JS change needed. */
.title-cell { display: contents; }
.title-cell-inner { display: contents; }

/* Box art: object-fit: contain inside a tile that fills the poster column
   and the card's full height, poster-style. Source art is a mix of
   landscape title cards (~1.8-2.4:1) and portrait movie posters -- contain
   shows either in full, never cropped.

   Deliberately position: absolute rather than a grid item sharing the row
   (grid-column/grid-row like the mobile version below): a grid item whose
   only sizing hint is "fill the row" still gets asked to contribute a
   preferred size when the browser sizes the row's auto/1fr tracks, and for
   an <img> with no explicit non-percentage height, that preferred size is
   its own aspect-ratio-driven auto-height at the column's width -- for a
   portrait poster at this column's width, that can be 400px+, ballooning
   the entire row. Taking the poster out of grid flow entirely removes it
   from that sizing negotiation: the row's height is set purely by the
   content column's text, and the poster (sized from the row's *resolved*
   height via top/bottom) just fills whatever that turns out to be.
   grid-template-columns on the row (30% 1fr) still reserves the poster's
   column even though nothing is placed in it, so the content column
   starts in the same place either way. */
.boxart-link {
    position: absolute;
    top: 12px;
    left: 12px;
    bottom: 12px;
    /* Matches the row's declared 30% column width -- computed against the
       *content* box (row width minus this same 12px+12px padding), not
       the *padding* box that an absolutely positioned element's own
       percentage width actually resolves against. */
    width: calc(30% - 7.2px);
    border-radius: 12px;
    overflow: hidden;
    background: linear-gradient(155deg, #fbfafb 0%, #f2f1f3 100%);
    box-shadow: 0 0 0 1px rgba(103, 39, 123, 0.16), 0 1px 2px rgba(0, 0, 0, 0.05);
    text-decoration: none;
    display: block;
    /* Now a <button>, not an <a> -- strip the native button chrome so it
       still looks exactly like the plain image tile it always was. */
    border: none;
    padding: 0;
    margin: 0;
    font: inherit;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.boxart {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: contain;
    padding: 6px;
    box-sizing: border-box;
    background: transparent;
}

.boxart-placeholder {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 0; /* corners already clipped by .boxart-link's overflow: hidden */
    text-align: center;
    padding: 10px;
    overflow: hidden;
}

.boxart-placeholder .bubble-title {
    font-family: 'Baloo 2', 'Lato', sans-serif;
    font-weight: 700;
    font-size: 1.3rem;
    line-height: 1.15;
    color: #ffffff;
    text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2), 0 0 6px rgba(255, 255, 255, 0.25);
    letter-spacing: 0.01em;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* A small, pleasant, high-contrast palette the placeholder cycles through
   (deterministically, by title) so repeat titles always get the same color
   and adjacent rows read as visually distinct. */
.bubble-bg-0 { background: linear-gradient(135deg, #6c5ce7, #a29bfe); }
.bubble-bg-1 { background: linear-gradient(135deg, #e17055, #fab1a0); }
.bubble-bg-2 { background: linear-gradient(135deg, #00b894, #55efc4); }
.bubble-bg-3 { background: linear-gradient(135deg, #0984e3, #74b9ff); }
.bubble-bg-4 { background: linear-gradient(135deg, #d63031, #ff7675); }
.bubble-bg-5 { background: linear-gradient(135deg, #e84393, #fd79a8); }
.bubble-bg-6 { background: linear-gradient(135deg, #00907a, #00cec9); }
.bubble-bg-7 { background: linear-gradient(135deg, #fdcb6e, #e17055); }

.title-cell .title-name {
    grid-column: 2;
    grid-row: 1;
    align-self: start;
    min-width: 0;
    padding-right: 56px; /* leave room for the rank numeral pinned top-right */
    text-decoration: none;
    color: var(--text);
    font-weight: 700;
    font-size: 1.15rem;
    line-height: 1.2;
    /* Now a <button>, not an <a> -- strip native button chrome and reset
       display/alignment so the text reads exactly as it did before. */
    display: flex;
    align-items: baseline;
    gap: 6px;
    background: none;
    border: none;
    text-align: left;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.title-cell .title-name:hover {
    color: var(--accent);
}

.expand-chevron {
    flex: none;
    display: inline-flex;
    color: var(--text-muted);
    transform: translateY(1px);
    transition: transform 0.22s ease;
}
.title-cell .title-name:hover .expand-chevron { color: var(--accent); }
table.top10 tbody tr.row-expanded .expand-chevron { transform: rotate(180deg) translateY(-1px); }

/* Line-clamp lives on the nested span, not on .title-name itself: since
   title-cell/title-cell-inner are display: contents, .title-name is
   promoted to a direct CSS Grid item, and Chrome silently breaks
   -webkit-line-clamp on an element that is itself a grid item -- the
   clamp's height math still applies, but it stops actually truncating the
   rendered text at 2 lines. The nested .title-name-text span is not
   itself a grid item, so the clamp works correctly on it. */
.title-cell .title-name-text {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.muted {
    color: var(--text-muted);
}

/* genre: plain, no chip/token -- bare uppercase text, click-to-filter via
   the same button, directly under the title. */
.genre-cell {
    grid-column: 2;
    grid-row: 2;
    justify-self: start;
    width: auto;
    max-width: none;
    margin: 0;
}
.genre-cell .cell-filter-btn {
    display: inline-block;
    background: none;
    color: var(--text);
    text-decoration: none;
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    padding: 0;
}
.genre-cell .cell-filter-btn:hover { color: var(--accent); }
.genre-runtime-sep { color: var(--text); font-size: 0.8em; }

.detail-keywords {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--text-muted);
    text-transform: uppercase;
    margin: 0 0 6px;
}

/* top cast: left-aligned under the genre, one name per line. */
.cast-cell {
    grid-column: 2;
    grid-row: 3;
    justify-self: start;
    width: auto;
    max-width: none;
    margin: 0;
    font-size: 0.8rem;
    line-height: 1.15;
}

.cast-link {
    display: block;
    color: inherit;
    text-decoration: underline;
    text-decoration-color: var(--border);
    text-underline-offset: 2px;
}

.cast-link + .cast-link {
    margin-top: 2px;
}

.cast-link:hover {
    color: var(--accent);
    text-decoration-color: var(--accent);
}

/* weeks-in-top10 and release date: small muted stat lines, each with an
   inline label baked into the text (dashboard.js) since there's no column
   header to explain a bare number or date anymore. */
.weeks-cell {
    grid-column: 2;
    grid-row: 4;
    justify-self: start;
    width: auto;
    max-width: none;
    margin: 0;
    font-size: 0.78rem;
    color: var(--text-muted);
}

.date-cell {
    grid-column: 2;
    grid-row: 5;
    justify-self: start;
    width: auto;
    max-width: none;
    margin: 0;
    font-size: 0.78rem;
    color: var(--text-muted);
}

/* footer row: the flexible spacer above (grid row 6) pushes this row to
   the card's bottom edge. Score bars sit bottom-left, streaming logo
   bottom-right -- both share this last grid row. */
.score-cell {
    grid-column: 2;
    grid-row: 7;
    justify-self: start;
    align-self: end;
    width: auto;
    max-width: none;
    margin: 0;
    display: flex;
    align-items: center;
}
/* mini play-button mark (cropped from the logo) as a small label icon
   sitting just to the left of the score bars */
.score-cell::before {
    content: "";
    display: block;
    flex: none;
    width: 10px;
    height: 13px;
    margin-right: 5px;
    background-image: url("img/vidmired-icon.png");
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
}

.stream-cell {
    grid-column: 2;
    grid-row: 7;
    justify-self: end;
    align-self: end;
    width: auto;
    max-width: none;
    margin: 0;
}

.watch-logos {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.watch-logo {
    height: 20px;
    max-width: 72px;
    object-fit: contain;
}

.stream-cell .watch-logos { justify-content: flex-end; }
.stream-cell .watch-logo { height: 20px; max-width: 74px; }

/* ---- expanded-card detail row --------------------------------------
   A genuine sibling <tr> inserted right after the card's own <tr> (see
   dashboard.js's makeDetailRow/render), not a grid-row placed inside the
   card's own row -- so it can never desync from however many rows the
   card's own grid-template-rows declares. Collapsed by default (zero
   height, clipped); toggleExpand() in dashboard.js measures and sets an
   explicit max-height to animate open/closed since the content's height
   isn't known in advance. */
table.top10 tbody tr.detail-row td { padding: 0; }

.detail-panel {
    max-height: 0;
    overflow: hidden;
    opacity: 0;
    transition: max-height 0.32s ease, opacity 0.22s ease;
    background: var(--card-bg);
    border-bottom-left-radius: 16px;
    border-bottom-right-radius: 16px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 10px 28px rgba(0, 0, 0, 0.07);
    margin-bottom: 10px;
    padding: 0 22px;
}
table.top10 tbody tr.detail-row:last-child .detail-panel { margin-bottom: 0; }
table.top10 tbody tr.detail-row.open .detail-panel { opacity: 1; }

.detail-panel-inner {
    padding: 14px 0 18px;
    border-top: 1px solid var(--border);
}

.detail-summary {
    margin: 0 0 12px;
    max-width: 640px;
    font-size: 0.88rem;
    line-height: 1.5;
    color: var(--text);
}

.detail-links {
    display: flex;
    align-items: center;
    gap: 14px;
}

/* Full-color brand icons (Google/Wikipedia/Rotten Tomatoes' own logo
   colors, not a currentColor glyph), so there's no tint-on-hover -- just a
   small lift/scale for feedback instead of a color change. */
.detail-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    transition: transform 0.15s ease, opacity 0.15s ease;
    opacity: 0.85;
}
.detail-link:hover { transform: translateY(-1px) scale(1.08); opacity: 1; }

.detail-link-icon {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

@media (prefers-reduced-motion: reduce) {
    .expand-chevron,
    .detail-panel {
        transition: none !important;
    }
}

#status {
    text-align: center;
    color: var(--text-muted);
    padding: 24px 0;
}
/* ==========================================================================
   VIDMIRED — MOBILE REDESIGN
   Everything below @media (max-width: 640px) shrinks the same card-per-row
   layout used at every width (see table.top10 tbody tr in the base rules
   above) down to a compact size, hides three lower-priority attributes
   (top cast is kept; weeks-in-top10 and release date are dropped) to fit
   a narrow screen, and repositions a few pieces (title text width, rank
   size, genre/score/stream placement) for the tighter card. Structural
   technique (display: contents to promote the poster+title out of their
   wrapper into direct grid items) is shared with the base rules, just
   re-declared here with mobile-scale numbers.
   ========================================================================== */

@media (max-width: 640px) {

    /* ---- page-level breathing room ------------------------------------ */
    body { padding: 18px 14px 40px; }
    header.masthead { margin-bottom: 10px; }

    /* ---- controls: bordered-pill soup -> wrapping chip bar. (Originally
       a horizontally-scrolling strip, but that hid filters off-screen;
       wrapping keeps every control visible without a scroll gesture.) --- */
    .controls {
        flex-wrap: wrap;
        justify-content: center;
        gap: 8px;
        margin-top: 10px;
        padding: 2px 0 2px;
    }

    /* Genre / streaming-service filter panels are dropped on mobile — no
       room for two more chip buttons once the metadata row already carries
       genre + score + stream. Filtering by tapping a genre pill or a
       streaming logo in the list (applyExclusiveFilter) still works; only
       the dedicated toggle+panel UI for picking filters ahead of time is
       gone. "Clear all filters" and the active-filter-token chips stay, so
       a filter applied by tapping a cell is still visible and removable. */
    #genre-filter, #service-filter { display: none; }

    .controls label,
    .controls .filter,
    .filter-toggle,
    .clear-filters-btn {
        flex: 0 0 auto;
    }

    .controls label {
        background: rgba(103, 39, 123, 0.07);
        border-radius: 999px;
        padding: 7px 12px 7px 14px;
        gap: 6px;
        color: var(--text-muted);
        font-weight: 600;
        font-size: 0.8rem;
    }
    .controls select {
        border: none;
        background: none;
        color: var(--brand-purple);
        font-weight: 700;
        padding: 0 2px;
    }

    .filter-toggle {
        border: none;
        border-radius: 999px;
        padding: 8px 14px;
        background: rgba(103, 39, 123, 0.08);
        color: var(--brand-purple);
        font-weight: 600;
        font-size: 0.8rem;
    }
    .filter-toggle.has-selection {
        background: var(--brand-purple);
        color: #fff;
        box-shadow: none;
    }
    .filter-toggle .filter-count { color: inherit; }
    .filter-toggle.has-selection .filter-count { color: #fff; }

    .clear-filters-btn {
        border-radius: 999px;
        padding: 8px 14px;
        font-size: 0.8rem;
        font-weight: 600;
    }

    .active-filter-tokens {
        justify-content: center;
        margin-top: 8px;
        min-height: 0; /* don't reserve 25px of empty space when no filter is active */
    }

    /* ---- filter panel: bigger touch targets, softer sheet feel -------- */
    .filter-panel {
        position: fixed;
        top: calc(var(--masthead-h, 0px) + 8px);
        left: 14px;
        right: 14px;
        width: auto;
        min-width: 0;
        max-height: calc(100vh - var(--masthead-h, 0px) - 24px);
        border-radius: 14px;
        padding: 10px;
    }
    .filter-panel label {
        min-height: 40px;
        font-size: 0.92rem;
    }

    /* ---- table chrome cleanup: drop date (no room); weeks-in-top10 stays */
    .date-cell { display: none; }

    table.top10 th,
    table.top10 td {
        font-size: 0.9rem;
    }

    /* ---- the row itself: a self-contained card, poster on the left,
       everything else stacked in a column on the right -------------------
         columns:  38% (poster)  |  1fr (content)
         rows:     title+rank  |  genre  |  cast  |  (flexible spacer)  |  score+stream
       Source images are a mix of vertical (movie posters) and horizontal
       (title cards) — min-height gives vertical ones enough room to render
       at a reasonable size under `contain` instead of shrinking down to
       match a short, text-driven card. Title/genre/cast stay packed at the
       top; the spacer row pushes score (bottom-left) and stream (bottom-
       right) down to the card's bottom edge. */
    table.top10 tbody tr {
        display: grid;
        grid-template-columns: 38% 1fr; /* was 50% -- more compact poster, same card height */
        grid-template-rows: auto auto auto auto 1fr auto;
        column-gap: 22px;
        row-gap: 6px;
        align-items: start;
        min-height: 168px; /* was 210px — ~20% shorter so more cards fit per screen */
        padding: 14px;
        position: relative;
        background: var(--card-bg);
        border-radius: 16px;
        box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 8px 20px rgba(0, 0, 0, 0.06);
        margin-bottom: 12px;
    }
    table.top10 tbody tr:last-child { margin-bottom: 0; }
    table.top10 tbody tr td { border-bottom: none; }
    table.top10 tbody tr:not(.detail-row):active { background: var(--row-hover); }

    table.top10 tbody tr.row-expanded {
        border-bottom-left-radius: 0;
        border-bottom-right-radius: 0;
        margin-bottom: 0;
    }
    table.top10 tbody tr.detail-row {
        display: table-row;
        min-height: 0;
        padding: 0;
        background: transparent;
        border-radius: 0;
        box-shadow: none;
        margin-bottom: 0;
        position: static;
    }

    /* rank: large, extra-bold purple numeral pinned to the card's top-right
       corner, next to the title, rather than a badge beside the poster —
       the title reserves room for it via padding-right below. */
    /* table.top10 .rank (not plain .rank) so this beats the generic
       "table.top10 td { font-size: 0.9rem }" rule on specificity — a plain
       class selector loses to that rule since it has more type/class
       components, silently capping the rank at 0.9rem otherwise. */
    table.top10 .rank {
        position: absolute;
        top: 14px;
        right: 14px;
        width: auto; /* desktop's "width: 2.2em" scales with this rule's much bigger font-size otherwise, ballooning to ~148px and pushing the digit left of where right:14 implies */
        color: var(--brand-purple);
        font-weight: 900;
        font-size: 2.94rem; /* was 4.2rem — 30% smaller, freeing horizontal room for the title */
        line-height: 1;
    }

    /* .title-cell and .title-cell-inner are collapsed with display: contents
       so the poster link and title link become direct grid items of the
       <tr> and can be placed independently — no HTML/JS change needed. */
    .title-cell { display: contents; }
    .title-cell-inner { display: contents; }

    /* Box art: object-fit: contain inside a tile that fills the poster
       column and the card's full height (poster-style). The ratio stays
       deliberately narrower than every real source image (1.78-2.43:1) via
       `contain`, so a source is always constrained by width and
       letterboxed top/bottom only, never cropped and never bar-cropped
       left/right (the failure mode that used to eat wordmark letters, e.g.
       "Slow Horses" -> "N HOI"). The link owns the soft brand-purple
       backdrop so any letterboxed space still reads as an intentional card
       mat.

       position: absolute (not a grid item, unlike the rank/title/etc. —
       see the desktop rule's comment for why) so it can't inflate the
       row's height, and so its own top/left/bottom offsets are absolute
       coordinates rather than relative-positioning nudges: with
       position: relative, this rule's old top/left/bottom (still applied
       from... no rule set them, but overriding to relative here would let
       an unrelated rule's top/left/bottom apply as offsets instead of a
       no-op, silently shifting the poster within its cell). */
    .boxart-link {
        position: absolute;
        top: 14px;
        left: 14px;
        bottom: 14px;
        /* Matches the row's declared 38% column width, adjusted for the
           containing-block/content-box mismatch (see desktop rule). */
        width: calc(38% - 10.6px);
        border-radius: 10px;
        overflow: hidden;
        background: linear-gradient(155deg, #fbfafb 0%, #f2f1f3 100%);
        box-shadow: 0 0 0 1px rgba(103, 39, 123, 0.16), 0 1px 2px rgba(0, 0, 0, 0.05);
    }
    .boxart {
        width: 100%;
        height: 100%;
        display: block;
        object-fit: contain;
        padding: 4px;
        box-sizing: border-box;
        background: transparent;
    }
    .boxart-placeholder {
        width: 100%;
        height: 100%;
        border-radius: 0; /* corners already clipped by .boxart-link's overflow: hidden */
        box-shadow: none;
        padding: 4px;
    }
    .boxart-placeholder .bubble-title { font-size: 0.76rem; line-height: 1.1; }

    .title-cell .title-name {
        grid-column: 2;
        grid-row: 1;
        align-self: start;
        min-width: 0;
        /* The rank numeral is position: absolute, so it doesn't contribute
           to this row's auto height on its own — without reserving height
           here, a short one-line title lets row 2 (genre) start high enough
           to sit underneath the numeral's tall glyph box. 34px of padding
           covers the worst case (a 1-line title, ~21px tall) up to the
           numeral's ~47px rendered height. */
        padding-bottom: 34px;
        padding-right: 56px; /* leave room for the rank numeral pinned top-right — widest 2-digit rank measures ~51px, so this trims the excess reserved space */
        display: flex;
        flex-wrap: wrap;
        align-items: baseline;
        gap: 6px;
        text-decoration: none; /* was inheriting the desktop "click to filter" underline */
        color: var(--text);
        font-weight: 700;
        font-size: 1.05rem;
        line-height: 1.25;
    }
    /* Line-clamp lives on the nested span, not on .title-name itself: since
       title-cell/title-cell-inner are display: contents, .title-name is
       promoted to a direct CSS Grid item, and Chrome silently breaks
       -webkit-line-clamp on an element that is itself a grid item — the
       clamp's height math still applies, but it stops actually truncating
       the rendered text at 2 lines. The nested .title-name-text span is
       not itself a grid item, so the clamp works correctly on it. */
    .title-cell .title-name-text {
        display: -webkit-box;
        -webkit-line-clamp: 3; /* was 2 — a 3rd line shows meaningfully more of longer titles before truncating */
        -webkit-box-orient: vertical;
        overflow: hidden;
    }

    /* genre: plain, no "Genre:" prefix (dashboard.js already only ever
       emits the bare label) — sits directly under the title. */
    .genre-cell {
        grid-column: 2;
        grid-row: 2;
        justify-self: start;
        width: auto;
        max-width: none;
        margin: 0;
    }

    /* top cast: left-aligned under the genre, one name per line (same
       treatment as desktop's .cast-link stack), just smaller/muted to
       stay compact in the card. */
    .cast-cell {
        grid-column: 2;
        grid-row: 3;
        justify-self: start;
        width: auto;
        max-width: none;
        margin: 0;
        font-size: 0.8rem;
        color: var(--text-muted);
    }

    .weeks-cell {
        grid-column: 2;
        grid-row: 4;
        justify-self: start;
        width: auto;
        max-width: none;
        margin: 0;
    }

    /* footer row: the flexible spacer above (grid row 5) pushes this row to
       the card's bottom edge. Score bars sit bottom-left, streaming logo
       bottom-right — both share this last grid row. */
    .score-cell {
        grid-column: 2;
        grid-row: 6;
        justify-self: start;
        align-self: end;
        width: auto;
        max-width: none;
        margin: 0;
        display: flex;
        align-items: center;
    }
    /* mini play-button mark (cropped from the logo) as a small label icon
       sitting just to the left of the score bars */
    .score-cell::before {
        content: "";
        display: block;
        flex: none;
        width: 10px;
        height: 12px;
        margin-right: 4px;
        background-image: url("img/vidmired-icon.png");
        background-size: contain;
        background-repeat: no-repeat;
        background-position: center;
    }
    .stream-cell {
        grid-column: 2;
        grid-row: 6;
        justify-self: end;
        align-self: end;
        width: auto;
        max-width: none;
        margin: 0;
    }

    /* genre: no chip/token — bare uppercase black text, matching the
       title's color, click-to-filter still works via the same button. */
    .genre-cell .cell-filter-btn {
        display: inline-block;
        background: none;
        color: var(--text);
        text-decoration: none;
        font-size: 0.72rem;
        font-weight: 700;
        letter-spacing: 0.02em;
        text-transform: uppercase;
        padding: 0;
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .genre-cell .cell-filter-btn:hover { color: var(--accent); }

    .stream-cell .watch-logos { gap: 8px; justify-content: flex-end; }
    .stream-cell .watch-logo { height: 16px; max-width: 56px; }

    .detail-panel { padding: 0 14px; }
    .detail-summary { font-size: 0.85rem; }
}

/* ── My List button ──────────────────────────────────────────────────── */
.my-list-btn {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    color: var(--brand-purple);
}
.my-list-icon {
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition: fill 0.15s;
}
.my-list-btn.active .my-list-icon {
    fill: currentColor;
    stroke: currentColor;
}
.my-list-btn.active {
    border-color: #ffffff;
    box-shadow: 0 0 0 2px var(--brand-purple);
    color: var(--brand-purple);
}
.my-list-count {
    font-weight: 700;
    color: var(--brand-purple);
    font-size: 0.82rem;
}

/* ── Header actions (My List + account icon, top-right of header) ────── */
.header-actions {
    position: absolute;
    top: 10px;
    right: 0;
    display: flex;
    align-items: center;
    gap: 8px;
}

@media (max-width: 640px) {
    header.masthead .logo {
        height: 32px;
    }
}

/* ── Account button ──────────────────────────────────────────────────── */
.account-btn {
    font-family: inherit;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: auto;
    height: auto;
    aspect-ratio: 1;
    padding: 5px;
    border-radius: 50%;
    border: 1.5px solid var(--brand-purple);
    background: var(--card-bg);
    color: var(--brand-purple);
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
    flex: none;
}
.account-btn:hover {
    background: rgba(103, 39, 123, 0.08);
}
.account-btn.signed-in {
    background: rgba(103, 39, 123, 0.08);
}

/* ── Gray out all filters when My List is active ─────────────────────── */
.controls.my-list-active > * {
    opacity: 0.35;
    pointer-events: none;
    user-select: none;
}

/* ── Bookmark button (overlaid on card poster) ───────────────────────── */
.bookmark-btn {
    position: absolute;
    bottom: 10px;
    left: 10px;
    z-index: 3;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: none;
    background: rgba(255, 255, 255, 0.82);
    color: var(--text-muted);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    transition: background 0.15s, color 0.15s, transform 0.15s;
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
    box-shadow: 0 1px 4px rgba(0,0,0,0.12);
}
.bookmark-btn:hover {
    background: rgba(255, 255, 255, 0.96);
    color: var(--brand-purple);
    transform: scale(1.1);
}
.bookmark-btn.saved {
    background: var(--brand-purple);
    color: #fff;
}
.bookmark-btn.saved:hover {
    background: #7d3095;
    color: #fff;
    transform: scale(1.1);
}

@media (max-width: 640px) {
    .bookmark-btn {
        bottom: 14px;
        left: 14px;
        width: 28px;
        height: 28px;
    }
}

/* ── Auth modal ──────────────────────────────────────────────────────── */
.auth-overlay {
    position: fixed;
    inset: 0;
    z-index: 200;
    background: rgba(0, 0, 0, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
}
.auth-modal {
    position: relative;
    background: var(--card-bg);
    border-radius: 18px;
    box-shadow: 0 8px 40px rgba(0, 0, 0, 0.22);
    padding: 28px 28px 24px;
    width: 100%;
    max-width: 360px;
    max-height: calc(100vh - 40px);
    overflow-y: auto;
}
.auth-close {
    position: absolute;
    top: 14px;
    right: 14px;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: none;
    background: var(--bg);
    color: var(--text-muted);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
}
.auth-close:hover { background: var(--border); }

.auth-view { /* each sub-view inside the modal */ }
.auth-view + .auth-view { margin-top: 0; }

.auth-heading {
    margin: 0 0 4px;
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--text);
}
.auth-subheading {
    margin: 0 0 18px;
    font-size: 0.85rem;
    color: var(--text-muted);
    line-height: 1.4;
}
.auth-tabs {
    display: flex;
    gap: 0;
    border-bottom: 2px solid var(--border);
    margin-bottom: 18px;
}
.auth-tab {
    font-family: inherit;
    font-size: 0.88rem;
    font-weight: 600;
    color: var(--text-muted);
    background: none;
    border: none;
    padding: 6px 14px 8px;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
    transition: color 0.15s, border-color 0.15s;
}
.auth-tab.active {
    color: var(--brand-purple);
    border-bottom-color: var(--brand-purple);
}
.auth-tab:hover { color: var(--brand-purple); }
.auth-google-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    width: 100%;
    padding: 10px 14px;
    border: 1.5px solid var(--border);
    border-radius: 8px;
    background: var(--card-bg);
    font-family: inherit;
    font-size: 0.88rem;
    font-weight: 600;
    color: var(--text);
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s;
}
.auth-google-btn:hover { border-color: #c9c6bd; background: var(--bg); }
.auth-or {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 14px 0;
    font-size: 0.78rem;
    color: var(--text-muted);
}
.auth-or::before,
.auth-or::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.auth-input {
    display: block;
    width: 100%;
    padding: 10px 12px;
    margin-bottom: 10px;
    border: 1.5px solid var(--border);
    border-radius: 8px;
    font-family: inherit;
    font-size: 0.9rem;
    color: var(--text);
    background: var(--card-bg);
    outline: none;
    transition: border-color 0.15s;
    box-sizing: border-box;
}
.auth-input:focus { border-color: var(--brand-purple); }
.auth-submit {
    display: block;
    width: 100%;
    padding: 11px;
    margin-top: 4px;
    border: none;
    border-radius: 8px;
    background: var(--brand-purple);
    color: #fff;
    font-family: inherit;
    font-size: 0.92rem;
    font-weight: 700;
    cursor: pointer;
    transition: background 0.15s;
}
.auth-submit:hover { background: #7d3095; }
.auth-submit:disabled { opacity: 0.6; cursor: default; }
.auth-link-btn {
    display: block;
    width: 100%;
    margin-top: 12px;
    background: none;
    border: none;
    font-family: inherit;
    font-size: 0.83rem;
    color: var(--text-muted);
    cursor: pointer;
    text-align: center;
    text-decoration: underline;
    text-decoration-color: var(--border);
    text-underline-offset: 2px;
}
.auth-link-btn:hover { color: var(--brand-purple); }
.auth-back {
    display: inline-block;
    margin-bottom: 14px;
    background: none;
    border: none;
    font-family: inherit;
    font-size: 0.83rem;
    color: var(--text-muted);
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: var(--border);
    text-underline-offset: 2px;
}
.auth-back:hover { color: var(--brand-purple); }
.auth-err {
    font-size: 0.82rem;
    color: var(--accent);
    margin: -4px 0 8px;
}
.auth-ok {
    font-size: 0.82rem;
    color: #0a7c42;
    margin: -4px 0 8px;
}
.auth-avatar {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 52px;
    height: 52px;
    border-radius: 50%;
    background: rgba(103, 39, 123, 0.1);
    color: var(--brand-purple);
    margin: 0 auto 12px;
}
.auth-user-email {
    margin: 0 0 4px;
    font-size: 0.92rem;
    font-weight: 600;
    color: var(--text);
    text-align: center;
}
.auth-sync-ok {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    margin: 0 0 20px;
    font-size: 0.83rem;
    color: #0a7c42;
}
.auth-signout-btn {
    display: block;
    width: 100%;
    padding: 10px;
    border: 1.5px solid var(--border);
    border-radius: 8px;
    background: var(--card-bg);
    font-family: inherit;
    font-size: 0.88rem;
    color: var(--text-muted);
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s;
}
.auth-signout-btn:hover { border-color: var(--accent); color: var(--accent); }

/* ── Sync nudge toast ────────────────────────────────────────────────── */
.sync-nudge {
    position: fixed;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 150;
    display: flex;
    align-items: center;
    gap: 10px;
    background: var(--brand-purple);
    color: #fff;
    border-radius: 999px;
    padding: 10px 14px 10px 18px;
    font-size: 0.84rem;
    font-weight: 500;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.22);
    white-space: nowrap;
    max-width: calc(100vw - 32px);
}
.sync-nudge-text {
    white-space: normal;
}
.sync-nudge-cta {
    flex: none;
    background: rgba(255, 255, 255, 0.2);
    border: none;
    border-radius: 999px;
    padding: 5px 12px;
    font-family: inherit;
    font-size: 0.82rem;
    font-weight: 700;
    color: #fff;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.15s;
}
.sync-nudge-cta:hover { background: rgba(255, 255, 255, 0.32); }
.sync-nudge-dismiss {
    flex: none;
    background: none;
    border: none;
    color: rgba(255, 255, 255, 0.7);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2px;
}
.sync-nudge-dismiss:hover { color: #fff; }

@media (max-width: 640px) {
    .sync-nudge {
        bottom: 16px;
        border-radius: 14px;
        white-space: normal;
        text-align: center;
        padding: 12px 14px;
        flex-wrap: wrap;
        justify-content: center;
    }
    .sync-nudge-text { flex: 1 1 100%; }
}

/* Score tooltip */
.score-tooltip {
    position: fixed;
    z-index: 1000;
    pointer-events: none;
    max-width: 240px;
    padding: 8px 12px;
    border-radius: 8px;
    font-size: 0.8rem;
    line-height: 1.4;
    background: var(--card-bg, #1e1e1e);
    color: var(--text, #e8e8e8);
    border: 1px solid var(--border, rgba(255,255,255,0.1));
    box-shadow: 0 4px 16px rgba(0,0,0,0.4);
}
