:root {
    --void: #001a2d;
    --ink: #f7f9fb;
    --blood: #8b0000;
    --steel: #5f6e7c;
    --gold: #c9a634;
    --slate: #1a1a1a;
    --glow-blood: rgba(139,0,0,.55);
    --glow-gold: rgba(201,166,52,.55);
    --line: rgba(255,255,255,.06);
    /* A red bright enough to mark one value inside an already-red card,
       which --blood cannot do against itself. */
    --fault-edge: #ff4d4d;
}

html, body {
    height: 100%;
    margin: 0;
}

body {
    display: flex;
    flex-direction: column;
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background: linear-gradient(180deg, #002a45 0%, var(--void) 40%, var(--void) 70%, #000d16 100%);
    /* Anchor the gradient to the viewport so it stays put while the page
       scrolls, matching the fixed blueprint grid painted over it. Without
       this the gradient is sized to the full document and slides away on a
       long listing. */
    background-attachment: fixed;
    color: var(--ink);
}

.blueprint-grid {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-image:
        linear-gradient(to right, rgba(95,110,124,0.08) 1px, transparent 1px),
        linear-gradient(to bottom, rgba(95,110,124,0.08) 1px, transparent 1px);
    background-size: 20px 20px;
    pointer-events: none;
    z-index: -1;
}

/* ── Masthead ──────────────────────────────────────────────────────────────── */
/* The brand ribbon and the section rail beneath it are one instrument panel:
   the same void ground the library header sits on, divided by hairlines
   rather than by slabs, so the top of every page reads as a continuous strip
   down to the listing's own band. Gold appears in exactly two states here —
   where you are, and what is under the pointer — which is the same job it
   does in the readout and on the index rail. */
.navbar {
    background: var(--void);
    border-bottom: 1px solid var(--line);
    padding: 0.6rem 1.5rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}

.navbar-brand {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    text-decoration: none;
    color: var(--ink);
}

/* The mark, set to the cap height of the name beside it so the two read as one
   lockup rather than as an image with a caption. */
.navbar-brand-logo {
    display: block;
    width: 1.35rem;
    height: 1.35rem;
    flex: 0 0 auto;
}

.navbar-brand:hover,
.navbar-brand:focus {
    text-decoration: none;
}

.navbar-brand-text {
    display: flex;
    flex-direction: column;
}

/* Set in the same wide micro-caps as a page's own name, one step up in size
   and weight so the two read as the same voice at different volumes. */
/* The name is set in capitals here rather than written in them, so the
   configured value is the name as it would be written anywhere else. */
.navbar-brand-title {
    color: var(--ink);
    font-weight: 700;
    letter-spacing: 0.22em;
    font-size: 0.82rem;
    line-height: 1.4;
    text-transform: uppercase;
    transition: color 0.12s ease;
}

.navbar-brand:hover .navbar-brand-title {
    color: var(--gold);
}

/* What this installation is, under what it is called. Set a step down in size
   and weight and in the label colour, so it reads as the name's second line
   rather than as a second name — the same relationship a label has to a figure
   everywhere else here. */
.navbar-brand-subtitle {
    color: var(--steel);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-size: 0.6rem;
    font-weight: 600;
    line-height: 1.3;
}

.navbar .nav-link {
    color: var(--steel) !important;
    font-size: 0.8rem;
}

.navbar .nav-link:hover {
    color: var(--gold) !important;
}

/* The signed-in account. A username is an identifier a person recognises
   themselves by, so it is set in the face this application gives every
   identifier, and the control around it stays a hairline until it is reached
   for. */
.nav-user-btn {
    background: transparent;
    border: 1px solid var(--line);
    border-radius: 3px;
    color: var(--steel);
    padding: 0.24rem 0.7rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.76rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    cursor: pointer;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    line-height: 1.5;
    transition: border-color 0.12s ease, color 0.12s ease;
}

.nav-user-btn:hover,
.nav-user-btn[aria-expanded="true"] {
    border-color: var(--gold);
    color: var(--gold);
    text-decoration: none;
}

/* ── Section rail ──────────────────────────────────────────────────────────── */
/* The libraries this system manages, as tabs. Each is a plain label until it
   is the one you are on, and then it carries the gold underline this
   application already uses for a reading that is also somewhere to go — so
   "where you are" is marked the same way everywhere. */
.admin-subnav {
    background: var(--void);
    border-bottom: 1px solid var(--line);
    padding: 0 1.5rem;
    display: flex;
    align-items: stretch;
    gap: 1.5rem;
    flex-wrap: wrap;
}

.admin-subnav-label {
    display: inline-flex;
    align-items: center;
    color: var(--steel);
    font-weight: 600;
    font-size: 0.62rem;
    text-transform: uppercase;
    letter-spacing: 0.16em;
}

.admin-subnav-link {
    display: inline-flex;
    align-items: center;
    padding: 0.55rem 0;
    color: var(--steel);
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    text-decoration: none;
    box-shadow: inset 0 -2px 0 transparent;
    transition: color 0.12s ease, box-shadow 0.12s ease;
}

.admin-subnav-link:hover {
    color: var(--ink);
    box-shadow: inset 0 -2px 0 rgba(201, 166, 52, 0.45);
    text-decoration: none;
}

.admin-subnav-link.is-current {
    color: var(--gold);
    box-shadow: inset 0 -2px 0 var(--gold);
}

/* Dropdown */
.dropdown-menu {
    background: var(--slate) !important;
    border: 1px solid var(--line) !important;
}

.dropdown-item {
    color: var(--ink) !important;
}

.dropdown-item:hover,
.dropdown-item:focus {
    background: var(--void) !important;
    color: var(--gold) !important;
}

.dropdown-divider {
    border-color: var(--line) !important;
}

/* Main content */
.main-content {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem 1rem;
}

.main-content.dashboard-content {
    align-items: flex-start;
    padding-top: 2rem;
}

/* Login card */
.login-card {
    background: var(--slate);
    border: 1px solid var(--line);
    border-radius: 4px;
    width: 100%;
    max-width: 420px;
    box-shadow: 0 8px 30px rgba(0,0,0,0.4);
    color: var(--ink);
}

.login-header {
    text-align: center;
    padding: 2rem 2rem 1rem;
    border-bottom: 1px solid var(--line);
}

.login-header h1 {
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--ink);
    letter-spacing: 0.1em;
    margin: 0.5rem 0 0;
}

.login-header .subtitle {
    color: var(--steel);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.login-failed {
    max-width: 420px;
    background: var(--blood);
    border-left: 3px solid #ff4444;
    color: var(--ink);
}

.login-actions {
    display: flex;
    gap: 0.5rem;
}

/* Forms */
.form-group {
    margin-bottom: 1rem;
}

label, .form-label {
    display: block;
    font-size: 0.8rem;
    color: var(--steel);
    margin-bottom: 0.35rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
}

.form-control {
    display: block;
    width: 100%;
    padding: 0.6rem 0.75rem;
    font-size: 0.95rem;
    font-family: inherit;
    color: var(--ink) !important;
    background: var(--void) !important;
    border: 1px solid var(--steel) !important;
    border-radius: 3px;
    outline: none;
    box-sizing: border-box;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.form-control:focus {
    border-color: var(--gold) !important;
    box-shadow: 0 0 0 2px var(--glow-gold) !important;
}

.form-control::placeholder {
    color: var(--steel) !important;
    opacity: 0.6;
}

.form-control:disabled,
.form-control[readonly] {
    background: rgba(0,26,45,0.5) !important;
    color: var(--steel) !important;
    border-color: rgba(95,110,124,0.3) !important;
}

select.form-control {
    appearance: auto;
}

/* Buttons */
.btn-primary {
    display: block;
    width: 100%;
    padding: 0.7rem 1.5rem;
    font-family: inherit;
    font-size: 0.95rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--void) !important;
    background: var(--gold) !important;
    border: none !important;
    border-radius: 3px;
    cursor: pointer;
    transition: background 0.15s ease;
}

.btn-primary:hover,
.btn-primary:focus {
    background: #b8952e !important;
    color: var(--void) !important;
}

.btn-gold {
    background: var(--gold) !important;
    color: var(--void) !important;
    border: none !important;
    padding: 0.5rem 1.5rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-size: 0.9rem;
    border-radius: 3px;
    transition: background 0.15s ease;
}

.btn-gold:hover {
    background: #b8952e !important;
    color: var(--void) !important;
}

.btn-void {
    background: var(--void) !important;
    color: var(--ink) !important;
    border: 1px solid var(--steel) !important;
    padding: 0.5rem 1.5rem;
    font-weight: 600;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border-radius: 3px;
    transition: border-color 0.15s ease;
}

.btn-void:hover {
    border-color: var(--gold) !important;
    color: var(--gold) !important;
}

.btn-blood {
    background: var(--blood) !important;
    color: var(--ink) !important;
    border: none !important;
    padding: 0.5rem 1rem;
    font-weight: 600;
    border-radius: 3px;
}

.btn-blood:hover {
    background: #a00000 !important;
}

.btn-outline-secondary {
    color: var(--steel) !important;
    border-color: var(--steel) !important;
    background: transparent !important;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.btn-outline-secondary:hover {
    color: var(--ink) !important;
    border-color: var(--ink) !important;
    background: rgba(255,255,255,0.05) !important;
}

/* Panel cards */
.panel-card {
    background: var(--slate);
    border: 1px solid var(--line);
    border-radius: 4px;
    box-shadow: 0 8px 30px rgba(0,0,0,0.4);
    color: var(--ink);
    margin-bottom: 1.5rem;
    overflow: hidden;
}

.panel-card-header {
    padding: 1.25rem 1.5rem 1rem;
    border-bottom: 1px solid var(--line);
}

.panel-card-header h2 {
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--ink);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    margin: 0;
}

.panel-card-header .subtitle {
    color: var(--steel);
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.panel-card-body {
    padding: 1.5rem;
}

/* Tables */
.table {
    color: var(--ink);
}

.table thead th {
    background: var(--void);
    color: var(--ink);
    font-weight: 600;
    text-transform: uppercase;
    font-size: 0.75rem;
    letter-spacing: 0.05em;
    border: none;
}

.table td {
    vertical-align: middle;
    color: var(--ink);
    border-color: var(--line);
}

.table-striped tbody tr:nth-of-type(odd) {
    background: rgba(0,26,45,0.3);
}

/* Links */
a {
    color: var(--gold);
    transition: color 0.1s ease;
}

a:hover {
    color: #b8952e;
}

/* Alerts */
.alert-danger,
.alert-danger.alert-dismissible {
    background: var(--blood) !important;
    border-color: var(--blood) !important;
    color: var(--ink) !important;
    border-radius: 3px;
}

.alert-danger .close,
.alert-danger .close:hover {
    color: var(--ink) !important;
    opacity: 0.8;
    text-shadow: none;
}

.alert-error {
    background: var(--blood);
    border-left: 3px solid #ff4444;
    color: var(--ink);
    padding: 0.75rem 1rem;
    margin-bottom: 1rem;
    border-radius: 3px;
}

.alert-success,
.alert-success.alert-dismissible {
    background: rgba(26,122,26,0.4) !important;
    border-color: #1a7a1a !important;
    color: var(--ink) !important;
    border-radius: 3px;
}

.alert-success .close,
.alert-success .close:hover {
    color: var(--ink) !important;
    opacity: 0.8;
    text-shadow: none;
}

/* Toast */
.site-toast {
    position: fixed;
    top: 1rem;
    right: 1rem;
    padding: 0.75rem 1.5rem;
    border-radius: 4px;
    border-left: 3px solid;
    color: var(--ink);
    font-weight: 600;
    font-size: 0.9rem;
    z-index: 9999;
    opacity: 0;
    transition: opacity 0.3s ease;
    box-shadow: 0 4px 15px rgba(0,0,0,0.4);
}

/* Badges */
.badge {
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-size: 0.7rem;
}

/* Nav tabs (LDAP login) */
.nav-tabs {
    border-color: var(--line);
}

.nav-tabs .nav-link {
    color: var(--steel);
    border-color: transparent;
    font-size: 0.85rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.nav-tabs .nav-link:hover {
    color: var(--ink);
    border-color: transparent;
}

.nav-tabs .nav-link.active {
    background: var(--slate);
    border-color: var(--line) var(--line) var(--slate);
    color: var(--gold);
}

/* Action groups */
.action-group {
    display: flex;
    gap: 0.35rem;
    justify-content: flex-end;
}

/* Headings */
h1, h2, h3, h4, h5, h6 {
    color: var(--ink);
}

/* Utility */
.color-gold { color: var(--gold); }
.color-steel { color: var(--steel); }
.color-blood { color: var(--blood); }
.color-ink { color: var(--ink); }
.code-steel { color: var(--steel); font-size: 0.75rem; }

/* Footer */
.footer {
    background: var(--void);
    border-top: 2px solid var(--slate);
    padding: 0.75rem 1.5rem;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: 0.25rem 1rem;
    font-size: 0.75rem;
    flex-shrink: 0;
}

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

.footer a:hover {
    color: var(--gold);
}

.footer .copyright {
    color: var(--steel);
}

.footer-meta {
    display: flex;
    align-items: baseline;
}

.footer-meta-item {
    display: flex;
    align-items: baseline;
    margin-left: 1.5rem;
}

.footer-meta-label {
    color: var(--steel);
    text-transform: uppercase;
    font-size: 0.65rem;
    letter-spacing: 0.08em;
    font-weight: 600;
    margin-right: 0.4rem;
}

@media (max-width: 576px) {
    .login-card { margin: 0 0.5rem; }
    .navbar-brand-subtitle { display: none; }
    .footer {
        flex-direction: column;
        text-align: center;
        padding: 0.5rem 1rem;
    }
    .login-actions {
        flex-direction: column;
    }
    .login-actions .btn { width: 100%; }
}

/* ── Role badges ──────────────────────────────────────────────────────────── */
/* Split pill pairing a role name with its ENABLED / DISABLED state. Every
   role renders whether or not it is held, so the state half carries the
   colour: gold reads as granted, steel as withheld. */
.role-badge-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.role-badge {
    display: inline-flex;
    align-items: stretch;
    border: 1px solid var(--line);
    border-radius: 3px;
    overflow: hidden;
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.role-badge-name {
    padding: 0.3rem 0.65rem;
    background: var(--void);
    color: var(--ink);
}

.role-badge-state {
    padding: 0.3rem 0.65rem;
}

.role-badge-enabled .role-badge-state {
    background: var(--gold);
    color: var(--void);
}

.role-badge-disabled .role-badge-state {
    background: transparent;
    color: var(--steel);
}

/* ── Library header ────────────────────────────────────────────────────────── */
/* The first half of one continuous band. It shares the ground and the bleed
   of the controls directly beneath it and carries no rule of its own, so the
   two read as a single strip: this half scrolls away, the half below it
   pins. Given a rule and a margin instead, it floats between the subnav
   above and the controls below and belongs to neither. */
.library-header {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.35rem 1.1rem;
    background: var(--void);
    margin: 0 -1rem;
    padding: 0.85rem 1rem 0.45rem;
}

/* The subnav has already said which listing this is, so the page name is set
   as a quiet marker rather than a headline — present for the document's
   structure and for orientation, not repeating itself at display size. */
.library-name {
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--ink);
    margin: 0;
    white-space: nowrap;
}

/* The part of a page's name that is also the way back up from it — a season
   naming its show. Set as the heading it sits in and marked only when it is
   reached for, so the name reads as a name rather than as a control. */
.library-name-link {
    color: inherit;
    text-decoration: none;
}

.library-name-link:hover,
.library-name-link:focus {
    color: var(--gold);
    text-decoration: none;
    box-shadow: inset 0 -2px 0 var(--gold);
}

/* The state of the listing as one line. Figure first because the figure is
   what is read; the label follows it the way a unit follows a measurement. */
.library-readout {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.2rem 1.1rem;
    min-width: 0;
}

.library-stat {
    display: inline-flex;
    align-items: baseline;
    gap: 0.3rem;
    text-decoration: none;
    white-space: nowrap;
}

.library-stat-value {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1;
    color: var(--ink);
}

.library-stat-label {
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--steel);
}

/* Nothing wrong is good news here, so a zero steps back and lets the counts
   that are asking for something hold the line. */
.library-stat.is-zero .library-stat-value {
    color: var(--steel);
    font-weight: 400;
}

/* The one reading that is also somewhere to go. */
a.library-stat-action .library-stat-value {
    color: var(--gold);
    box-shadow: inset 0 -2px 0 rgba(201, 166, 52, 0.35);
}

a.library-stat-action:hover .library-stat-value {
    box-shadow: inset 0 -2px 0 var(--gold);
}

a.library-stat-action:hover .library-stat-label {
    color: var(--gold);
}

/* The concern being viewed. Marked in the accent like the links beside it,
   but without their underline, because it is not somewhere to go. */
.library-stat.is-current .library-stat-value,
.library-stat.is-current .library-stat-label {
    color: var(--gold);
}

/* A page with no control strip under it closes the band itself. */
.library-header-standalone {
    padding-bottom: 0.85rem;
    margin-bottom: 1.25rem;
    border-bottom: 1px solid var(--line);
}

.library-header-action {
    margin-left: auto;
}

/* ── Search and letter index ───────────────────────────────────────────────── */
/* Pinned to the top of the viewport while the listing scrolls: the control
   that moves you to another letter should never itself require scrolling to
   reach. It carries its own opaque ground because the page background is a
   fixed gradient that cards would otherwise show through as they pass under. */
.library-controls {
    position: sticky;
    top: 0;
    z-index: 20;
    background: var(--void);
    margin: 0 -1rem 1rem;
    padding: 0.55rem 1rem 0;
    border-bottom: 1px solid var(--line);
    box-shadow: 0 6px 14px -8px rgba(0, 0, 0, 0.85);
}

/* The search sits on the page rather than in a card, on the same hairline
   language as the header above it, so the cards below stay the only boxes. */
.library-search {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin-bottom: 0.75rem;
}

.library-search-label {
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--steel);
    margin: 0;
    white-space: nowrap;
}

.library-search-field {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
}

/* Room at the right edge of the field for the clearing control that sits
   inside it. */
.library-search-field .form-control {
    width: 100%;
    padding-right: 2.1rem;
}

/* Clearing a search happens in the field the search was typed into, so the
   icon rides inside the input and appears only once there is a term to drop.
   The browser's own cancel button is turned off to leave one control. */
.library-search-clear {
    position: absolute;
    top: 50%;
    right: 0.6rem;
    transform: translateY(-50%);
    line-height: 1;
    font-size: 0.78rem;
    color: var(--steel);
    transition: color 0.12s ease;
}

.library-search-clear:hover,
.library-search-clear:focus {
    color: var(--gold);
}

.library-search .form-control::-webkit-search-cancel-button {
    -webkit-appearance: none;
    appearance: none;
}

.library-search .btn {
    flex: 0 0 auto;
}

/* The index rail. Letters that lead nowhere in the current listing are still
   shown, so the alphabet keeps its shape and a gap reads as information —
   nothing is filed there — rather than as a missing control. */
.alpha-seek {
    display: flex;
    flex-wrap: wrap;
    gap: 0.1rem;
    padding-bottom: 0.7rem;
}

.alpha-seek-letter {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.78rem;
    font-weight: 600;
    line-height: 1;
    padding: 0.3rem 0.42rem;
    border-radius: 2px;
    text-decoration: none;
    color: var(--gold);
}

a.alpha-seek-letter:hover,
a.alpha-seek-letter:focus-visible {
    background: var(--gold);
    color: var(--void);
    text-decoration: none;
}

/* The letter currently being shown. Filled rather than tinted, because it is
   the one piece of state the listing depends on. */
a.alpha-seek-letter.is-active {
    background: var(--gold);
    color: var(--void);
}

.alpha-seek-letter.is-empty {
    color: var(--steel);
    opacity: 0.4;
}

.navbar-brand:focus-visible,
.nav-user-btn:focus-visible,
.admin-subnav-link:focus-visible,
a.library-stat-action:focus-visible,
a.alpha-seek-letter:focus-visible,
.library-search .form-control:focus-visible,
.library-search-clear:focus-visible,
.library-card-controls .btn:focus-visible,
.queue-row-controls .btn:focus-visible,
a.queue-row-title:focus-visible,
.episode-row-queue:focus-visible,
.library-card-poster-zoom:focus-visible,
a.library-reconcile-title:focus-visible,
.library-card-billing-name a:focus-visible,
.library-card-cast-name a:focus-visible,
.library-name-link:focus-visible,
.season-card-open:focus-visible,
button.season-card-directory:focus-visible,
button.season-card-number:focus-visible,
button.episode-row-name:focus-visible,
button.episode-row-number:focus-visible,
.library-reconcile-id a:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
}

.library-card-issue .library-card-controls .btn:focus-visible,
.library-card-issue .library-reconcile-id a:focus-visible {
    outline-color: var(--ink);
}

/* Nothing here is load-bearing motion — the transitions are hover feedback
   only — so they come off entirely when the viewer has asked for that. */
@media (prefers-reduced-motion: reduce) {
    .library-card,
    .episode-row,
    .library-stat,
    a.alpha-seek-letter,
    .navbar-brand-title,
    .nav-user-btn,
    .admin-subnav-link {
        transition: none;
    }
}

/* ── Library cards ─────────────────────────────────────────────────────────── */
/* One card per entry, stacked one per row, in both the movies and the shows
   listing. Each card is a reconciliation: the archive's reading of a title on
   the first line, IMDb's on the second, sharing one grid so title and year
   align column for column. A card that disagrees with itself shows it as a
   broken column before it shows it as words, which is the only thing these
   listings exist to help someone do.

   Tighter than a stock panel-card, because the listing runs to thousands of
   rows and the vertical rhythm has to hold across all of them. */
.library-card {
    background: var(--slate);
    border: 1px solid var(--line);
    border-left: 3px solid transparent;
    border-radius: 3px;
    margin-bottom: 0.6rem;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.library-card:hover {
    border-left-color: var(--gold);
    background: #1f1f1f;
}

.library-card-body {
    display: flex;
    align-items: flex-start;
    gap: 1.1rem;
    padding: 0.9rem 1.1rem;
}

/* Poster column. The image is sized rather than the column, so the column
   collapses to whatever the artwork needs and the detail column takes the
   rest. The hairline frame reads as a mounted still rather than a raw image
   dropped into a list. */
.library-card-poster {
    flex: 0 0 auto;
}

.library-card-poster-image,
.library-card-poster-empty {
    width: 4.25rem;
    border-radius: 2px;
    display: block;
    box-shadow: 0 0 0 1px rgba(201, 166, 52, 0.25), 0 2px 8px rgba(0, 0, 0, 0.45);
}

.library-card-poster-image {
    height: auto;
    background: var(--void);
}

/* Stand-in when there is no artwork. Proportioned to a poster so a card
   without one keeps the same shape as its neighbours. */
.library-card-poster-empty {
    aspect-ratio: 2 / 3;
    background: var(--void);
    color: var(--steel);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.2rem;
    box-shadow: inset 0 0 0 1px var(--line);
}

.library-card-detail {
    flex: 1 1 auto;
    min-width: 0;
}

/* Four columns shared by both readings: the side label, the title, the year,
   and IMDb's id. Years and ids are set in the monospace face so the rows
   align to the character and a mismatch registers as a jump. */
.library-reconcile {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr) max-content max-content;
    align-items: baseline;
    column-gap: 0.9rem;
    row-gap: 0.15rem;
}

/* Switching a dialog between its two answers, set as a line of type at the far
   end of the footer so it does not compete with the control that carries the
   dialog out. */
.move-into-show-mode {
    margin-right: auto;
    padding: 0;
    border: 0;
    background: none;
    color: var(--gold);
    font: inherit;
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    cursor: pointer;
}

.move-into-show-mode:hover {
    text-decoration: underline;
}

.move-into-show-mode:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
}

/* ── Taking the name from IMDb ─────────────────────────────────────────────── */
/* A control inside the rename dialog, set as a line of type rather than as a
   second button competing with the one that carries the rename out. What it
   would fill in is named beside it, so nobody has to press it to find out. */
.edit-entry-adopt {
    margin-top: 0.9rem;
    padding: 0;
    border: 0;
    background: none;
    color: var(--gold);
    font: inherit;
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    cursor: pointer;
}

.edit-entry-adopt:hover:not(:disabled) {
    text-decoration: underline;
}

.edit-entry-adopt:disabled {
    color: var(--steel);
    cursor: default;
}

.edit-entry-adopt:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
}

/* What the control would put in the fields. */
.edit-entry-adopt-note {
    display: block;
    margin-top: 0.25rem;
    font-size: 0.78rem;
    color: var(--steel);
}

.edit-entry-adopt-note:empty {
    display: none;
}

/* IMDb's title is also the way to IMDb's page for it. It stays the reading it
   is — same size, same accent — and is marked only when it is reached for, so
   the row reads as two readings rather than as a control. */
a.library-reconcile-title {
    text-decoration: none;
}

a.library-reconcile-title:hover,
a.library-reconcile-title:focus {
    color: var(--gold);
    text-decoration: none;
    box-shadow: inset 0 -2px 0 var(--gold);
}

.library-card-issue a.library-reconcile-title:hover,
.library-card-issue a.library-reconcile-title:focus {
    color: var(--ink);
    box-shadow: inset 0 -2px 0 var(--ink);
}

/* Names which reading a row is. Kept quiet — it should never compete with
   the reading itself. */
.library-reconcile-side {
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--steel);
    white-space: nowrap;
    padding-top: 0.35rem;
}

/* The archive's title is the card's headline as well as the top half of the
   comparison, so it carries the weight for both jobs. */
.library-card-title {
    font-size: 1.2rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.25;
    color: var(--ink);
    margin: 0;
    min-width: 0;
    word-break: break-word;
}

/* IMDb's reading of the same film, set smaller and in the accent so the two
   sources stay distinguishable while scanning a long listing. */
.library-reconcile-title {
    font-size: 0.9rem;
    color: var(--gold);
    min-width: 0;
    word-break: break-word;
}

.library-reconcile-year,
.library-reconcile-id {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* The archive's year shares the headline row, so it is set to match it. */
.library-card-title + .library-reconcile-year {
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--ink);
}

/* IMDb's year follows IMDb's title, and takes the accent with it. */
.library-reconcile-title + .library-reconcile-year {
    font-size: 0.9rem;
    font-weight: 400;
    color: var(--gold);
}

.library-reconcile-id {
    font-size: 0.8rem;
    letter-spacing: 0.02em;
}

.library-reconcile-id a {
    color: var(--steel);
}

.library-reconcile-id a:hover {
    color: var(--gold);
}

/* A value neither side has. Dimmed rather than marked: an absence is not
   the same thing as a disagreement. */
.library-reconcile .is-absent {
    color: var(--steel);
    font-weight: 400;
}

/* A value contradicting the row above it. This is the mark the whole layout
   exists to deliver, so it is the one place colour is spent inside an
   otherwise quiet card. */
.library-reconcile .is-mismatch {
    color: var(--ink);
    box-shadow: inset 0 -2px 0 var(--fault-edge);
}

/* The shape of a series — how many seasons and episodes, how long it ran, how
   it is rated and filed. Read as one line of facts rather than as a set of
   fields, so the parts are separated by spacing alone and the seasons figure,
   which is the one being checked against a directory of season folders, is
   the only part set in the face this application gives its figures. */
.library-card-shape {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.15rem 0.9rem;
    margin-top: 0.5rem;
    font-size: 0.75rem;
    color: var(--steel);
}

.library-card-shape-figure {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
}

/* The seasons the archive does not hold, marked inside the line rather than
   set apart from it: the reader is already looking at the season figure, and
   what is absent belongs beside what is present. */
.library-card-shape-fault {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    color: var(--fault-edge);
}

.library-card-issue .library-card-shape {
    color: rgba(255, 255, 255, 0.72);
}

.library-card-issue .library-card-shape-fault {
    color: var(--ink);
    box-shadow: inset 0 -2px 0 var(--ink);
}

.library-card-issue .library-card-shape-figure {
    color: var(--ink);
}

/* Square hairline labels rather than filled pills: several appear at once on
   a bad card, and filled shapes at that density read as noise. */
.library-card-badges {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.35rem;
    margin-top: 0.55rem;
}

.library-card-badges:empty {
    display: none;
}

.library-flag {
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    padding: 0.15rem 0.45rem;
    border: 1px solid currentColor;
    border-radius: 2px;
    white-space: nowrap;
}

.library-flag-fault {
    color: var(--fault-edge);
}

.library-flag-warn {
    color: var(--gold);
}

.library-flag-gone {
    color: var(--steel);
}

/* The performers IMDb ships with the title. Kept in its own quieter register
   above the collected billing, because the two are different depths of the
   same answer and a card should still show which came with the link. Every
   name is a search for that performer within this listing. */
.library-card-cast {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.1rem 0.3rem;
    list-style: none;
    margin: 0.6rem 0 0;
    padding: 0;
    font-size: 0.8rem;
    color: var(--steel);
}

.library-card-cast-name {
    white-space: nowrap;
}

.library-card-cast-name:not(:last-child)::after {
    content: ',';
}

.library-card-cast-name a {
    color: inherit;
    text-decoration: none;
}

.library-card-cast-name a:hover,
.library-card-cast-name a:focus {
    color: var(--gold);
    text-decoration: underline;
}

.library-card-issue .library-card-cast-name a:hover,
.library-card-issue .library-card-cast-name a:focus {
    color: var(--ink);
}

/* The collected billing, running on beneath the names IMDb ships with the
   title. Set in the accent the IMDb row above it uses, because it is the same
   source answering about the same title — and read as a cast list, comma to
   comma, rather than as a rank of separate items. Every name is a search for
   that performer within this listing. */
.library-card-billing {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.1rem 0.3rem;
    list-style: none;
    margin: 0.35rem 0 0;
    padding: 0;
}

.library-card-billing-name {
    font-size: 0.78rem;
    white-space: nowrap;
}

.library-card-billing-name:not(:last-child)::after {
    content: ',';
    color: var(--steel);
}

.library-card-billing-name a {
    color: var(--gold);
    text-decoration: none;
}

.library-card-billing-name a:hover,
.library-card-billing-name a:focus {
    color: var(--gold);
    text-decoration: underline;
}

/* On a flagged card the palette is forced to white, so the accent that
   distinguishes these names elsewhere would disappear into the red. */
.library-card-issue .library-card-billing-name a {
    color: var(--ink);
}

.library-card-issue .library-card-billing-name:not(:last-child)::after {
    color: rgba(255, 255, 255, 0.72);
}

/* The synopsis, closing the card. Larger and looser than everything above
   it: those lines are compared, this one is read. Held to a comfortable
   measure so it does not run the full width of a wide screen. */
.library-card-description {
    margin: 0.6rem 0 0;
    padding-top: 0.6rem;
    border-top: 1px solid var(--line);
    max-width: 68ch;
    font-size: 0.95rem;
    line-height: 1.55;
    color: var(--ink);
}

/* ── Movie card: needs attention ───────────────────────────────────────────── */
/* A card whose entry has any coherency problem — a title or year that
   disagrees with IMDb, a link that resolves to nothing, a missing link, a
   misnamed directory, a directory that is gone — turns red so the listing
   can be scanned for trouble rather than read. Everything inside it is
   forced to white, because the normal palette puts gold and steel on a
   dark ground and neither survives being moved onto red. */
.library-card-issue {
    background: var(--blood);
    border-color: var(--fault-edge);
    border-left-color: var(--fault-edge);
    color: var(--ink);
}

.library-card-issue:hover {
    background: #9b1414;
    border-left-color: var(--ink);
}

.library-card-issue .library-card-title,
.library-card-issue .library-reconcile-title,
.library-card-issue .library-reconcile-year,
.library-card-issue .library-card-title + .library-reconcile-year,
.library-card-issue .library-reconcile-title + .library-reconcile-year,
.library-card-issue .library-card-description,
.library-card-issue .color-steel {
    color: var(--ink);
}

.library-card-issue .library-reconcile-side,
.library-card-issue .library-card-cast,
.library-card-issue .library-reconcile .is-absent,
.library-card-issue .library-reconcile-id a {
    color: rgba(255, 255, 255, 0.72);
}

.library-card-issue .library-reconcile-id a:hover {
    color: var(--ink);
}

.library-card-issue .library-reconcile .is-mismatch {
    box-shadow: inset 0 -2px 0 var(--ink);
}

.library-card-issue .library-card-description {
    border-top-color: rgba(255, 255, 255, 0.28);
}

.library-card-issue .library-flag {
    color: var(--ink);
}

.library-card-issue .library-card-poster-image,
.library-card-issue .library-card-poster-empty {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.35), 0 2px 8px rgba(0, 0, 0, 0.45);
}

/* The blood-coloured delete button disappears against a blood-coloured
   card, so on a flagged card every control is outlined in white instead. */
.library-card-issue .btn-void,
.library-card-issue .btn-blood {
    background: transparent !important;
    border: 1px solid rgba(255, 255, 255, 0.55) !important;
    color: var(--ink) !important;
}

.library-card-issue .btn-void:hover,
.library-card-issue .btn-blood:hover {
    background: var(--ink) !important;
    color: var(--blood) !important;
    border-color: var(--ink) !important;
}

/* ── Season cards ──────────────────────────────────────────────────────────── */
/* One row per season of one show. Shorter than a library card and set to one
   line, because a page of seasons is read down the left edge as a sequence:
   the number leads in the figure face, what is in that season follows, and
   anything wrong with it is marked at the right. */
.season-card {
    display: flex;
    align-items: baseline;
    gap: 1.1rem;
    background: var(--slate);
    border: 1px solid var(--line);
    border-left: 3px solid transparent;
    border-radius: 3px;
    margin-bottom: 0.35rem;
    padding: 0.55rem 1.1rem;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.season-card:hover {
    border-left-color: var(--gold);
    background: #1f1f1f;
}

/* The number is also the control that renames the directory, since the number
   is what a wrongly-named one gets wrong. It stays a plain figure until it is
   reached for, so the row reads as a sequence rather than as a rank of
   buttons. */
.season-card-number {
    flex: 0 0 auto;
    min-width: 6.5rem;
    padding: 0;
    border: 0;
    background: none;
    text-align: left;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.92rem;
    font-weight: 600;
    color: var(--ink);
    transition: color 0.12s ease, box-shadow 0.12s ease;
}

button.season-card-number {
    cursor: pointer;
    box-shadow: inset 0 -2px 0 transparent;
}

button.season-card-number:hover,
button.season-card-number:focus {
    color: var(--gold);
    box-shadow: inset 0 -2px 0 var(--gold);
    outline: none;
}

/* The rest of the row opens the season. A link rather than a control, because
   it only shows what is already there. */
.season-card-detail {
    flex: 1 1 auto;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.15rem 0.9rem;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

a.season-card-detail:hover,
a.season-card-detail:focus {
    text-decoration: none;
}

a.season-card-detail:hover .season-card-episodes {
    text-decoration: underline;
}

/* The directory as it exists, shown where it says more than the number does.
   Set in the figure face because it is a string being checked character by
   character when something about it is wrong. */
.season-card-directory {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.76rem;
    color: var(--steel);
    word-break: break-word;
}

.season-card-episodes {
    font-size: 0.8rem;
    color: var(--gold);
    white-space: nowrap;
}

/* The episodes the season is short of, named beside what it holds. */
.season-card-fault-note {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.76rem;
    color: var(--fault-edge);
}

.season-card-state {
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--steel);
    white-space: nowrap;
}

/* A season the archive does not have. Held to an outline rather than filled,
   because the row stands for something absent and should not carry the weight
   of the seasons that are there. */
.season-card-missing {
    background: transparent;
    border-style: dashed;
}

.season-card-missing:hover {
    background: transparent;
    border-left-color: transparent;
}

.season-card-missing .season-card-number {
    color: var(--steel);
}

/* A season that is there and wrong: a directory named outside the convention,
   or one holding no episodes. */
.season-card-fault {
    border-left-color: var(--fault-edge);
}

/* Something in the show's directory that is not a season at all. The first
   column holds the icon saying which kind of thing it is, where a season
   would put its number. The whole card renames it, because renaming is the
   only thing to be done with something whose name is the problem. */
/* A directory that is not a season opens into a listing of what is in it. The
   card is the target and its controls take their own clicks, so the pointer
   changes over the card rather than over the whole row of buttons. */
.season-card-open {
    cursor: pointer;
}

.season-card-open:hover {
    border-left-color: var(--gold);
    background: #1f1f1f;
}

/* The name renames the thing; it is the one part of a card that is a control
   in its own right whether or not the card opens anything. */
.season-card-directory {
    padding: 0;
    border: 0;
    background: none;
    text-align: left;
}

button.season-card-directory {
    cursor: pointer;
}

button.season-card-directory:hover,
button.season-card-directory:focus {
    color: var(--gold);
    text-decoration: underline;
    outline: none;
}

/* Controls sitting inside a card, holding their own ground rather than
   inheriting the card's click. */
.season-card-controls {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 0.3rem;
}

.season-card-move,
.season-card-promote,
.season-card-queue,
.season-card-report,
.season-card-controls .btn {
    flex: 0 0 auto;
    font-size: 0.7rem;
    padding: 0.2rem 0.6rem;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

/* A season already lined up. Inert because what it would do is done, so it
   reads as a state rather than as an action taken away. */
.season-card-queue.is-queued {
    border-color: rgba(201, 166, 52, 0.5) !important;
    color: var(--gold) !important;
    opacity: 1;
}

.season-card-queue.is-queued:disabled {
    opacity: 1;
    pointer-events: none;
}

.season-card-stray .season-card-number {
    min-width: 2rem;
    color: var(--steel);
}

@media (max-width: 575px) {
    .season-card {
        flex-wrap: wrap;
        gap: 0.3rem 1.1rem;
    }
    .season-card-number {
        min-width: 0;
    }
}

/* ── One season's episodes ─────────────────────────────────────────────────── */
/* The files as they are named on disk, beside what they weigh. Set as rows
   rather than cards: this is a directory listing, and it is read against the
   file browser it mirrors, so the filename carries the whole line in the
   figure face and nothing competes with it. */
.season-directory-note {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.6rem;
    margin: 0 0 1rem;
}

/* An episode is a card, on the same slate ground and the same hairline frame
   every other record in this application is drawn on, with the left edge
   carrying its state. A season sits inside a show sits inside a library, and
   a reader moving down through them meets one shape at every level. */
.episode-row {
    display: flex;
    align-items: stretch;
    background: var(--slate);
    border: 1px solid var(--line);
    border-left: 3px solid transparent;
    border-radius: 3px;
    margin-bottom: 0.35rem;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.episode-row:hover {
    border-left-color: var(--gold);
    background: #1f1f1f;
}

/* The column the season is read down. It holds the episode as a bare figure
   and is ruled off from the rest of the card, so running an eye down the page
   gives the shape of the season before a word of it is read: filled where the
   archive holds the episode, hollow where it does not.

   Right-aligned, in the tabular face, so every figure lands on the same
   column and the sequence reads as a sequence. */
.episode-row-number {
    flex: 0 0 3.6rem;
    display: flex;
    align-items: flex-start;
    justify-content: flex-end;
    padding: 0.6rem 0.8rem 0.6rem 0.6rem;
    border: 0;
    border-right: 1px solid var(--line);
    background: none;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1.4;
    color: var(--ink);
}

.episode-row-number.is-absent {
    color: var(--steel);
    font-weight: 400;
}

/* Everything the row says about one episode, stacked: what IMDb calls it,
   the file delivering it, and what it is about. The column carries the width
   so the number leads and the size closes, the same shape the row had when
   the filename was all there was to say. */
.episode-row-body {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.55rem 0.95rem;
}

/* What the episode is, and the few facts worth having beside its name. Set on
   one line so a season reads down its titles, with the facts trailing in the
   quieter weight the rest of the application uses for them. */
.episode-row-heading {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.9rem;
    line-height: 1.4;
    margin-bottom: 0.1rem;
}

.episode-row-title {
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--ink);
}

.episode-row-fact {
    font-size: 0.72rem;
    font-variant-numeric: tabular-nums;
    color: var(--steel);
    white-space: nowrap;
}

.episode-row-rating {
    color: var(--gold);
}

/* What the episode is about, held to three lines: a season page is read for
   its sequence, and a full synopsis on every row would bury it.
   Dimmed from the card's own ink rather than set in the label colour, because
   this is the one line on the row meant to be read as prose. */
.episode-row-plot {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin: 0.2rem 0 0.1rem;
    max-width: 78ch;
    font-size: 0.78rem;
    line-height: 1.45;
    color: rgba(247, 249, 251, 0.68);
}

/* The filename is also the control that renames it, the way a season number
   is the control that renames its directory. It stays plain text until it is
   reached for, so a directory listing reads as a listing. */
.episode-row-name {
    display: block;
    width: 100%;
    min-width: 0;
    padding: 0;
    border: 0;
    background: none;
    text-align: left;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.8rem;
    line-height: 1.4;
    color: var(--ink);
    word-break: break-all;
}

button.episode-row-name {
    cursor: pointer;
}

button.episode-row-name:hover,
button.episode-row-name:focus {
    color: var(--gold);
    text-decoration: underline;
    outline: none;
}

/* The gutter of an unpaired file, which is what pairs it. One click stages the
   episode it would become and shows it here; the staged value is set in the
   accent so what is about to happen is legible before it happens. */
button.episode-row-number {
    cursor: pointer;
}

button.episode-row-pair:hover,
button.episode-row-pair:focus {
    color: var(--gold);
    outline: none;
}

/* A staged value is the whole name the file is about to take — longer than the
   figure the column is cut for, so it is set down a step to sit inside it. */
button.episode-row-pair.is-staged {
    color: var(--gold);
    font-size: 0.78rem;
    font-weight: 600;
}

/* The extension a rename leaves alone, shown beside the field holding the
   name so it is clear what is being edited and what is not. */
.episode-rename-field {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.episode-rename-field .form-control {
    flex: 1 1 auto;
    min-width: 0;
}

.episode-rename-extension {
    flex: 0 0 auto;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.85rem;
    color: var(--steel);
}

/* A file that will not say which episode it is, or one answering for no
   episode this season runs to. Marked on its left edge, the same mark a
   season card carries for the same reason — and the mark holds under the
   pointer, because hovering a fault does not resolve it. */
.episode-row-fault {
    border-left-color: var(--fault-edge);
}

.episode-row-fault:hover {
    border-left-color: var(--fault-edge);
}

.episode-row-fault .episode-row-name {
    color: var(--steel);
}

/* The files answering for no slot, set off from the sequence above them
   because they are not part of it. */
.episode-group-heading {
    margin: 1.5rem 0 0.5rem;
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--steel);
}

/* An episode IMDb lists that no file claims to be. Held to an outline, the
   way a missing season is, because the row stands for something absent — the
   card is a space where one should be rather than a record of one. It does
   not answer the pointer, since there is nothing here to act on. */
.episode-row-missing {
    background: transparent;
    border-style: dashed;
}

.episode-row-missing:hover {
    background: transparent;
    border-left-color: transparent;
}

.episode-row-missing .episode-row-number {
    border-right-style: dashed;
}

.episode-row-missing .episode-row-number,
.episode-row-missing .episode-row-name {
    color: var(--steel);
    font-weight: 400;
}

/* What the file weighs, closing the row. Held to the top so it lands on the
   title's line however far the card runs beneath it. */
.episode-row-size {
    flex: 0 0 auto;
    align-self: flex-start;
    padding: 0.6rem 0.95rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.76rem;
    line-height: 1.4;
    color: var(--steel);
    white-space: nowrap;
}

.episode-row-size.is-absent {
    opacity: 0.5;
}

/* A row's flags sit on the title's line beside the size, rather than being
   stretched down a card that may run three lines deep. */
.episode-row > .library-flag {
    align-self: flex-start;
    margin: 0.66rem 0 0.66rem 0.6rem;
}

/* On a narrow screen the column narrows with the card rather than being given
   up: it is the one part of the row a reader is running an eye down. */
@media (max-width: 575px) {
    .episode-row-number {
        flex-basis: 2.9rem;
        padding: 0.6rem 0.55rem;
        font-size: 0.85rem;
    }
    .episode-row-body {
        padding: 0.55rem 0.7rem;
    }
    .episode-row-size {
        padding: 0.6rem 0.7rem;
    }
}

/* ── Queue ─────────────────────────────────────────────────────────────────── */
/* One row per thing the reader has lined up, on the same card language the
   libraries and the seasons use. The place in the queue leads it in a column
   of its own — here the number is the content rather than a label on it, since
   the whole point of the page is the order, and the two controls that change
   that order sit at the far end of the row where every list in this
   application puts what can be done with a thing. */
.queue-row {
    display: flex;
    align-items: stretch;
    background: var(--slate);
    border: 1px solid var(--line);
    border-left: 3px solid transparent;
    border-radius: 3px;
    margin-bottom: 0.35rem;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.queue-row:hover {
    border-left-color: var(--gold);
    background: #1f1f1f;
}

.queue-row-position {
    flex: 0 0 3.6rem;
    display: flex;
    align-items: flex-start;
    justify-content: flex-end;
    padding: 0.7rem 0.8rem 0.7rem 0.6rem;
    border-right: 1px solid var(--line);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1.4;
    color: var(--ink);
}

/* Artwork at the size a list is scanned at, framed the way a card's poster is
   so the two read as the same object at different scales. */
.queue-row-art {
    flex: 0 0 auto;
    padding: 0.6rem 0 0.6rem 0.85rem;
}

.queue-row-poster,
.queue-row-poster-empty {
    width: 3.25rem;
    border-radius: 2px;
    display: block;
    box-shadow: 0 0 0 1px rgba(201, 166, 52, 0.25), 0 2px 8px rgba(0, 0, 0, 0.45);
}

.queue-row-poster {
    height: auto;
    background: var(--void);
}

.queue-row-poster-empty {
    aspect-ratio: 2 / 3;
    background: var(--void);
    color: var(--steel);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.8rem;
    box-shadow: inset 0 0 0 1px var(--line);
}

.queue-row-body {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.65rem 0.9rem;
}

.queue-row-heading {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.8rem;
    line-height: 1.4;
}

.queue-row-title {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--ink);
    text-decoration: none;
    word-break: break-word;
}

a.queue-row-title:hover,
a.queue-row-title:focus {
    color: var(--gold);
    text-decoration: none;
    box-shadow: inset 0 -2px 0 var(--gold);
}

.queue-row-fact {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.78rem;
    color: var(--steel);
    white-space: nowrap;
}

/* Which episode of which season, in the form this archive writes them in, so
   the string on the row is the string on the file. */
.queue-row-episode {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.78rem;
    color: var(--gold);
    white-space: nowrap;
}

.queue-row-episode-title {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.85rem;
    font-weight: 600;
    line-height: 1.4;
    color: var(--ink);
}

/* What IMDb knows about the queued thing, on one line under its name: an
   episode's own facts, or the names a film is identified by. */
.queue-row-facts {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.85rem;
    margin-top: 0.2rem;
    line-height: 1.4;
}

.queue-row-rating {
    color: var(--gold);
}

.queue-row-cast {
    font-size: 0.78rem;
    color: var(--steel);
    min-width: 0;
}

/* The synopsis, closing the row. Held to two lines and a comfortable measure:
   a queue is read as a list of what is coming next, and a full synopsis on
   every row would turn it into a page of prose. */
.queue-row-description {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin: 0.3rem 0 0;
    max-width: 78ch;
    font-size: 0.8rem;
    line-height: 1.45;
    color: rgba(247, 249, 251, 0.68);
}

.queue-row-controls {
    flex: 0 0 auto;
    display: flex;
    align-items: flex-start;
    gap: 0.3rem;
    padding: 0.6rem 0.85rem 0.6rem 0;
}

.queue-row-controls .btn {
    font-size: 0.7rem;
    padding: 0.25rem 0.6rem;
    letter-spacing: 0.04em;
}

/* The two that change the order. Square, because an arrow is the whole label
   and a word beside it would say the same thing twice. */
.queue-row-step {
    min-width: 2rem;
    text-align: center;
}

.queue-row-controls .btn:disabled {
    opacity: 0.35;
}

/* An entry naming something the archive no longer answers for. Marked the way
   every other fault in this application is, and left in place: it is a list
   the reader built, and only the reader takes something out of it. */
.queue-row-fault {
    border-left-color: var(--fault-edge);
}

.queue-row-fault:hover {
    border-left-color: var(--fault-edge);
}

.queue-row-fault .queue-row-title {
    color: var(--fault-edge);
}

/* Something already on the queue. The control is inert because the thing it
   would do is done, so it reads as a state rather than as an action that has
   been taken away. */
.library-card-queue.is-queued,
.episode-row-queue.is-queued {
    border-color: rgba(201, 166, 52, 0.5) !important;
    color: var(--gold) !important;
    opacity: 1;
}

.library-card-queue.is-queued:disabled,
.episode-row-queue.is-queued:disabled {
    opacity: 1;
    pointer-events: none;
}

.library-card-issue .library-card-queue.is-queued {
    border-color: rgba(255, 255, 255, 0.55) !important;
    color: var(--ink) !important;
}

/* What can be done with one episode, stacked at the end of its row the way
   every other card in this application stacks its controls. Held to the top so
   the column starts on the title's line however far the card runs beneath it. */
.episode-row-controls {
    flex: 0 0 auto;
    align-self: flex-start;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.25rem;
    padding: 0.5rem 0.85rem 0.5rem 0;
    min-width: 9.5rem;
}

/* Inside the column each control is a full-width row of it, so the labels line
   up down the left edge rather than each button being its own width. */
.episode-row-controls .btn {
    margin: 0 !important;
    width: 100%;
    text-align: left;
}

@media (max-width: 575px) {
    .episode-row-controls {
        min-width: 0;
        padding: 0.5rem 0.7rem 0.5rem 0;
    }
}

/* The queue control closes an episode row, held to the title's line so it does
   not stretch down a card that runs three lines deep. */
.episode-row-queue {
    flex: 0 0 auto;
    align-self: flex-start;
    margin: 0.5rem 0.85rem 0.5rem 0;
    font-size: 0.7rem !important;
    padding: 0.2rem 0.6rem !important;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

/* A slot with no file behind it keeps the control and greys it, so the row
   holds the same shape as the ones around it and the absence reads as this
   episode not being here rather than as a missing feature. */
.episode-row-queue:disabled {
    opacity: 0.35;
    cursor: default;
}

@media (max-width: 767px) {
    .queue-row-art {
        display: none;
    }
    .queue-row-controls {
        flex-wrap: wrap;
        justify-content: flex-end;
    }
}

@media (prefers-reduced-motion: reduce) {
    .queue-row {
        transition: none;
    }
}

/* ── Dashboard ─────────────────────────────────────────────────────────────── */
/* The archive's condition, read the way you would hold a print up to the
   light. Two lengths of film across the top, then one sheet of readings — no
   panels, no boxes: the page is a single surface, and what divides it is the
   hairline this application divides everything with.
   The two gauges are the page's argument. They are drawn to the same width and
   they say very different things — one measures how much of the catalogue is
   described, the other how much of the television is actually there — and that
   difference is what the archive's keeper needs to see on arrival. */
/* The page is one card on the same slate ground every record in this
   application sits on. The grid behind the page is background: nothing is
   asked to be read against it. Inside, the sections are ruled apart by the
   hairline this application divides everything with, so it stays one surface
   rather than becoming a set of tiles. */
.board {
    padding: 0;
    overflow: hidden;
}

.board-gauges {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(24rem, 1fr));
    gap: 1.7rem 3rem;
    padding: 1.5rem 1.6rem 1.7rem;
    border-bottom: 1px solid var(--line);
}

.gauge {
    margin: 0;
    min-width: 0;
}

.gauge-caption {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0 0.6rem;
    margin-bottom: 0.7rem;
}

/* What is being measured, in the micro-caps this application names a reading
   with everywhere else. */
.gauge-name {
    flex: 0 0 100%;
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--steel);
    margin-bottom: 0.35rem;
}

/* The whole the strip is a length of. The largest figure on any page here, and
   set in the same tabular face as the smallest, because on this page every
   number is an instrument reading. */
.gauge-total {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 2.6rem;
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.03em;
    color: var(--ink);
}

.gauge-unit {
    font-size: 0.76rem;
    color: var(--steel);
}

/* How much of that whole is in the state the strip is about, held to the far
   end of the caption line so it lands over the end of the strip below it — the
   reading the whole measure exists to give, and the only thing on the page
   that states a share on its own. */
.gauge-lead {
    margin-left: auto;
    display: inline-flex;
    align-items: baseline;
    gap: 0.35rem;
    text-align: right;
}

.gauge-lead-share {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 1.15rem;
    font-weight: 600;
    line-height: 1;
    color: var(--gold);
}

.gauge-lead-label {
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--steel);
}

/* The strip itself. Perforated along both edges in the page's own ground, so
   it reads as a length of film rather than as a chart. */
/* The strip itself. Its unexposed length is the page's own ground, so a
   half-filled strip reads as film that has not been shot rather than as an
   empty box, and the perforations are punched in the colour of the card
   underneath so they read as holes through it. */
.gauge-strip {
    position: relative;
    display: flex;
    height: 2.4rem;
    border-radius: 2px;
    overflow: hidden;
    background: var(--void);
    box-shadow: inset 0 0 0 1px var(--line);
}

.gauge-strip::before,
.gauge-strip::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    height: 0.34rem;
    z-index: 1;
    background-image: repeating-linear-gradient(
        to right,
        var(--slate) 0 0.3rem,
        transparent 0.3rem 0.78rem);
}

.gauge-strip::before { top: 0; }
.gauge-strip::after { bottom: 0; }

.gauge-band {
    width: var(--band);
    min-width: 2px;
    transition: width 0.5s cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Four states, four weights of the same accent. Gold is what this application
   marks a good reading with, so the described stretch carries it solid, the
   half-done stretch carries it thinned, what is not there at all recedes to
   the ground, and what has left the disk takes the fault colour. */
.gauge-band-full { background: var(--gold); }
.gauge-band-part { background: rgba(201, 166, 52, 0.42); }
.gauge-band-none { background: rgba(95, 110, 124, 0.32); }
.gauge-band-fault { background: var(--fault-edge); }

.gauge-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 0.2rem 1.4rem;
    margin-top: 0.7rem;
}

.gauge-key {
    display: inline-flex;
    align-items: baseline;
    gap: 0.35rem;
    white-space: nowrap;
}

/* A key is marked by the colour of the stretch it names, on the figure alone —
   a swatch beside every one would be four more objects saying what four
   figures already say. */
.gauge-key-value {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--steel);
}

.gauge-key-full .gauge-key-value { color: var(--gold); }
.gauge-key-part .gauge-key-value { color: rgba(201, 166, 52, 0.75); }
.gauge-key-fault .gauge-key-value { color: var(--fault-edge); }

.gauge-key-label {
    font-size: 0.74rem;
    color: var(--steel);
}

.gauge-key-share {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.72rem;
    color: rgba(247, 249, 251, 0.4);
}

/* ── The sheet ───────────────────────────────────────────────────────────── */
/* One surface, ruled into groups. The group stands in the margin and its
   readings run beside it, which is the shape a library card uses to set two
   readings of one title against each other — the same device at page scale. */
.sheet {
    display: block;
}

.sheet-group {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    column-gap: 2.5rem;
    padding: 1.05rem 1.6rem;
    border-bottom: 1px solid var(--line);
    transition: background 0.12s ease;
}

/* A group lights faintly under the pointer, the way every row in this
   application does — enough to keep your place across a wide sheet, and no
   more than that. */
.sheet-group:hover {
    background: #1f1f1f;
}

.sheet-group:last-child {
    border-bottom: 0;
}

.sheet-side {
    margin: 0;
    min-width: 7.5rem;
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--steel);
    padding-top: 0.3rem;
}

.sheet-side a {
    color: var(--steel);
    text-decoration: none;
}

.sheet-side a:hover,
.sheet-side a:focus-visible {
    color: var(--gold);
}

.sheet-readings {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: 0.1rem 2rem;
    min-width: 0;
}

.sheet-reading {
    display: grid;
    grid-template-columns: 5rem minmax(0, 1fr);
    align-items: baseline;
    column-gap: 0.85rem;
    padding: 0.16rem 0;
}

/* The figures line up in one column down the whole sheet, so a value out of
   place is findable without reading a word. */
.sheet-value {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 1rem;
    font-weight: 600;
    line-height: 1.4;
    text-align: right;
    color: var(--ink);
}

.sheet-label {
    font-size: 0.76rem;
    line-height: 1.4;
    color: var(--steel);
    text-decoration: none;
    min-width: 0;
}

a.sheet-label:hover,
a.sheet-label:focus-visible {
    color: var(--gold);
    text-decoration: underline;
}

/* A figure asking for something. Marked only when it is not nought: nought of
   anything on this sheet is the good outcome, and a sheet where everything is
   marked marks nothing. */
.sheet-reading.is-marked .sheet-value {
    color: var(--gold);
}

.sheet-side a:focus-visible,
a.sheet-label:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
}

@media (max-width: 767px) {
    .board-gauges {
        padding: 1.2rem 1.1rem 1.3rem;
    }
    .sheet-group {
        grid-template-columns: minmax(0, 1fr);
        row-gap: 0.5rem;
        padding: 1rem 1.1rem;
    }
    .gauge-total {
        font-size: 2rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .sheet-group {
        transition: none;
    }
}

/* The strips fill once as the page settles, which is the archive being
   measured. Nothing else on the page moves. */
@media (prefers-reduced-motion: no-preference) {
    .gauge-band {
        animation: gauge-fill 0.75s cubic-bezier(0.22, 0.61, 0.36, 1) both;
    }
    .gauge:last-child .gauge-band {
        animation-delay: 0.14s;
    }
}

@keyframes gauge-fill {
    from { width: 0; }
    to { width: var(--band); }
}

/* ── Requests ──────────────────────────────────────────────────────────────── */
/* Two lists on one page, and they have to read as different things: what IMDb
   answered, which is a question waiting to be picked from, and what has been
   asked for, which is the record. The results carry the gold edge of something
   actionable; the requests are cards like every other record here. */
.request-results {
    margin-bottom: 1.5rem;
}

.request-results-close {
    margin-left: 0.8rem;
    padding: 0;
    border: 0;
    background: none;
    color: var(--gold);
    font: inherit;
    font-size: 0.66rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    cursor: pointer;
}

.request-results-close:hover {
    text-decoration: underline;
}

.request-result,
.request-row {
    display: flex;
    align-items: stretch;
    background: var(--slate);
    border: 1px solid var(--line);
    border-left: 3px solid transparent;
    border-radius: 3px;
    margin-bottom: 0.35rem;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.request-result:hover,
.request-row:hover {
    border-left-color: var(--gold);
    background: #1f1f1f;
}

/* A candidate is not a record yet, so it is held to an outline until it is
   asked for. */
.request-result {
    background: transparent;
    border-style: dashed;
}

.request-result:hover {
    background: rgba(201, 166, 52, 0.05);
}

.request-result-art,
.request-row-art {
    flex: 0 0 auto;
    padding: 0.7rem 0 0.7rem 0.9rem;
}

.request-result-poster,
.request-result-poster-empty,
.request-row-poster,
.request-row-poster-empty {
    width: 3.5rem;
    border-radius: 2px;
    display: block;
    box-shadow: 0 0 0 1px rgba(201, 166, 52, 0.25), 0 2px 8px rgba(0, 0, 0, 0.45);
}

.request-result-poster,
.request-row-poster {
    height: auto;
    background: var(--void);
}

.request-result-poster-empty,
.request-row-poster-empty {
    aspect-ratio: 2 / 3;
    background: var(--void);
    color: var(--steel);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    box-shadow: inset 0 0 0 1px var(--line);
}

.request-result-body,
.request-row-body {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.7rem 0.95rem;
}

.request-result-heading,
.request-row-heading {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.8rem;
    line-height: 1.4;
}

.request-result-title,
.request-row-title {
    font-size: 1rem;
    font-weight: 600;
    color: var(--ink);
    word-break: break-word;
}

.request-result-fact,
.request-row-fact {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.8rem;
    color: var(--steel);
    white-space: nowrap;
}

/* IMDb's own word for what the title is, and the part of the archive a title
   of that kind belongs in — which is what somebody filling the request needs
   to know before they go looking. */
.request-result-kind,
.request-row-kind {
    padding: 0.05rem 0.4rem;
    border: 1px solid var(--line);
    border-radius: 2px;
    font-size: 0.6rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--steel);
    white-space: nowrap;
}

.request-result-section,
.request-row-section {
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--gold);
    white-space: nowrap;
}

.request-result-cast,
.request-row-cast {
    display: block;
    margin-top: 0.25rem;
    font-size: 0.8rem;
    color: var(--steel);
}

.request-result-description,
.request-row-description {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin: 0.35rem 0 0;
    max-width: 78ch;
    font-size: 0.82rem;
    line-height: 1.45;
    color: rgba(247, 249, 251, 0.68);
}

/* Who asked and when, which is what makes this a shared list rather than a
   pile of titles. */
.request-row-meta {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.9rem;
    margin-top: 0.45rem;
}

.request-row-who {
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--steel);
}

.request-row-when,
.request-row-imdb,
.request-result-imdb {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.74rem;
    color: var(--steel);
    text-decoration: none;
}

.request-result-imdb {
    display: inline-block;
    margin-top: 0.35rem;
}

a.request-row-imdb:hover,
a.request-result-imdb:hover {
    color: var(--gold);
    text-decoration: underline;
}

.request-result-controls,
.request-row-controls {
    flex: 0 0 auto;
    display: flex;
    align-items: flex-start;
    gap: 0.3rem;
    padding: 0.7rem 0.9rem 0.7rem 0;
}

.request-result-controls .btn,
.request-row-controls .btn {
    font-size: 0.7rem;
    padding: 0.25rem 0.7rem;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

.request-row-controls .btn:disabled {
    opacity: 0.35;
}

@media (max-width: 575px) {
    .request-result-art,
    .request-row-art {
        display: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .request-result,
    .request-row {
        transition: none;
    }
}

/* ── Filmography ───────────────────────────────────────────────────────────── */
/* Somebody's career rather than the archive's holdings, so the cards are read
   for what is missing as much as for what is there. Same card language as
   everywhere else: the difference is that most of these rows will never be in
   the library, and the ones that already are say so instead of offering a
   control that would do nothing. */
.person-card {
    display: flex;
    align-items: stretch;
    background: var(--slate);
    border: 1px solid var(--line);
    border-left: 3px solid transparent;
    border-radius: 3px;
    margin-bottom: 0.4rem;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.person-card:hover {
    border-left-color: var(--gold);
    background: #1f1f1f;
}

.person-card-open {
    cursor: pointer;
}

.person-card-open:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
}

.person-card-art {
    flex: 0 0 auto;
    padding: 0.7rem 0 0.7rem 0.9rem;
}

/* A portrait rather than a poster, so it is squarer than the artwork
   everywhere else here and framed the same way. */
.person-card-portrait,
.person-card-portrait-empty {
    width: 3.25rem;
    border-radius: 2px;
    display: block;
    box-shadow: 0 0 0 1px rgba(201, 166, 52, 0.25), 0 2px 8px rgba(0, 0, 0, 0.45);
}

.person-card-portrait {
    height: auto;
    background: var(--void);
}

.person-card-portrait-empty {
    aspect-ratio: 3 / 4;
    background: var(--void);
    color: var(--steel);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.1rem;
    box-shadow: inset 0 0 0 1px var(--line);
}

.person-card-body {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.75rem 0.95rem;
}

.person-card-name {
    display: block;
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--ink);
}

/* The titles IMDb identifies somebody by, which is what tells one person of a
   name from the next three of the same name. */
.person-card-known {
    display: block;
    margin-top: 0.2rem;
    font-size: 0.8rem;
    color: var(--steel);
}

.person-card-imdb {
    display: inline-block;
    margin-top: 0.3rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.74rem;
    color: var(--steel);
    text-decoration: none;
}

a.person-card-imdb:hover {
    color: var(--gold);
    text-decoration: underline;
}

.person-card-controls {
    flex: 0 0 auto;
    display: flex;
    align-items: flex-start;
    gap: 0.3rem;
    padding: 0.75rem 0.9rem 0.75rem 0;
}

.person-card-controls .btn {
    font-size: 0.72rem;
    padding: 0.3rem 0.6rem;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

/* One thing somebody acted in, and not in the archive — which is most of a
   career. Held to an outline, the way everything this application knows about
   but does not have is drawn: a missing season, an episode with no file, a
   candidate nobody has asked for yet. The row is a space where a holding could
   be. */
.credit-card {
    display: flex;
    align-items: stretch;
    background: transparent;
    border: 1px dashed var(--line);
    border-left: 3px solid transparent;
    border-radius: 3px;
    margin-bottom: 0.4rem;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.credit-card:hover {
    border-left-color: var(--gold);
}

/* In the archive. Filled and solid, which is how every record this
   application actually holds is drawn — the card carries weight because there
   is something behind it. */
.credit-card-held {
    background: var(--slate);
    border-style: solid;
    border-left-color: rgba(201, 166, 52, 0.5);
}

.credit-card-held:hover {
    background: #1f1f1f;
    border-left-color: var(--gold);
}

.credit-card-art {
    flex: 0 0 auto;
    padding: 0.7rem 0 0.7rem 0.9rem;
}

.credit-card-poster,
.credit-card-poster-empty {
    width: 3rem;
    border-radius: 2px;
    display: block;
    box-shadow: 0 0 0 1px rgba(201, 166, 52, 0.25), 0 2px 8px rgba(0, 0, 0, 0.45);
}

.credit-card-poster {
    height: auto;
    background: var(--void);
}

.credit-card-poster-empty {
    aspect-ratio: 2 / 3;
    background: var(--void);
    color: var(--steel);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.8rem;
    box-shadow: inset 0 0 0 1px var(--line);
}

.credit-card-body {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.7rem 0.95rem;
}

/* The series an episode belongs to, above the episode's own name — that is the
   part somebody recognises. */
.credit-card-series {
    display: block;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--steel);
}

.credit-card-episode {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    letter-spacing: 0.02em;
    color: var(--gold);
    margin-left: 0.4rem;
}

.credit-card-heading {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.8rem;
    line-height: 1.4;
}

.credit-card-title {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--ink);
    word-break: break-word;
}

.credit-card-fact {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.78rem;
    color: var(--steel);
    white-space: nowrap;
}

.credit-card-rating {
    color: var(--gold);
}

.credit-card-kind {
    padding: 0.05rem 0.4rem;
    border: 1px solid var(--line);
    border-radius: 2px;
    font-size: 0.6rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--steel);
    white-space: nowrap;
}

.credit-card-section {
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--gold);
    white-space: nowrap;
}

/* Who they played, which is half of what a filmography is read for. */
.credit-card-character {
    display: block;
    margin-top: 0.2rem;
    font-size: 0.8rem;
    color: var(--ink);
}

.credit-card-description {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin: 0.3rem 0 0;
    max-width: 78ch;
    font-size: 0.8rem;
    line-height: 1.45;
    color: rgba(247, 249, 251, 0.68);
}

.credit-card-meta {
    display: block;
    margin-top: 0.3rem;
}

.credit-card-imdb {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.74rem;
    color: var(--steel);
    text-decoration: none;
}

a.credit-card-imdb:hover {
    color: var(--gold);
    text-decoration: underline;
}

.credit-card-controls {
    flex: 0 0 auto;
    display: flex;
    align-items: flex-start;
    gap: 0.3rem;
    padding: 0.7rem 0.9rem 0.7rem 0;
    min-width: 9.5rem;
    justify-content: flex-end;
}

.credit-card-controls .btn {
    font-size: 0.72rem;
    padding: 0.3rem 0.6rem;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

/* Already in the archive. A statement rather than a control, because there is
   nothing left to do with it. */
.credit-card-obtained {
    display: inline-flex;
    align-items: center;
    padding: 0.3rem 0.6rem;
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--gold);
    white-space: nowrap;
}

/* How much of a series somebody is in, said beside the part they played. */
.credit-card-episode-count {
    margin-left: 0.5rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.74rem;
    color: var(--steel);
}

/* One episode of a series somebody guested in, indented under the series it
   belongs to and set narrower — it is part of the credit above it rather than
   a credit of its own. */
.episode-credit {
    display: flex;
    align-items: stretch;
    margin: 0 0 0.3rem 2.5rem;
    background: transparent;
    border: 1px dashed var(--line);
    border-left: 2px solid rgba(201, 166, 52, 0.35);
    border-radius: 3px;
}

.episode-credit-held {
    background: var(--slate);
    border-style: solid;
    border-left-color: rgba(201, 166, 52, 0.5);
}

.episode-credit-number {
    flex: 0 0 4.6rem;
    display: flex;
    align-items: flex-start;
    justify-content: flex-end;
    padding: 0.5rem 0.7rem;
    border-right: 1px solid var(--line);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--gold);
    white-space: nowrap;
}

.episode-credit-body {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.5rem 0.8rem;
}

.episode-credit-heading {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.8rem;
    line-height: 1.4;
}

.episode-credit-title {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--ink);
    word-break: break-word;
}

/* Asked for. Inert, the way every control in this application is once the
   thing it would do is done. */
.credit-card-request.is-requested {
    background: transparent !important;
    border: 1px solid rgba(201, 166, 52, 0.5) !important;
    color: var(--gold) !important;
    opacity: 1;
    pointer-events: none;
}

@media (max-width: 767px) {
    .person-card-art,
    .credit-card-art {
        display: none;
    }
    .credit-card-controls {
        min-width: 0;
    }
}

@media (prefers-reduced-motion: reduce) {
    .person-card,
    .credit-card {
        transition: none;
    }
}

/* ── Suggestions ───────────────────────────────────────────────────────────── */
/* What the archive is missing next door to what it has, on the card language
   the libraries use — because the question a suggestion asks is whether it
   should become one of them. What a library card spends on reconciling two
   readings, this spends on the reason it is here: what the title is to a film
   already on the shelf, which leads the card. */
.suggestion-card {
    display: flex;
    align-items: stretch;
    background: var(--slate);
    border: 1px solid var(--line);
    border-left: 3px solid transparent;
    border-radius: 3px;
    margin-bottom: 0.5rem;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.suggestion-card:hover {
    border-left-color: var(--gold);
    background: #1f1f1f;
}

.suggestion-card-art {
    flex: 0 0 auto;
    padding: 0.9rem 0 0.9rem 1rem;
}

.suggestion-card-poster,
.suggestion-card-poster-empty {
    width: 4.25rem;
    border-radius: 2px;
    display: block;
    box-shadow: 0 0 0 1px rgba(201, 166, 52, 0.25), 0 2px 8px rgba(0, 0, 0, 0.45);
}

.suggestion-card-poster {
    height: auto;
    background: var(--void);
}

.suggestion-card-poster-empty {
    aspect-ratio: 2 / 3;
    background: var(--void);
    color: var(--steel);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.2rem;
    box-shadow: inset 0 0 0 1px var(--line);
}

.suggestion-card-body {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.9rem 1.1rem;
}

/* Why the card is on the page: what this title is to a film the archive holds.
   Set above the title in the micro-caps this application names a reading with,
   because it is the reason rather than the thing. */
.suggestion-card-reason {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.5rem;
    margin-bottom: 0.15rem;
}

.suggestion-card-relation {
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--gold);
    white-space: nowrap;
}

.suggestion-card-source {
    font-size: 0.74rem;
    color: var(--steel);
    min-width: 0;
}

.suggestion-card-heading {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.8rem;
    line-height: 1.35;
}

.suggestion-card-title {
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--ink);
    word-break: break-word;
}

.suggestion-card-fact {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.85rem;
    color: var(--steel);
    white-space: nowrap;
}

/* IMDb's own word for what the title is, and the part of the archive a title
   of that kind belongs in — which is what somebody filling the suggestion
   needs to know before they go looking. */
.suggestion-card-kind {
    padding: 0.05rem 0.4rem;
    border: 1px solid var(--line);
    border-radius: 2px;
    font-size: 0.6rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--steel);
    white-space: nowrap;
}

.suggestion-card-section {
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--gold);
    white-space: nowrap;
}

/* The shape of a suggested series, read as one line of facts the way a show's
   own card reads it. */
.suggestion-card-shape {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.15rem 0.9rem;
    margin-top: 0.35rem;
    font-size: 0.75rem;
    color: var(--steel);
}

.suggestion-card-shape-figure {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
}

.suggestion-card-cast {
    display: block;
    margin-top: 0.3rem;
    font-size: 0.8rem;
    color: var(--steel);
}

.suggestion-card-description {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin: 0.4rem 0 0;
    max-width: 78ch;
    font-size: 0.85rem;
    line-height: 1.5;
    color: rgba(247, 249, 251, 0.68);
}

.suggestion-card-meta {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.9rem;
    margin-top: 0.4rem;
}

.suggestion-card-imdb {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.74rem;
    color: var(--steel);
    text-decoration: none;
}

a.suggestion-card-imdb:hover {
    color: var(--gold);
    text-decoration: underline;
}

/* A card whose title has not had its turn with IMDb yet. It carries a name and
   a year and says that the rest is coming, rather than reading as a title
   nobody knows anything about. */
.suggestion-card-pending {
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--steel);
}

.suggestion-card-controls {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.3rem;
    padding: 0.9rem 1rem 0.9rem 0;
    min-width: 8.5rem;
}

.suggestion-card-controls .btn {
    text-align: left;
    font-size: 0.72rem;
    padding: 0.3rem 0.6rem;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

/* Asked for. The control is inert because the thing it would do is done, so it
   reads as a state rather than as an action taken away. */
.suggestion-card-request.is-requested {
    background: transparent !important;
    border: 1px solid rgba(201, 166, 52, 0.5) !important;
    color: var(--gold) !important;
    opacity: 1;
    pointer-events: none;
}

/* How many titles are being kept off the list, carried on the control that
   opens them so nobody has to look to find out. */
.suggestion-bans-count {
    margin-left: 0.35rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
}

.suggestion-ban-list {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    max-height: 55vh;
    overflow-y: auto;
}

/* One banned title: what it is, where to read about it, who took it out, and
   the way to put it back. Laid out as a grid so the four line up down the list
   however long a title runs. */
.suggestion-ban {
    display: grid;
    grid-template-columns: minmax(0, 1fr) max-content max-content max-content;
    align-items: baseline;
    gap: 0.2rem 0.9rem;
    padding: 0.5rem 0.7rem;
    background: var(--void);
    border: 1px solid var(--line);
    border-radius: 3px;
}

.suggestion-ban-title {
    font-size: 0.88rem;
    font-weight: 600;
    color: var(--ink);
    min-width: 0;
    word-break: break-word;
}

.suggestion-ban-imdb {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.74rem;
    color: var(--steel);
    text-decoration: none;
}

a.suggestion-ban-imdb:hover {
    color: var(--gold);
    text-decoration: underline;
}

.suggestion-ban-who {
    display: inline-flex;
    align-items: baseline;
    gap: 0.5rem;
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--steel);
}

.suggestion-ban-when {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0;
    text-transform: none;
}

.suggestion-ban-clear {
    font-size: 0.7rem !important;
    padding: 0.2rem 0.6rem !important;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

.suggestion-bans-empty {
    margin: 0.9rem 0 0;
}

@media (max-width: 767px) {
    .suggestion-card-art {
        display: none;
    }
    .suggestion-card-controls {
        flex-direction: row;
        flex-wrap: wrap;
        min-width: 0;
        padding: 0 1.1rem 0.9rem;
    }
    .suggestion-card {
        flex-wrap: wrap;
    }
    .suggestion-ban {
        grid-template-columns: minmax(0, 1fr) max-content;
    }
}

@media (prefers-reduced-motion: reduce) {
    .suggestion-card {
        transition: none;
    }
}

/* ── Issues ────────────────────────────────────────────────────────────────── */
/* What people have found wrong with what the archive holds, on the same card
   language the queue is read in — this is the other list a person works down,
   and the two should read the same way. What differs is the gutter and the
   body: the gutter says which level a fault sits at rather than a place in an
   order, and the first thing in the body is the sentence somebody typed, since
   that is the reason the row exists. */
.issue-row {
    display: flex;
    align-items: stretch;
    background: var(--slate);
    border: 1px solid var(--line);
    border-left: 3px solid transparent;
    border-radius: 3px;
    margin-bottom: 0.35rem;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.issue-row:hover {
    border-left-color: var(--gold);
    background: #1f1f1f;
}

/* Which of the four levels the fault is at — a film, a show, a season, one
   episode. Wider than the queue's own gutter because "S01E05" is what goes in
   it, and set in the same tabular face so the column reads down the page. */
.issue-row-subject {
    flex: 0 0 5rem;
    display: flex;
    align-items: flex-start;
    justify-content: flex-end;
    padding: 0.7rem 0.8rem 0.7rem 0.6rem;
    border-right: 1px solid var(--line);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.8rem;
    font-weight: 600;
    line-height: 1.55;
    color: var(--gold);
    white-space: nowrap;
}

.issue-row-art {
    flex: 0 0 auto;
    padding: 0.6rem 0 0.6rem 0.85rem;
}

.issue-row-poster,
.issue-row-poster-empty {
    width: 3.25rem;
    border-radius: 2px;
    display: block;
    box-shadow: 0 0 0 1px rgba(201, 166, 52, 0.25), 0 2px 8px rgba(0, 0, 0, 0.45);
}

.issue-row-poster {
    height: auto;
    background: var(--void);
}

.issue-row-poster-empty {
    aspect-ratio: 2 / 3;
    background: var(--void);
    color: var(--steel);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.8rem;
    box-shadow: inset 0 0 0 1px var(--line);
}

.issue-row-body {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.65rem 0.9rem;
}

.issue-row-heading {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.8rem;
    line-height: 1.4;
}

.issue-row-title {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--ink);
    text-decoration: none;
    word-break: break-word;
}

a.issue-row-title:hover,
a.issue-row-title:focus {
    color: var(--gold);
    text-decoration: none;
    box-shadow: inset 0 -2px 0 var(--gold);
}

.issue-row-fact {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.78rem;
    color: var(--steel);
    white-space: nowrap;
}

/* Which season or which episode, in the form this archive writes them in, so
   the string on the row is the string on the file. */
.issue-row-episode {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.78rem;
    color: var(--gold);
    white-space: nowrap;
}

.issue-row-episode-title {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.85rem;
    font-weight: 600;
    line-height: 1.4;
    color: var(--ink);
}

.issue-row-facts {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.85rem;
    margin-top: 0.2rem;
    line-height: 1.4;
}

.issue-row-rating {
    color: var(--gold);
}

.issue-row-cast {
    font-size: 0.78rem;
    color: var(--steel);
    min-width: 0;
}

/* The fault itself, as it was typed. Set at full contrast and ruled off from
   the title above it, because everything else on the row describes the thing
   and this one line describes what is wrong with it. Line breaks are kept: a
   person listing three faults writes three lines. */
.issue-row-problem {
    margin: 0.45rem 0 0;
    padding-left: 0.7rem;
    border-left: 2px solid rgba(201, 166, 52, 0.45);
    max-width: 78ch;
    font-size: 0.88rem;
    line-height: 1.5;
    color: var(--ink);
    white-space: pre-wrap;
    word-break: break-word;
}

/* Who reported it and when, which is what makes this a shared list rather than
   a pile of complaints. */
.issue-row-meta {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.9rem;
    margin-top: 0.4rem;
}

.issue-row-who {
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--steel);
}

.issue-row-when {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.74rem;
    color: var(--steel);
}

/* IMDb's synopsis, closing the row and held to two lines. It is here so the
   row can be recognised at a glance; the fault above it is what is read. */
.issue-row-description {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin: 0.35rem 0 0;
    max-width: 78ch;
    font-size: 0.8rem;
    line-height: 1.45;
    color: rgba(247, 249, 251, 0.68);
}

.issue-row-controls {
    flex: 0 0 auto;
    display: flex;
    align-items: flex-start;
    gap: 0.3rem;
    padding: 0.6rem 0.85rem 0.6rem 0;
}

.issue-row-controls .btn {
    font-size: 0.7rem;
    padding: 0.25rem 0.6rem;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

.issue-row-controls .btn:disabled {
    opacity: 0.35;
}

/* A report naming something the archive no longer answers for. Marked the way
   every other fault in this application is, and left in place: the thing
   having disappeared is itself worth reading. */
.issue-row-fault {
    border-left-color: var(--fault-edge);
}

.issue-row-fault:hover {
    border-left-color: var(--fault-edge);
}

.issue-row-fault .issue-row-title {
    color: var(--fault-edge);
}

.issue-row-fault .issue-row-subject {
    color: var(--fault-edge);
}

/* A row that opens the report on its own page. The card is the target and its
   controls take their own clicks, so the pointer changes over the card rather
   than over the row of buttons at the end of it. */
.issue-row-open {
    cursor: pointer;
}

.issue-row-open:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
}

/* ── One report ────────────────────────────────────────────────────────────── */
/* Where a fault is worked out. The same card the listing draws a row on, opened
   up: the artwork is the size a poster is looked at rather than scanned, the
   fault is set as prose because it is the thing being read, and the
   conversation runs beneath it. */
.issue-detail {
    display: flex;
    align-items: stretch;
    background: var(--slate);
    border: 1px solid var(--line);
    border-left: 3px solid var(--gold);
    border-radius: 3px;
    margin-bottom: 0.35rem;
}

.issue-detail-fault {
    border-left-color: var(--fault-edge);
}

.issue-detail-art {
    flex: 0 0 auto;
    padding: 1.1rem 0 1.1rem 1.1rem;
}

.issue-detail-poster,
.issue-detail-poster-empty {
    width: 7rem;
    border-radius: 3px;
    display: block;
    box-shadow: 0 0 0 1px rgba(201, 166, 52, 0.3), 0 6px 20px rgba(0, 0, 0, 0.5);
}

.issue-detail-poster {
    height: auto;
    background: var(--void);
}

.issue-detail-poster-empty {
    aspect-ratio: 2 / 3;
    background: var(--void);
    color: var(--steel);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.6rem;
    box-shadow: inset 0 0 0 1px var(--line);
}

.issue-detail-body {
    flex: 1 1 auto;
    min-width: 0;
    padding: 1.1rem 1.2rem;
}

/* Which of the four levels the fault is at, in the micro-caps this application
   names a reading with. */
.issue-detail-eyebrow {
    display: block;
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--gold);
}

.issue-detail-title {
    margin: 0.3rem 0 0;
    font-size: 1.35rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.25;
    color: var(--ink);
    word-break: break-word;
}

.issue-detail-facts {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.9rem;
    margin-top: 0.35rem;
}

.issue-detail-fact {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.78rem;
    color: var(--steel);
}

.issue-detail-episode {
    color: var(--gold);
    white-space: nowrap;
}

.issue-detail-rating {
    color: var(--gold);
}

/* The fault itself, set as prose and given the room the listing could not.
   Line breaks are kept, because a person listing three faults writes three
   lines. */
.issue-detail-problem {
    margin: 0.8rem 0 0;
    padding-left: 0.8rem;
    border-left: 2px solid rgba(201, 166, 52, 0.45);
    max-width: 78ch;
    font-size: 0.95rem;
    line-height: 1.55;
    color: var(--ink);
    white-space: pre-wrap;
    word-break: break-word;
}

.issue-detail-meta {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.9rem;
    margin-top: 0.7rem;
}

.issue-detail-who {
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--steel);
}

.issue-detail-when {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.74rem;
    color: var(--steel);
}

/* The way to the thing the fault is in. */
.issue-detail-link {
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--gold);
    text-decoration: none;
}

.issue-detail-link:hover,
.issue-detail-link:focus {
    color: var(--gold);
    box-shadow: inset 0 -2px 0 var(--gold);
    text-decoration: none;
}

.issue-detail-description {
    margin: 0.7rem 0 0;
    max-width: 78ch;
    font-size: 0.85rem;
    line-height: 1.5;
    color: rgba(247, 249, 251, 0.68);
}

/* Closing the card, where every listing in this application puts what can be
   done with a thing. */
.issue-detail-controls {
    flex: 0 0 auto;
    display: flex;
    align-items: flex-start;
    gap: 0.3rem;
    padding: 1.1rem 1.1rem 1.1rem 0;
}

.issue-detail-controls .btn {
    font-size: 0.7rem;
    padding: 0.25rem 0.6rem;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

/* ── The conversation ──────────────────────────────────────────────────────── */
/* What has been said about the fault, oldest first. Each comment is ruled off
   at its left edge rather than boxed: it is one voice in a thread, and a page
   of boxes would read as a page of records. */
.issue-comment-add {
    margin-left: 0.8rem;
    padding: 0;
    border: 0;
    background: none;
    color: var(--gold);
    font: inherit;
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    cursor: pointer;
}

.issue-comment-add:hover {
    text-decoration: underline;
}

.issue-comment-add:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
}

.issue-comments {
    display: block;
}

.issue-comment {
    padding: 0.6rem 0 0.6rem 0.9rem;
    border-left: 2px solid var(--line);
    margin-bottom: 0.3rem;
}

.issue-comment-meta {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.8rem;
}

.issue-comment-who {
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--gold);
}

.issue-comment-when {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.72rem;
    color: var(--steel);
}

/* Said differently since it was posted. Quiet — it is a note on the words
   rather than part of them — and carrying the day it changed for anybody who
   asks the control for it. */
.issue-comment-edited {
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--steel);
    cursor: help;
}

/* What can be done with a comment, shown only to the account that may do it.
   Held at the end of the line the comment names itself on, and kept to the
   label colour until reached for — a thread is read, and two buttons per
   paragraph in the accent would be read before the words are. */
.issue-comment-controls {
    display: inline-flex;
    align-items: baseline;
    gap: 0.8rem;
    margin-left: auto;
}

.issue-comment-action {
    padding: 0;
    border: 0;
    background: none;
    color: var(--steel);
    font: inherit;
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    cursor: pointer;
    transition: color 0.12s ease;
}

.issue-comment-action:hover {
    color: var(--gold);
}

.issue-comment-remove:hover {
    color: var(--fault-edge);
}

.issue-comment-action:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
}

.issue-comment-body {
    margin: 0.25rem 0 0;
    max-width: 78ch;
    font-size: 0.88rem;
    line-height: 1.5;
    color: var(--ink);
    white-space: pre-wrap;
    word-break: break-word;
}

/* The field a fault or a comment is written in. Taller than one line because
   either takes a sentence or three, and free to grow downwards only — a
   control that can be dragged wider than its own dialog is a control fighting
   its box. */
.issue-report-field {
    min-height: 6rem;
    resize: vertical;
}

@media (max-width: 575px) {
    .issue-detail-art {
        display: none;
    }
}

@media (max-width: 767px) {
    .issue-row-art {
        display: none;
    }
    .issue-row-controls {
        flex-wrap: wrap;
        justify-content: flex-end;
    }
}

@media (max-width: 575px) {
    .issue-row-subject {
        flex-basis: 3.6rem;
        padding: 0.7rem 0.55rem;
        font-size: 0.72rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .issue-row {
        transition: none;
    }
}

/* ── Watch ─────────────────────────────────────────────────────────────────── */
/* The one page in this application that is not a listing. Everything else here
   is built for comparing fields; this is built for looking at a picture, so the
   screen takes the width it can get, sits on void rather than on slate, and the
   controls hold to the bottom edge of the picture instead of floating beside
   it. */
/* The stage is two different things at two moments, and says so. Idle it is
   part of the console — the void ground, the blueprint grid showing through,
   the title card mounted on it. Playing it goes black and the picture owns the
   page. Nothing else in this application changes ground like that, which is
   how the page marks the difference between choosing and watching. */
.watch-stage {
    margin: 0 -1rem 1.25rem;
    background: #000;
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    transition: background 0.2s ease;
}

.watch-screen {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    max-height: 72vh;
    margin: 0 auto;
    background: #000;
    overflow: hidden;
}

.watch:not(.is-loaded) .watch-stage,
.watch:not(.is-loaded) .watch-screen {
    background: transparent;
}

.watch:not(.is-loaded) .watch-stage {
    border-color: transparent;
}

/* Idle, the stage is as tall as what it is showing rather than held to the
   shape of a picture that is not there. */
.watch:not(.is-loaded) .watch-screen {
    aspect-ratio: auto;
    max-height: none;
    overflow: visible;
}

.watch:not(.is-loaded) .watch-video,
.watch:not(.is-loaded) .watch-controls {
    display: none;
}

.watch-video {
    display: block;
    width: 100%;
    height: 100%;
    background: #000;
    object-fit: contain;
}

/* The picture is a control while there is one: clicking it stops and starts
   it. The controls over it keep their own pointer. */
.watch.is-loaded .watch-video {
    cursor: pointer;
}

/* ── The title card ──────────────────────────────────────────────────────── */
/* What is cued, as the card a film gets before it starts. The poster is
   mounted in the same hairline gold frame every other poster in this
   application wears, because it is the same object at a different size — and
   the grid behind it stays visible, so the page reads as the console it is
   rather than as somebody's streaming front end. */
.watch-cue {
    display: flex;
    align-items: center;
    gap: 2rem;
    padding: 2.25rem 1rem;
    min-height: 18rem;
}

.watch-cue-art {
    flex: 0 0 auto;
}

.watch-cue-poster,
.watch-cue-poster-empty {
    width: 10rem;
    border-radius: 3px;
    display: block;
    box-shadow: 0 0 0 1px rgba(201, 166, 52, 0.3), 0 10px 34px rgba(0, 0, 0, 0.6);
}

.watch-cue-poster {
    height: auto;
    background: var(--void);
}

.watch-cue-poster-empty {
    aspect-ratio: 2 / 3;
    background: var(--void);
    color: var(--steel);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2rem;
    box-shadow: inset 0 0 0 1px var(--line);
}

.watch-cue-body {
    flex: 1 1 auto;
    min-width: 0;
    max-width: 46rem;
}

/* Where this sits in the run — first up, or part-way through. Set in the
   micro-caps this application gives every label that names what a reading is. */
.watch-cue-eyebrow {
    display: block;
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--gold);
}

/* The largest type on any page of this application. The listings are read by
   comparing lines; this is read as a name, once, so it is allowed the size. */
.watch-cue-title {
    margin: 0.35rem 0 0;
    font-size: 2rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.15;
    color: var(--ink);
    word-break: break-word;
}

.watch-cue-episode {
    display: block;
    margin-top: 0.3rem;
    font-size: 1rem;
    font-weight: 600;
    color: var(--ink);
}

.watch-cue-episode-number {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--gold);
    margin-right: 0.5rem;
}

.watch-cue-facts {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 1rem;
    margin-top: 0.55rem;
}

.watch-cue-fact {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.8rem;
    color: var(--steel);
    white-space: nowrap;
}

.watch-cue-rating {
    color: var(--gold);
}

.watch-cue-cast {
    display: block;
    margin-top: 0.35rem;
    font-size: 0.85rem;
    color: var(--steel);
}

.watch-cue-description {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin: 0.7rem 0 0;
    max-width: 62ch;
    font-size: 0.9rem;
    line-height: 1.55;
    color: rgba(247, 249, 251, 0.72);
}

/* The one control on the card, and the only filled gold button on the page.
   Everything else here is a hairline, so where the eye lands is where playing
   starts. */
.watch-cue-start {
    display: inline-flex;
    align-items: center;
    margin-top: 1.25rem;
    padding: 0.6rem 1.6rem;
    border: 0;
    border-radius: 3px;
    background: var(--gold);
    color: var(--void);
    font-family: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background 0.12s ease;
}

.watch-cue-start:hover {
    background: #b8952e;
}

.watch-cue-start:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 3px;
}

.watch-cue-note {
    margin: 0.5rem 0 0;
    max-width: 46ch;
    font-size: 0.9rem;
    line-height: 1.6;
    color: var(--steel);
}

/* The end of the run, in place of a frozen last frame. */
.watch-done {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    padding: 3rem 1rem;
    min-height: 18rem;
}

/* Waiting on the archive for more of the file. A ring rather than a spinner
   of dots, turning slowly, because a stall on a large file lasts long enough
   that anything faster reads as alarm. */
.watch-wait {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

.watch-wait-mark {
    width: 2.6rem;
    height: 2.6rem;
    border: 2px solid rgba(255, 255, 255, 0.18);
    border-top-color: var(--gold);
    border-radius: 50%;
    animation: watch-wait-turn 0.9s linear infinite;
}

@keyframes watch-wait-turn {
    to { transform: rotate(360deg); }
}

/* The controls sit on the picture, on a gradient rather than a bar, so the
   frame keeps its full height and the controls still read against a bright
   scene. They stay put while paused and while the pointer is on the picture,
   and withdraw once neither is true. */
.watch-controls {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 2.5rem 1rem 0.7rem;
    background: linear-gradient(to top, rgba(0, 0, 0, 0.88), rgba(0, 0, 0, 0));
    opacity: 1;
    transition: opacity 0.18s ease;
}

.watch.is-playing .watch-screen:not(:hover):not(:focus-within) .watch-controls {
    opacity: 0;
}

.watch-scrub {
    padding: 0 0.15rem 0.45rem;
}

/* The seek bar carries three readings on one line: how much has been played,
   how much is ready to play, and where a drag would land. The input itself is
   invisible and lies over the whole track, so the bar is the control rather
   than a thumb beside one. */
.watch-scrub-track {
    position: relative;
    height: 4px;
    border-radius: 2px;
    background: rgba(255, 255, 255, 0.22);
}

.watch-scrub-buffered,
.watch-scrub-played {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    border-radius: 2px;
    width: 0;
}

.watch-scrub-buffered {
    background: rgba(255, 255, 255, 0.3);
}

.watch-scrub-played {
    background: var(--gold);
}

.watch-scrub-input {
    position: absolute;
    inset: -7px 0;
    width: 100%;
    height: auto;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}

.watch-buttons {
    display: flex;
    align-items: center;
    gap: 0.65rem;
}

.watch-button {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    border: 0;
    border-radius: 3px;
    background: none;
    color: var(--ink);
    font-size: 0.9rem;
    cursor: pointer;
    transition: color 0.12s ease, background 0.12s ease;
}

.watch-button:hover,
.watch-button:focus-visible {
    color: var(--gold);
    background: rgba(255, 255, 255, 0.08);
    outline: none;
}

.watch-button:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 1px;
}

.watch-button:disabled {
    opacity: 0.35;
    cursor: default;
}

/* Captions, when they are on. The one control on the bar that holds a state. */
.watch-button.is-on {
    color: var(--gold);
    box-shadow: inset 0 -2px 0 var(--gold);
}

.watch-button-play {
    width: 2.4rem;
    height: 2.4rem;
    font-size: 1.05rem;
}

.watch-time {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: baseline;
    gap: 0.3rem;
    margin-left: 0.3rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.76rem;
    color: var(--ink);
}

.watch-time-separator {
    color: rgba(255, 255, 255, 0.45);
}

/* What is playing, named on the bar so the picture never has to be paused to
   find out. Takes the room left between the transport and the volume, and
   gives it up first. */
.watch-now {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding: 0 0.5rem;
    font-size: 0.78rem;
    color: rgba(255, 255, 255, 0.72);
}

.watch-volume {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

.watch-volume-input {
    width: 5rem;
    accent-color: var(--gold);
    cursor: pointer;
}

/* The level as a figure, because the control now goes past the file's own
   loudness and a slider position alone cannot say by how much. */
.watch-volume-level {
    min-width: 2.6rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.72rem;
    color: var(--steel);
    white-space: nowrap;
}

/* Turned up past what is in the file. Marked, because that is the state worth
   knowing about when something starts distorting. */
.watch-volume-level.is-boosted {
    color: var(--gold);
}

/* ── The settings panel ──────────────────────────────────────────────────── */
/* Everything the player can be told to do differently, in one panel opening
   from its own control. Built on the same slate-and-hairline card the rest of
   the application is drawn on, so the panel belongs to this system rather than
   to the picture it sits over. */
.watch-settings {
    position: relative;
    flex: 0 0 auto;
    display: inline-flex;
}

.watch-menu {
    position: absolute;
    right: 0;
    bottom: calc(100% + 0.6rem);
    z-index: 5;
    width: 16.5rem;
    padding: 0.9rem 1rem;
    background: var(--slate);
    border: 1px solid var(--line);
    border-radius: 4px;
    box-shadow: 0 14px 40px rgba(0, 0, 0, 0.7);
}

.watch-menu-group + .watch-menu-group {
    margin-top: 0.9rem;
    padding-top: 0.9rem;
    border-top: 1px solid var(--line);
}

.watch-menu-label {
    display: block;
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--steel);
}

.watch-menu-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin-top: 0.5rem;
}

/* One setting with a small set of answers, laid out as the answers themselves
   rather than hidden behind a dropdown — there are six speeds and three sizes,
   and a menu that has to be opened to see them is a menu inside a menu. */
.watch-menu-chip {
    padding: 0.24rem 0.55rem;
    border: 1px solid var(--line);
    border-radius: 2px;
    background: var(--void);
    color: var(--steel);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.7rem;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.12s ease, color 0.12s ease;
}

.watch-menu-chip:hover {
    border-color: var(--gold);
    color: var(--ink);
}

.watch-menu-chip.is-on {
    border-color: var(--gold);
    background: var(--gold);
    color: var(--void);
}

.watch-menu-list {
    display: flex;
    flex-direction: column;
    margin-top: 0.4rem;
    max-height: 11rem;
    overflow-y: auto;
}

.watch-menu-item {
    padding: 0.34rem 0.1rem;
    border: 0;
    background: none;
    text-align: left;
    color: var(--steel);
    font-family: inherit;
    font-size: 0.82rem;
    cursor: pointer;
    transition: color 0.12s ease;
}

.watch-menu-item:hover {
    color: var(--ink);
}

.watch-menu-item.is-on {
    color: var(--gold);
    font-weight: 600;
}

.watch-menu-note {
    margin-top: 0.4rem;
    font-size: 0.76rem;
    line-height: 1.45;
    color: var(--steel);
}

/* ── Choosing a subtitle ─────────────────────────────────────────────────── */
/* Candidates from the provider. Built on the same box the IMDb assign results
   use, because it is the same act: several answers to one question, and one of
   them gets written to disk. */
.subtitle-modal-file {
    margin: 0 0 0.9rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.8rem;
    color: var(--steel);
    word-break: break-all;
}

.subtitle-result-list {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    max-height: 50vh;
    overflow-y: auto;
}

.subtitle-result {
    background: var(--void);
    border: 1px solid var(--line);
    border-left: 3px solid transparent;
    border-radius: 3px;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.subtitle-result:hover,
.subtitle-result:focus-within {
    border-color: var(--gold);
    background: rgba(201, 166, 52, 0.08);
}

/* Matched on the file's own bytes rather than on its name, so it is timed to
   this exact release. The one distinction worth marking in a list where every
   other difference is a matter of judgement. */
.subtitle-result.is-exact {
    border-left-color: var(--gold);
}

.subtitle-result-choose {
    display: block;
    width: 100%;
    padding: 0.55rem 0.8rem;
    border: 0;
    background: none;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.subtitle-result-choose:focus {
    outline: none;
}

.subtitle-result-choose:disabled {
    opacity: 0.5;
    pointer-events: none;
}

.subtitle-result-title {
    font-size: 0.88rem;
    font-weight: 600;
    color: var(--ink);
    word-break: break-word;
}

.subtitle-result-meta {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.85rem;
    margin-top: 0.2rem;
}

.subtitle-result-fact {
    font-size: 0.74rem;
    color: var(--steel);
    white-space: nowrap;
}

.subtitle-result-exact {
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--gold);
    white-space: nowrap;
}

/* Fetching subtitles for a file. The provider's hash is asked first whatever
   the file is called, so this is one click and no modal wherever an exact
   match exists. */
.episode-row-subtitles,
.episode-row-retitle,
.episode-row-report {
    flex: 0 0 auto;
    align-self: flex-start;
    margin: 0.5rem 0.85rem 0.5rem 0;
    font-size: 0.7rem !important;
    padding: 0.2rem 0.6rem !important;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

/* An exact match landed and was written. Marked and inert, because the thing
   it would do is done. */
.episode-row-subtitles.is-taken {
    border-color: rgba(201, 166, 52, 0.5) !important;
    color: var(--gold) !important;
    opacity: 1;
}

.episode-row-subtitles.is-taken:disabled {
    opacity: 1;
    pointer-events: none;
}

/* A setting with two answers, shown as the switch it is. */
.watch-menu-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.8rem;
    width: 100%;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
}

.watch-menu-switch {
    flex: 0 0 auto;
    position: relative;
    width: 2.1rem;
    height: 1.05rem;
    border: 1px solid var(--line);
    border-radius: 1rem;
    background: var(--void);
    transition: background 0.12s ease, border-color 0.12s ease;
}

.watch-menu-switch::after {
    content: '';
    position: absolute;
    top: 1px;
    left: 1px;
    width: 0.85rem;
    height: 0.85rem;
    border-radius: 50%;
    background: var(--steel);
    transition: transform 0.12s ease, background 0.12s ease;
}

.watch-menu-toggle.is-on .watch-menu-switch {
    border-color: var(--gold);
}

.watch-menu-toggle.is-on .watch-menu-switch::after {
    background: var(--gold);
    transform: translateX(1.05rem);
}

.watch-menu-chip:focus-visible,
.watch-menu-item:focus-visible,
.watch-menu-toggle:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
}

/* Subtitles as this application sets them: the archive's own ink on a ground
   solid enough to hold against any frame, at the size the reader chose. */
.watch-video::cue {
    background: rgba(0, 10, 18, 0.82);
    color: var(--ink);
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
    line-height: 1.35;
}

.watch[data-caption-size="small"] .watch-video::cue {
    font-size: 0.85em;
}

.watch[data-caption-size="large"] .watch-video::cue {
    font-size: 1.35em;
}

/* The queue, as the player works down it. Same card language as everywhere
   else; what differs is that one row at a time is the one on screen. */
.watch-list {
    display: block;
}

.watch-item {
    display: flex;
    align-items: stretch;
    background: var(--slate);
    border: 1px solid var(--line);
    border-left: 3px solid transparent;
    border-radius: 3px;
    margin-bottom: 0.35rem;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.watch-item:hover {
    border-left-color: var(--gold);
    background: #1f1f1f;
}

/* The row on screen. Marked in the accent the way a current reading is marked
   everywhere in this application. */
.watch-item.is-playing {
    border-left-color: var(--gold);
    background: #1f1f1f;
}

.watch-item.is-playing .watch-item-title {
    color: var(--gold);
}

.watch-item-position {
    flex: 0 0 3.6rem;
    display: flex;
    align-items: flex-start;
    justify-content: flex-end;
    padding: 0.6rem 0.8rem 0.6rem 0.6rem;
    border-right: 1px solid var(--line);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1.4;
    color: var(--ink);
}

.watch-item-art {
    flex: 0 0 auto;
    display: block;
    padding: 0.6rem 0 0.6rem 0.85rem;
}

.watch-item-poster,
.watch-item-poster-empty {
    width: 3.25rem;
    border-radius: 2px;
    display: block;
    box-shadow: 0 0 0 1px rgba(201, 166, 52, 0.25), 0 2px 8px rgba(0, 0, 0, 0.45);
}

.watch-item-poster {
    height: auto;
    background: var(--void);
}

.watch-item-poster-empty {
    aspect-ratio: 2 / 3;
    background: var(--void);
    color: var(--steel);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.8rem;
    box-shadow: inset 0 0 0 1px var(--line);
}

/* The whole body plays the row, so the title and what is known about it are
   one control rather than a label beside a button. */
.watch-item-body {
    flex: 1 1 auto;
    min-width: 0;
    display: block;
    padding: 0.55rem 0.95rem;
    border: 0;
    background: none;
    text-align: left;
    color: inherit;
    cursor: pointer;
}

.watch-item-body:disabled {
    cursor: default;
}

.watch-item-body:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: -2px;
}

.watch-item-heading {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.8rem;
    line-height: 1.4;
}

.watch-item-title {
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--ink);
}

.watch-item-episode {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.76rem;
    color: var(--gold);
    white-space: nowrap;
}

.watch-item-episode-title {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.85rem;
    font-weight: 600;
    line-height: 1.4;
    color: var(--ink);
}

/* What IMDb knows about the row, on one line under its name. */
.watch-item-facts {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.1rem 0.85rem;
    margin-top: 0.2rem;
    line-height: 1.4;
}

.watch-item-fact {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    font-size: 0.76rem;
    color: var(--steel);
    white-space: nowrap;
}

.watch-item-rating {
    color: var(--gold);
}

.watch-item-cast {
    font-size: 0.78rem;
    color: var(--steel);
    min-width: 0;
}

/* The collected billing, running on beneath the names IMDb ships with the
   title. Set in the accent the IMDb row uses on a library card, because it is
   the same source answering about the same title. */
.watch-item-billing {
    display: block;
    margin-top: 0.2rem;
    font-size: 0.78rem;
    line-height: 1.45;
    color: var(--gold);
}

/* The synopsis, held to two lines: this page is read to choose what plays
   next, and full prose on every row would bury the order. */
.watch-item-description {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-top: 0.3rem;
    max-width: 78ch;
    font-size: 0.8rem;
    line-height: 1.45;
    color: rgba(247, 249, 251, 0.68);
}

/* Where the row stands: ready, part-way through, already seen, or with
   nothing in the archive to play. */
.watch-item-state {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--steel);
}

.watch-item-watched .watch-item-title,
.watch-item-watched .watch-item-episode-title {
    color: var(--steel);
}

.watch-item-watched .watch-item-state {
    color: var(--gold);
}

/* An entry the archive has no file for. Held to an outline, the way every
   absence in this application is, and its body is not a control because there
   is nothing to play. */
.watch-item-unplayable {
    background: transparent;
    border-style: dashed;
}

.watch-item-unplayable:hover {
    background: transparent;
    border-left-color: transparent;
}

.watch-item-unplayable .watch-item-title,
.watch-item-unplayable .watch-item-position {
    color: var(--steel);
    font-weight: 400;
}

.watch-item-unwatch,
.watch-item-remove {
    flex: 0 0 auto;
    align-self: flex-start;
    margin: 0.5rem 0.85rem 0.5rem 0;
    font-size: 0.7rem !important;
    padding: 0.2rem 0.6rem !important;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

/* Two controls on one row need the space between them rather than a margin
   apiece pushing the second off the card. */
.watch-item-unwatch + .watch-item-remove {
    margin-left: 0;
}

/* A row taken off the queue while it was playing. The film runs on — being
   dropped from a queue is not being stopped — so the row keeps its place and
   its state line says what happened, in the fault colour every other absence
   in this application is marked with. */
.watch-item-dropped .watch-item-state {
    color: var(--fault-edge);
}

@media (max-width: 767px) {
    .watch-cue {
        flex-direction: column;
        align-items: flex-start;
        gap: 1.25rem;
        padding: 1.5rem 1rem;
    }
    .watch-cue-poster,
    .watch-cue-poster-empty {
        width: 7rem;
    }
    .watch-cue-title {
        font-size: 1.5rem;
    }
    .watch-item-art {
        display: none;
    }
}

@media (max-width: 575px) {
    .watch-now {
        display: none;
    }
    .watch-volume-input {
        width: 3.5rem;
    }
    .watch-item-position {
        flex-basis: 2.9rem;
        padding: 0.6rem 0.55rem;
        font-size: 0.85rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .watch-item,
    .watch-stage,
    .watch-controls,
    .watch-button,
    .watch-cue-start {
        transition: none;
    }
    .watch-wait-mark {
        animation: none;
    }
}

/* ── IMDb assignment results ───────────────────────────────────────────────── */
/* Candidate titles inside the assign modal. Each is a click target, so the
   whole row lifts on hover rather than relying on a button inside it. */
.assign-result-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    max-height: 55vh;
    overflow-y: auto;
}

/* The box holds two actions: choosing the candidate, and opening its IMDb
   title page. The box carries the frame and the hover so the row still reads
   as one thing, while each action stays its own element. */
.assign-result {
    background: var(--void);
    border: 1px solid var(--line);
    border-radius: 3px;
    padding: 0.6rem 0.8rem;
    color: var(--ink);
    transition: border-color 0.12s ease, background 0.12s ease;
}

.assign-result:hover,
.assign-result:focus-within {
    border-color: var(--gold);
    background: rgba(201, 166, 52, 0.08);
}

.assign-result-choose {
    display: block;
    width: 100%;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.assign-result-choose:focus {
    outline: none;
}

.assign-result-choose:disabled {
    opacity: 0.5;
    pointer-events: none;
}

.assign-result-title {
    font-weight: 600;
    font-size: 0.95rem;
}

.assign-result-year {
    color: var(--gold);
    font-variant-numeric: tabular-nums;
    margin-left: 0.4rem;
}

/* IMDb's classification of the candidate, carried on every row. */
.assign-result-kind {
    display: inline-block;
    margin-left: 0.5rem;
    padding: 0.05rem 0.4rem;
    border: 1px solid var(--line);
    border-radius: 2px;
    font-size: 0.6rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--steel);
    vertical-align: 0.1rem;
}

/* A candidate of a kind this listing does not hold. It keeps full contrast
   on the kind and the section it belongs to, because those two are the whole
   reason it is on screen; the title recedes to steel and the row loses its
   hover, so the rows that can be chosen are the ones that answer the
   pointer. */
.assign-result.is-unassignable {
    background: transparent;
    border-style: dashed;
}

.assign-result.is-unassignable .assign-result-choose:disabled {
    opacity: 1;
    cursor: default;
    pointer-events: auto;
}

.assign-result.is-unassignable:hover,
.assign-result.is-unassignable:focus-within {
    border-color: var(--line);
    background: transparent;
}

.assign-result.is-unassignable .assign-result-title {
    color: var(--steel);
}

.assign-result.is-unassignable .assign-result-kind {
    border-color: rgba(201, 166, 52, 0.4);
    color: var(--gold);
}

.assign-result-note {
    margin-top: 0.2rem;
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--gold);
}

.assign-result-meta {
    color: var(--steel);
    font-size: 0.8rem;
    margin-top: 0.15rem;
}

/* The IMDb id, and the way to read about a candidate before committing to
   it. Inline so the link is only as wide as the id itself, leaving the rest
   of the line to the button behind it. */
.assign-result-id {
    display: inline-block;
    margin-top: 0.15rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.8rem;
    color: var(--steel);
}

a.assign-result-id:hover,
a.assign-result-id:focus {
    color: var(--gold);
    text-decoration: underline;
}

a.assign-result-id:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
}

/* An entry waiting on the next background pass. The control is inert — the
   request is already in — so it signals by pulsing instead, which is the one
   thing on a card that says something is going to happen rather than
   something is wrong. Kept to the accent and to a slow cycle: this can sit on
   screen for a couple of minutes, and anything faster would be an alarm. */
.library-card-refresh.is-pending {
    border-color: var(--gold) !important;
    color: var(--gold) !important;
    opacity: 1;
    animation: refresh-pending 1.6s ease-in-out infinite;
}

/* The disabled rule dims every other inert control on a card, and this one is
   inert for a different reason: it is working, not unavailable. */
.library-card-refresh.is-pending:disabled {
    opacity: 1;
    pointer-events: none;
}

@keyframes refresh-pending {
    0%, 100% {
        border-color: rgba(201, 166, 52, 0.3);
        color: rgba(201, 166, 52, 0.5);
    }
    50% {
        border-color: var(--gold);
        color: var(--gold);
    }
}

/* A pulse that will not stop is exactly what someone asking for reduced
   motion is asking not to have, so it holds at its brightest instead. */
@media (prefers-reduced-motion: reduce) {
    .library-card-refresh.is-pending {
        animation: none;
    }
}

/* ── The card's Actions menu ───────────────────────────────────────────────── */
/* Everything past the queue control, behind one control. The listing is read
   down its cards rather than across their buttons, so the two used while
   reading stay open and the rest is opened when there is something to do.
   The controls inside are the card's own controls — same classes, same
   endpoints — laid out as menu rows here rather than written twice. */
.library-card-actions {
    display: block;
}

.library-card-actions > .dropdown-toggle {
    width: 100%;
    text-align: left;
}

.library-card-actions .dropdown-menu {
    min-width: 12rem;
    padding: 0.25rem;
}

/* A control inside the menu is a row of it: full width, ruled off from the
   next, and carrying no frame of its own — the menu is the frame. */
.library-card-actions .dropdown-menu .btn {
    display: block;
    width: 100%;
    margin: 0;
    text-align: left;
    background: transparent !important;
    border: 0 !important;
    border-radius: 2px;
    padding: 0.35rem 0.6rem;
}

.library-card-actions .dropdown-menu .btn + .btn {
    border-top: 1px solid var(--line) !important;
}

.library-card-actions .dropdown-menu .btn:hover:not(:disabled) {
    background: var(--void) !important;
    color: var(--gold) !important;
}

/* The one control that destroys something keeps its colour, because that is
   what it is for. */
.library-card-actions .dropdown-menu .btn-blood {
    color: var(--fault-edge) !important;
}

.library-card-actions .dropdown-menu .btn-blood:hover:not(:disabled) {
    background: var(--blood) !important;
    color: var(--ink) !important;
}

/* On a flagged card the palette is forced to white, and the menu sits on its
   own ground rather than on the red. */
.library-card-issue .library-card-actions .dropdown-menu {
    background: var(--slate) !important;
}

.library-card-issue .library-card-actions .dropdown-menu .btn {
    color: var(--ink) !important;
    border: 0 !important;
}

.library-card-issue .library-card-actions .dropdown-menu .btn + .btn {
    border-top: 1px solid var(--line) !important;
}

/* Per-card control panel, held to the right edge. Stays in the layout even
   when it holds nothing, so the detail column ends at the same place on
   every card and the listing does not ripple. */
.library-card-controls {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.3rem;
    min-width: 9.5rem;
}

.library-card-controls .btn {
    text-align: left;
    font-size: 0.72rem;
    padding: 0.3rem 0.6rem;
    letter-spacing: 0.04em;
}

/* Below the breakpoint the three columns stack: controls run along the
   bottom, and the reconciliation drops its id column under the titles
   rather than forcing a horizontal scroll. */
@media (max-width: 767px) {
    .library-card-body {
        flex-wrap: wrap;
    }
    .library-card-controls {
        flex-direction: row;
        flex-wrap: wrap;
        width: 100%;
        min-width: 0;
    }
    .library-reconcile {
        grid-template-columns: max-content minmax(0, 1fr);
    }
    .library-reconcile-id {
        grid-column: 2;
    }
}

.library-card-year {
    color: var(--gold);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* The IMDb title id, shown as the link to the title page. Rendered in a
   monospace face because it is an identifier a maintainer reads and
   compares character by character when checking a mis-pinned entry. */
.library-card-imdb {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.85rem;
    letter-spacing: 0.02em;
}

/* ── Poster showcase ──────────────────────────────────────────────────────── */
/* The artwork on a card is a thumbnail sized for scanning a listing of
   thousands. This is where a poster is shown as a poster: the page drops to
   void, the art takes the screen at the largest size that fits, and the
   cursor carries the zoom-out shape everywhere, because a click anywhere
   closes it. */
.library-card-poster-zoom {
    display: block;
    padding: 0;
    border: 0;
    background: none;
    border-radius: 2px;
    cursor: zoom-in;
}

.poster-showcase {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 1080;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.1rem;
    padding: 2rem 1.5rem;
    background: rgba(0, 10, 18, 0.95);
    cursor: zoom-out;
}

.poster-showcase.is-open {
    display: flex;
    animation: poster-showcase-enter 170ms ease-out;
}

.poster-showcase-frame {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.85rem;
    margin: 0;
    min-height: 0;
}

/* The gold hairline the cards use, carried onto the full-size art so a poster
   with dark edges still resolves against the field behind it. */
.poster-showcase-image {
    display: block;
    max-width: 92vw;
    max-height: 80vh;
    border-radius: 2px;
    background: var(--void);
    box-shadow: 0 0 0 1px rgba(201, 166, 52, 0.45), 0 24px 60px rgba(0, 0, 0, 0.75);
}

.poster-showcase-caption {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 0.6rem;
    text-align: center;
}

.poster-showcase-title {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--ink);
}

.poster-showcase-year {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    color: var(--gold);
    font-variant-numeric: tabular-nums;
}

.poster-showcase-year:empty {
    display: none;
}

/* The layer carries no close button, so the way out is said once in the
   listing's own micro-type. */
.poster-showcase-dismiss {
    margin: 0;
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--steel);
}

@keyframes poster-showcase-enter {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

/* The art comes forward as it arrives, which is the difference between a
   poster being put on display and an overlay appearing. */
.poster-showcase.is-open .poster-showcase-frame {
    animation: poster-showcase-lift 170ms ease-out;
}

@keyframes poster-showcase-lift {
    from {
        transform: scale(0.94);
    }
    to {
        transform: none;
    }
}

/* The page behind the showcase holds its place while a poster is open. */
body.poster-showcase-open {
    overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
    .poster-showcase.is-open,
    .poster-showcase.is-open .poster-showcase-frame {
        animation: none;
    }
}

/* ── Network graph controls ── */
.network-controls {
    position: absolute;
    bottom: 1rem;
    right: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    z-index: 10;
}

.network-controls .btn-net {
    width: 2rem;
    height: 2rem;
    background: var(--gold);
    color: var(--void);
    border: none;
    cursor: pointer;
    font-size: 0.85rem;
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}

.network-controls .btn-net:hover {
    background: #b8952e;
}

/* Engagement list cards — stacked vertical representation of an engagement on the engagements list view */
.engagement-card-title-wrap {
    flex: 1;
    min-width: 0;
    padding-right: 0.5rem;
}

.panel-card-header h2.engagement-card-title {
    text-transform: none;
    letter-spacing: 0;
    font-size: 1rem;
}

.engagement-card-actions {
    flex-shrink: 0;
}

.engagement-card-description {
    white-space: pre-wrap;
    font-size: 0.9rem;
    margin-bottom: 1rem;
}

.engagement-card-meta {
    font-size: 0.8rem;
    margin-bottom: 0.25rem;
}

.engagement-card-meta:last-child {
    margin-bottom: 0;
}

.empty-state {
    color: var(--steel);
    text-align: center;
}

/* Confirm modal — themed Bootstrap modal used for destructive-action prompts */
.modal-content {
    background: var(--slate);
    border: 1px solid var(--line);
    color: var(--ink);
}

.modal-header {
    border-bottom: 1px solid var(--line);
}

.modal-footer {
    border-top: 1px solid var(--line);
}

.modal-title {
    color: var(--gold);
    font-size: 0.9rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

/* ── Detail table: label/value cells used on entity detail pages ───────────── */
.detail-label {
    color: var(--steel);
    text-transform: uppercase;
    font-size: 0.75rem;
    font-weight: 600;
    border-color: var(--line);
}

.detail-label-first {
    width: 160px;
}

.detail-label-multiline {
    vertical-align: top;
}

.detail-label-last {
    color: var(--steel);
    text-transform: uppercase;
    font-size: 0.75rem;
    font-weight: 600;
    border: none;
}

.detail-value {
    border-color: var(--line);
}

.detail-value-prewrap {
    border-color: var(--line);
    white-space: pre-wrap;
}

.detail-value-last {
    border: none;
}

/* ── Form label styled to match detail-label (used on inline add forms) ───── */
.form-label-meta {
    color: var(--steel);
    text-transform: uppercase;
    font-size: 0.75rem;
    font-weight: 600;
}

/* ── Empty-state table cells in list tables ───────────────────────────────── */
.empty-state-cell {
    color: var(--steel);
    padding: 2rem;
}

.empty-state-cell-sm {
    color: var(--steel);
    padding: 1.5rem;
}

/* ── Compact text utilities used where specific class names aren't warranted */
.text-xs {
    font-size: 0.75rem;
}

.text-sm {
    font-size: 0.8rem;
}

.text-prewrap {
    white-space: pre-wrap;
}

/* ── Narrow associations-list swap column (↔) ─────────────────────────────── */
.assoc-swap-col {
    width: 3rem;
}

/* ── Network graph panel wrapper and canvas ───────────────────────────────── */
.network-panel {
    position: relative;
}

.network-canvas {
    height: 75vh;
    background: var(--void);
}

.network-canvas-fullscreen {
    height: 100vh;
}

.network-panel-fullscreen {
    background: var(--void);
}

/* ── Smaller helper text below form fields (e.g. toggle explanations) ─────── */
.form-text-small {
    font-size: 0.8rem;
}

/* ── Activity-type description-template legend (lighter card variant) ─────── */
.panel-card-subtle {
    opacity: 0.95;
}

.panel-card-subtle code {
    background: var(--void);
    padding: 0.05rem 0.3rem;
    border-radius: 3px;
    font-size: 0.85em;
}

/* Description text under a custom-activity FILE-type value rendering. */
.custom-activity-field-file-description {
    font-size: 0.85em;
    margin-top: 0.15rem;
}

/* "Current file: ..." line shown above the replace picker on the
   custom-activity edit form. */
.custom-activity-file-existing {
    font-size: 0.85em;
}

/* ── Integrations landing page — card grid ─────────────────────────────────── */
.integration-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 1rem;
}

.integration-card {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    padding: 1rem;
    border: 1px solid var(--line);
    border-radius: 4px;
    background: var(--slate);
    color: inherit;
    text-decoration: none;
    transition: border-color 120ms ease;
}

.integration-card:hover {
    border-color: var(--gold);
    text-decoration: none;
}

.integration-card-icon {
    font-size: 2rem;
    color: var(--gold);
    flex: 0 0 auto;
}

.integration-card-body {
    flex: 1 1 auto;
}

.integration-card-title {
    margin: 0 0 0.25rem 0;
    font-size: 1.1rem;
}

.integration-card-description {
    margin: 0;
    font-size: 0.85rem;
    color: var(--steel);
}

/* ── Network header filter form (Show closed engagements toggle) ──────────── */
/* The toggle itself reuses the shared .toggle-switch pattern for visual
   continuity with the engagements list filter; only the surrounding
   form margin needs resetting so the card header's vertical alignment
   stays clean. */
.network-filter-form {
    margin: 0;
}

/* ── Engagement entries rendered inside another entity's detail view ──────── */
.engagement-entry {
    padding: 1rem;
    border-bottom: 1px solid var(--line);
}

.engagement-entry-meta {
    font-size: 0.8rem;
    margin-bottom: 0.25rem;
}

.engagement-entry-description {
    white-space: pre-wrap;
    margin-bottom: 0.5rem;
}

.engagement-entry-note {
    font-size: 0.75rem;
}

.engagement-entry-comments-section {
    margin-top: 0.5rem;
}

.engagement-entry-comments-heading {
    color: var(--steel);
    font-size: 0.75rem;
    text-transform: uppercase;
    font-weight: 600;
    margin-bottom: 0.5rem;
}

/* ── Comment thread blocks (used on engagement view and nested engagement sections) ── */
.comment-block {
    border-left: 2px solid var(--line);
    padding-left: 0.75rem;
    margin-bottom: 0.5rem;
}

.comment-body {
    white-space: pre-wrap;
    margin-top: 0.25rem;
}

/* Used for activity-stream entries that are not actual comments (file
   attachments, custom activities). Same vertical offset as .comment-body
   but without the pre-wrap inheritance that would render template source
   indentation as visible whitespace inside the wrapper. */
.activity-body {
    margin-top: 0.25rem;
}

.comment-timestamp {
    font-size: 0.75rem;
}

/* ── Custom-activity grid (shared by read-only display and inline edit form) ── */
.custom-activity-input-grid {
    display: grid;
    grid-template-columns: max-content 1fr;
    column-gap: 0.75rem;
    row-gap: 0.25rem;
    align-items: center;
}

.custom-activity-input-label {
    color: var(--steel);
    font-size: 0.8125rem;
    text-align: right;
    margin-bottom: 0;
    white-space: nowrap;
    align-self: center;
}

/* Multi-line content (textareas in edit, .custom-activity-field-text in
   view) is taller than the center-aligned baseline; anchor labels to the
   top so they read with the first line of the value. */
.custom-activity-input-grid label:has(+ textarea),
.custom-activity-input-grid > .custom-activity-input-label:has(+ .custom-activity-field-value-text) {
    align-self: start;
    padding-top: 0.1rem;
}

.custom-activity-field-value {
    color: var(--ink);
    word-break: break-word;
    min-width: 0;
    font-size: 0.875rem;
}

.custom-activity-field-text {
    white-space: pre-wrap;
}

.custom-activity-field-value-number,
.custom-activity-field-value-date,
.custom-activity-field-value-datetime {
    font-variant-numeric: tabular-nums;
    color: var(--gold);
}

/* ── Engagement list search bar ───────────────────────────────────────────── */
/* Search input + submit button glue together as one pill via Bootstrap's
   input-group. The group spans the full panel-card-body width; the
   show-closed toggle sits below as a styled switch. */
.engagement-search-group {
    width: 100%;
}
.engagement-search-group .form-control {
    border-right: 0;
}
.engagement-search-group .btn {
    border-radius: 0;
}
.engagement-search-group > :last-child {
    border-top-right-radius: 3px;
    border-bottom-right-radius: 3px;
}
.engagement-search-clear {
    border-left: 0;
}
.list-filter-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 1.5rem;
}
.list-per-page-label {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--ink);
    font-size: 0.9rem;
    margin-bottom: 0;
}
.list-per-page-select {
    width: auto;
    min-width: 5rem;
}
.list-pagination {
    padding-top: 0.5rem;
}
.list-pagination-info {
    font-size: 0.85rem;
}
.btn.disabled,
.btn-outline-secondary.disabled,
.btn:disabled {
    opacity: 0.45;
    pointer-events: none;
    cursor: default;
}

/* ── Collapsible panel-card header ────────────────────────────────────────── */
/* When a panel-card's header is used as a Bootstrap collapse trigger, the
   left-hand title block gets this class. Clicking the block expands/collapses
   the body; the chevron icon inside the title flips to indicate state.
   Bootstrap's collapse plugin adds the `.collapsed` class to the trigger
   whenever its target is hidden. */
.panel-card-header-collapse {
    cursor: pointer;
    user-select: none;
}
.panel-card-collapse-icon {
    font-size: 0.8em;
    transition: transform 0.2s ease;
    color: var(--steel);
}
.panel-card-header-collapse.collapsed .panel-card-collapse-icon {
    transform: rotate(-90deg);
}
.engagement-search-toggle {
    color: var(--ink);
    font-size: 0.9rem;
}

/* ── Themed toggle switch (styled <input type="checkbox">) ────────────────── */
/* Drop-in replacement for a native checkbox when you want a switch UX. The
   real checkbox is kept in the DOM (form submission works unchanged); it's
   positioned off-screen and the visible switch is drawn via the sibling
   .toggle-switch-track span. */
.toggle-switch {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    cursor: pointer;
    user-select: none;
    margin-bottom: 0;
}
.toggle-switch-input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
    pointer-events: none;
}
.toggle-switch-track {
    position: relative;
    flex: 0 0 auto;
    width: 2.5rem;
    height: 1.25rem;
    background: var(--steel);
    border-radius: 999px;
    transition: background 0.15s ease;
}
.toggle-switch-track::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 1rem;
    height: 1rem;
    background: var(--ink);
    border-radius: 50%;
    transition: left 0.15s ease, background 0.15s ease;
}
.toggle-switch-input:checked ~ .toggle-switch-track {
    background: var(--gold);
}
.toggle-switch-input:checked ~ .toggle-switch-track::after {
    left: calc(100% - 1rem - 2px);
    background: var(--void);
}
.toggle-switch-input:focus-visible ~ .toggle-switch-track {
    box-shadow: 0 0 0 2px var(--glow-gold);
}

/* ── Separators used to divide a panel-card-body into two regions ─────────── */
.panel-section-top {
    padding: 1rem;
    border-top: 1px solid var(--line);
}

/* ── Inline association-note edit form inputs ─────────────────────────────── */
.assoc-note-form-row {
    gap: 0.5rem;
}

.assoc-note-input {
    flex: 1;
}

/* ── Auth pages (login / signup) ──────────────────────────────────────────── */
.auth-alert {
    max-width: 420px;
}

.auth-tabs-nav {
    border-color: var(--line);
}

/* ── Partial-field toggle link (+ New organization / + New person) ────────── */
.field-mode-toggle {
    font-size: 0.75rem;
}

/* ── Field-partial hidden create-row default state ────────────────────────── */
.field-create-row-hidden {
    display: none;
}

/* ── API error box — fixed, discardable, shown on HTMX non-2xx responses ──── */
.api-error-box {
    position: fixed;
    top: 1rem;
    right: 1rem;
    max-width: 420px;
    background: var(--blood);
    color: var(--ink);
    border: 1px solid #ff4444;
    border-radius: 4px;
    padding: 0.75rem 1rem;
    box-shadow: 0 4px 12px rgba(0,0,0,0.4);
    z-index: 1080;
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.api-error-box-body {
    flex: 1;
}

.api-error-box-close {
    background: transparent;
    border: none;
    color: var(--ink);
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
    padding: 0;
    opacity: 0.8;
}

.api-error-box-close:hover {
    opacity: 1;
}

/* ── Network graph hover preview card ─────────────────────────────────────── */
/* Cards are spawned on node click, persist until dismissed via the close box,
   support drag-repositioning by the header, and carry an "Open" link to the
   entity's full detail page. Data is fetched fresh per card open so the
   content reflects the current record without reloading the graph. */
.network-card-layer {
    position: absolute;
    inset: 0;
    pointer-events: none;  /* the layer itself never blocks the canvas */
}

.network-card-layer > .network-card {
    pointer-events: auto;  /* individual cards are fully interactive */
}

.network-card {
    position: fixed;
    background: var(--slate);
    border: 1px solid var(--line);
    border-radius: 4px;
    box-shadow: 0 8px 30px rgba(0,0,0,0.5);
    color: var(--ink);
    width: 320px;
    max-width: 90vw;
    z-index: 1095;
    display: flex;
    flex-direction: column;
}

.network-card-dragging {
    user-select: none;
    box-shadow: 0 12px 40px rgba(0,0,0,0.7);
}

.network-card-header {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    padding: 0.6rem 0.75rem;
    border-bottom: 1px solid var(--line);
    cursor: move;
}

.network-card-header-text {
    flex: 1;
    min-width: 0;
}

.network-card-title {
    color: var(--gold);
    font-size: 0.95rem;
    font-weight: 600;
    margin-bottom: 0.15rem;
    word-break: break-word;
}

.network-card a {
    color: var(--gold);
    text-decoration: none;
}

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

.network-card-subtitle {
    color: var(--steel);
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.network-card-refresh,
.network-card-close {
    background: transparent;
    border: none;
    color: var(--steel);
    line-height: 1;
    padding: 0;
    cursor: pointer;
    opacity: 0.7;
    flex-shrink: 0;
    width: 1.5rem;
    height: 1.5rem;
    font-size: 1rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.network-card-refresh:hover,
.network-card-close:hover {
    opacity: 1;
    color: var(--ink);
}

.network-card-refresh:disabled {
    cursor: default;
    opacity: 0.5;
}

/* Merge badge on organization cards. Styled as a compact pill that
   sits alongside the header refresh/close icons. Colored like a gold
   action since it's a destructive-but-deliberate operation (moves
   data and deletes the source). */
.network-card-merge {
    background: transparent;
    border: 1px solid var(--gold);
    color: var(--gold);
    border-radius: 999px;
    padding: 0.1rem 0.6rem;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    cursor: pointer;
    line-height: 1.2;
    margin-right: 0.25rem;
    flex-shrink: 0;
}

.network-card-merge:hover {
    background: var(--gold);
    color: var(--void);
}

.network-card-body {
    padding: 0.6rem 0.75rem;
    overflow-y: auto;
    max-height: 60vh;
}

.network-card-fields {
    margin: 0;
    font-size: 0.8rem;
}

.network-card-fields:empty {
    display: none;
}

.network-card-fields dt {
    color: var(--steel);
    text-transform: uppercase;
    font-size: 0.65rem;
    letter-spacing: 0.06em;
    font-weight: 600;
    margin-top: 0.4rem;
}

.network-card-fields dt:first-child {
    margin-top: 0;
}

.network-card-fields dd {
    margin: 0;
    color: var(--ink);
    white-space: pre-wrap;
    word-break: break-word;
}

.network-card-comments:not(:empty) {
    margin-top: 0.6rem;
}

.network-card-comments-heading {
    color: var(--steel);
    text-transform: uppercase;
    font-size: 0.65rem;
    letter-spacing: 0.06em;
    font-weight: 600;
    margin-bottom: 0.35rem;
}

.network-card-comment {
    border-left: 2px solid var(--line);
    padding-left: 0.6rem;
    margin-bottom: 0.4rem;
}

.network-card-comment:last-child {
    margin-bottom: 0;
}

.network-card-comment-meta {
    color: var(--steel);
    font-size: 0.7rem;
    margin-bottom: 0.15rem;
}

.network-card-comment-body {
    font-size: 0.8rem;
    white-space: pre-wrap;
    word-break: break-word;
}

.network-card-actions {
    padding: 0.5rem 0.75rem;
    border-top: 1px solid var(--line);
    display: flex;
    justify-content: flex-end;
}

.network-card-actions:empty {
    display: none;
}
