/* body {
    font-family: Arial, sans-serif;
    background-color: #2a3a4f;
    color: #8d97ad;
} */

.grid-container {
    padding: 20px;
}

.game-info {
    bottom: 0;
    left: 0;
    text-align: left !important;
    /* background-image: linear-gradient(to top, rgba(7, 0, 91, 0.49), #003aa800); */
    background-image: linear-gradient(to top, rgba(7, 0, 91, 0.71), #001233a1);
    padding: 10px;
    border-radius: 0 0 8px 8px;
    border-top: 1px solid #d5d5d5bd;
    top: unset;
}

.game-tag {
    font-size: 1em;
    margin-right: 5px;
}

.card {
    border-radius: 8px !important;
    /* linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent 50%), */
}

.card-img {
    mask-image: linear-gradient(to top, rgba(7, 0, 91, 0.71), #001233ed);
    border-bottom-right-radius: 8px;
    border-bottom-left-radius: 8px;
}

/* Stands in for a missing banner: the card keeps the height it would have had, so a
   row of cards stays even whatever artwork each one turned out to have. */
.card-art-fallback {
    aspect-ratio: 16 / 9;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    background-color: var(--bs-secondary-bg);
}

.card-art-fallback .card-art-icon {
    height: 100%;
    border-radius: 8px;
}

.art-missing {
    color: var(--bs-secondary-color);
    font-size: 0.875rem;
    text-align: center;
}

.game-title {
    font-size: 1.1rem;
    /* font-size: 18px; */
    font-weight: bold;
    margin-bottom: 5px;
    color: white;
}

p.game-description {
    margin-bottom: 0;
    font-size: 0.875rem;
}

.tags-container {
    position: absolute;
    bottom: 10px;
    right: 10px;
    margin-top: 10px;
}

/* A title id is one unbreakable 16-character token: without this it pushes the
   overlay wider than the card and hangs off its edge. */
.game-title,
p.game-description {
    overflow-wrap: break-word;
}

/* The metadata can only be an overlay while the banner is wide enough to hold it. On
   a phone the text wraps to several lines, the overlay outgrows the image it is
   anchored to, and the badges - placed from the card's own corner - land on top of
   that text. Below md it stops being an overlay and becomes a block under the banner,
   where growing costs nothing. */
@media (max-width: 767.98px) {
    .game-info {
        position: static;
    }

    .card-img {
        /* The fade was there to keep text legible over the banner; nothing sits on it
           once the metadata is below. */
        mask-image: none;
        border-bottom-right-radius: 0;
        border-bottom-left-radius: 0;
    }

    .tags-container {
        position: static;
        display: flex;
        justify-content: flex-end;
        margin-top: .5rem;
    }

    .game-tag:last-child {
        margin-right: 0;
    }
}

.btn-outline-info {
    --bs-btn-hover-color: #fff;
}

.text-bg-warning {
    color: #fff !important;
    background-color: RGBA(var(--bs-warning-rgb),var(--bs-bg-opacity,1)) !important;
    font-weight: normal;
}

.text-bg-info {
    color: #fff !important;
    background-color: RGBA(var(--bs-info-rgb),var(--bs-bg-opacity,1)) !important;
    font-weight: normal;
  }

.col-1, .col-2, .col-3, .col-4, .col-6, .col-12 {
    display: flex;
    flex-direction: column;
}

.col-1 > .card, .col-2 > .card, .col-3 > .card, .col-4 > .card, .col-6 > .card, .col-12 > .card {
    flex: 1;
}

/* Update popover */
.popover-body {
    max-height: 200px; /* Set your desired max height */
    overflow-y: auto; /* Enable vertical scrolling */
}

/* Input code form */
.form-control-code {
    font-family: monospace;
    font-size: .875em;
    background-color: var(--bs-dark-bg-subtle);
    color: var(--bs-code-color);
}

/* hr */
.hr-settings {
    margin-top: 2rem !important;
}

/* Settings / Tasks sidebar */
.settings-sidebar {
    position: sticky;
    top: 0;
    align-self: flex-start;
    width: 200px;
    flex: 0 0 200px;
    height: 100vh;
    padding-top: 1rem;
}

.settings-sidebar .nav-link {
    color: var(--bs-secondary-color);
    border-radius: 0;
    border-left: 3px solid transparent;
}

.settings-sidebar .nav-link:hover {
    color: var(--bs-body-color);
    background-color: var(--bs-secondary-bg);
}

.settings-sidebar .nav-link.active {
    color: var(--bs-body-color);
    background-color: var(--bs-secondary-bg);
    border-left-color: var(--bs-primary);
}

.settings-panel {
    min-width: 0;  /* let long task names shrink instead of widening the row */
}

/* Below md the sidebar becomes a row of tabs across the top. 767.98 rather than 768,
   so the switch happens exactly where Bootstrap's own md rules take over. */
@media (max-width: 767.98px) {
    .settings-sidebar {
        position: static;
        width: 100%;
        flex-basis: 100%;
        height: auto;
        /* The divider now separates the tabs from the panel below them, not from a
           column beside them. */
        border-right: 0 !important;
        border-bottom: var(--bs-border-width) solid var(--bs-border-color);
    }

    .settings-sidebar .nav {
        flex-direction: row !important;
    }

    /* The panel's flex-basis is 0, so a full-width sidebar alone never overflows the
       row and the two stay on one line: the sidebar takes all of it and the panel is
       squeezed to nothing against the right edge. Giving the panel a basis of its own
       is what pushes it onto the next line. */
    .settings-panel {
        flex-basis: 100%;
    }

    /* A left border marks the active item in a column of links; in a row of tabs it
       belongs underneath. */
    .settings-sidebar .nav-link {
        border-left: 0;
        border-bottom: 3px solid transparent;
    }

    .settings-sidebar .nav-link.active {
        border-bottom-color: var(--bs-primary);
    }
}

/* Task list */
.task-list {
    list-style: none;
    padding-left: 0;
    margin-bottom: 0;
}

.task-row {
    margin-bottom: .5rem;
    transition: opacity .5s ease-out;
}

/* The queue is unbounded — a library scan queues one task per file — so it scrolls
   inside a fixed frame instead of running the page off the bottom of the screen.
   The gutter and padding keep the scrollbar off the cancel button, which sits flush
   right and is otherwise covered by an overlay scrollbar the moment the list overflows. */
.task-scroll {
    max-height: 26rem;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-gutter: stable;
    padding-right: .75rem;
}

.task-row.task-fading {
    opacity: 0;
}

.task-head {
    display: flex;
    align-items: center;
    gap: .5rem;
}

.task-name {
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.task-meta {
    margin-left: auto;
    font-size: .875rem;
    white-space: nowrap;
}

/* Bare icon buttons: the rows are already dense, and bordered buttons competed with the
   status icons for attention. Muted by default; the per-row cross opts into danger. */
.icon-btn {
    padding: 0;
    border: 0;
    background: none;
    line-height: 1;
    color: var(--bs-secondary-color);
}

.icon-btn:hover:not(:disabled) {
    color: var(--bs-body-color);
}

.icon-btn:disabled {
    opacity: .25;
}

.task-cancel {
    font-size: .8rem;
    color: var(--bs-danger);
}

.task-cancel:hover:not(:disabled) {
    color: var(--bs-danger-text-emphasis);
}

/* Matches the scroll container's padding, so the section's action lines up with the
   per-row crosses below it rather than sitting a scrollbar's width to their right. */
.task-section-head {
    display: flex;
    align-items: center;
    padding-right: .75rem;
}

/* Too slim for a label, so the percentage is shown in the row's meta text instead. */
.task-progress {
    height: .8rem;
    margin-top: .25rem;
}

/* The bar is driven by percentages arriving a few times a second; easing the width
   turns those steps into continuous movement. */
.task-progress .progress-bar {
    transition: width .3s linear;
}

.task-children {
    list-style: none;
    padding-left: 1.25rem;
    margin: .375rem 0 0;
    border-left: 2px solid var(--bs-border-color);
}

.task-children:empty {
    display: none;
}


/* Stats */
.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: 1.25rem;
}

.stat-value {
    font-size: 1.75rem;
    font-weight: 600;
    line-height: 1.1;
}

.stat-label {
    font-weight: 500;
}

.stat-note {
    color: var(--bs-secondary-color);
    font-size: .8rem;
}

/* Two columns rather than auto-fit: four sections in a row would put each table's
   figures a screen away from the next, and the tables are all the same shape anyway. */
.stat-sections {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 2.5rem;
}

@media (max-width: 767.98px) {
    .stat-sections {
        grid-template-columns: 1fr;
    }
}

/* Three short columns: left to itself the table would stretch across its half of the
   panel and strand the figures a screen away from their labels. */
.stat-table {
    max-width: 30rem;
}

.stat-table th:not(:first-child),
.stat-table td:not(:first-child) {
    text-align: right;
    white-space: nowrap;
}

/* A library root is a full path, and it now shares the row with a second table:
   wrapping keeps the figures beside it rather than pushed out of the column. */
.stat-table td:first-child {
    overflow-wrap: anywhere;
}

/* Admin Files page. Its own soft palette, one tone per state: `--files-<tone>` for text and
   icons, `-bg` for the tint behind them. Named after Bootstrap's tones so a tone reads the
   same in the template and the script. */
.files-page {
    --files-success: #1a7f4b;
    --files-success-bg: #e1f3e8;
    --files-info: #0b6f91;
    --files-info-bg: #dff1f7;
    --files-warning: #9a6512;
    --files-warning-bg: #fcf0d6;
    --files-danger: #b83434;
    --files-danger-bg: #fae4e4;
    --files-primary: #0d6efd;
    --files-primary-bg: #e6effe;
    --files-secondary: #68707a;
    --files-secondary-bg: #eceef1;
    --files-off: #c3c8ce;
    --files-surface: #f1f3f5;
    --files-surface-hover: #e7eaee;
}

[data-bs-theme=dark] .files-page {
    --files-success: #75b798;
    --files-success-bg: #16301f;
    --files-info: #6edff6;
    --files-info-bg: #0f2c34;
    --files-warning: #f2c95c;
    --files-warning-bg: #362b0e;
    --files-danger: #ea868f;
    --files-danger-bg: #3a1a1d;
    --files-primary: #6ea8fe;
    --files-primary-bg: #1b2b45;
    --files-secondary: #9aa3ad;
    --files-secondary-bg: #2f343a;
    --files-off: #525a63;
    --files-surface: #2b3035;
    --files-surface-hover: #2f343a;
}

.files-fg-success { color: var(--files-success); }
.files-fg-info { color: var(--files-info); }
.files-fg-warning { color: var(--files-warning); }
.files-fg-danger { color: var(--files-danger); }
.files-fg-primary { color: var(--files-primary); }

/* A soft tint with its tone's text, no border: states read by colour, not by chrome. */
.files-pill {
    --tone: var(--files-secondary);
    --tone-bg: var(--files-secondary-bg);
    display: inline-block;
    padding: .12em .6em;
    border-radius: 999px;
    background: var(--tone-bg);
    color: var(--tone);
    font-size: .72rem;
    font-weight: 500;
    line-height: 1.5;
    white-space: nowrap;
    vertical-align: middle;
}

.files-pill[data-tone=success] { --tone: var(--files-success); --tone-bg: var(--files-success-bg); }
.files-pill[data-tone=info] { --tone: var(--files-info); --tone-bg: var(--files-info-bg); }
.files-pill[data-tone=warning] { --tone: var(--files-warning); --tone-bg: var(--files-warning-bg); }
.files-pill[data-tone=danger] { --tone: var(--files-danger); --tone-bg: var(--files-danger-bg); }
.files-pill[data-tone=primary] { --tone: var(--files-primary); --tone-bg: var(--files-primary-bg); }

.files-page .form-check-input:checked {
    background-color: var(--files-primary);
    border-color: var(--files-primary);
}

.files-btn-danger {
    border: 0;
    background: var(--files-danger);
    color: var(--bs-body-bg);
    font-weight: 500;
}

.files-btn-danger:hover:not(:disabled) {
    background: var(--files-danger);
    color: var(--bs-body-bg);
    filter: brightness(1.08);
}

.files-btn-danger:disabled {
    background: var(--files-danger);
    color: var(--bs-body-bg);
    opacity: .4;
}

/* The tiles are the page's tabs: each is a preset view with its count. */
.files-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
    gap: .75rem;
    margin-bottom: 1.25rem;
}

/* Label at the top, subtitle at the bottom, the count between them. */
.files-tile {
    position: relative;
    display: grid;
    grid-template-rows: auto 1fr auto;
    align-items: center;
    min-height: 8.5rem;
    padding: .9rem 1rem;
    border-radius: .75rem;
    background: var(--files-surface);
    color: var(--bs-body-color);
    text-decoration: none;
}

.files-tile:hover {
    background: var(--files-surface-hover);
}

.files-tile.active {
    background: var(--files-primary-bg);
}

.files-tile.active {
    box-shadow: inset 0 0 0 1.5px var(--files-primary);
}

/* Nothing to report: a check mark takes the tile's free side. */
.files-tile-ok {
    display: none;
    position: absolute;
    top: 50%;
    right: 1rem;
    transform: translateY(-50%);
    font-size: 4rem;
    line-height: 1;
    color: var(--files-success);
}

.files-tile.ok .files-tile-ok {
    display: block;
}

.files-tile-label,
.files-tile-sub {
    font-size: .8rem;
    color: var(--bs-secondary-color);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.files-tile-count {
    font-size: 2rem;
    font-weight: 600;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

.files-tile-dot {
    position: absolute;
    top: .8rem;
    right: .8rem;
    width: .5rem;
    height: .5rem;
    border-radius: 50%;
}

/* What the view lists, beside the settings it depends on. */
.files-status {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem 1.5rem;
    margin-bottom: 1rem;
}

.files-status p {
    flex: 1 1 24rem;
    margin: 0;
    font-size: .875rem;
    color: var(--bs-secondary-color);
}

.files-settings {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
}

/* A setting as a chip: its state as a dot and a value, the gear saying it opens the setting. */
.files-setting {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    padding: .3rem .75rem;
    border-radius: 999px;
    background: var(--files-surface);
    color: var(--bs-body-color);
    font-size: .8rem;
    text-decoration: none;
    white-space: nowrap;
}

.files-setting:hover {
    background: var(--files-surface-hover);
    color: var(--bs-body-color);
}

.files-setting b {
    font-weight: 500;
    color: var(--files-secondary);
}

.files-setting .bi {
    font-size: .75rem;
    color: var(--files-secondary);
}

.files-setting-dot {
    width: .45rem;
    height: .45rem;
    border-radius: 50%;
    background: var(--files-off);
}

.files-setting-dot[data-tone=success] { background: var(--files-success); }
.files-setting-dot[data-tone=warning] { background: var(--files-warning); }

.files-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    margin-bottom: .75rem;
}

.files-search {
    position: relative;
    flex: 1 1 16rem;
    max-width: 24rem;
}

.files-search > .bi {
    position: absolute;
    left: .6rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--bs-secondary-color);
    pointer-events: none;
}

.files-search input {
    padding-left: 1.9rem;
}

.files-chips {
    display: contents;
}

.files-filter-chip {
    display: inline-flex;
    align-items: center;
    border-radius: 999px;
    background: var(--files-surface);
    font-size: .8rem;
}

.files-filter-chip > button {
    border: 0;
    background: none;
    color: inherit;
}

.files-filter-chip > button:first-child {
    padding: .2rem .15rem .2rem .7rem;
}

.files-filter-chip > button:last-child {
    padding: .2rem .5rem .2rem .15rem;
    color: var(--bs-secondary-color);
}

.files-filter-chip .dropdown-item,
.files-toolbar .dropdown-menu {
    font-size: .8rem;
    cursor: pointer;
}

.files-chip {
    display: inline-block;
    padding: 0 .4em;
    border-radius: .25rem;
    background: var(--files-surface);
    color: var(--files-secondary);
    font-family: var(--bs-font-monospace);
    font-size: .66rem;
    font-weight: 600;
    line-height: 1.6;
    letter-spacing: .02em;
    text-transform: uppercase;
}

/* Rows separated by a hairline and a hover tint. Fixed layout: the widths come from the
   colgroup alone, so what a cell or a filter holds never resizes a column; a cell clips
   what it cannot wrap. */
.files-table {
    --bs-table-bg: var(--bs-body-bg);
    --bs-table-hover-bg: var(--files-surface);
    --bs-table-hover-color: var(--bs-body-color);
    --bs-border-color: var(--bs-border-color-translucent);
    font-size: .875rem;
    table-layout: fixed;
    width: 100%;
}

.files-table > :not(caption) > * > * {
    padding: .6rem .5rem;
    vertical-align: middle;
    overflow: hidden;
    text-overflow: ellipsis;
}

.files-table thead th {
    border-bottom: 0;
    color: var(--bs-secondary-color);
    font-size: .68rem;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    white-space: nowrap;
}

.files-suggest {
    position: fixed;
    max-width: 28rem;
    font-size: .8rem;
}

.files-suggest .dropdown-item {
    overflow: hidden;
    text-overflow: ellipsis;
}

.files-sortable {
    cursor: pointer;
    user-select: none;
}

.files-sortable:hover {
    color: var(--bs-body-color) !important;
}

.files-num {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.files-table tbody tr {
    cursor: pointer;
}

@media (max-width: 575.98px) {
    .files-table .files-wide {
        display: none;
    }

    .files-table col.files-wide {
        width: 0 !important;
    }
}

.files-table tbody tr.files-selected > td {
    --bs-table-bg-state: var(--files-primary-bg);
}

.files-busy {
    opacity: .5;
}

/* A row leads with the title's art, then three lines fading in weight: the title, what the
   file carries, and the file name - or what the view is about. */
.files-row {
    display: flex;
    align-items: center;
    gap: .9rem;
    min-width: 0;
}

.files-row-lines {
    display: grid;
    gap: .05rem;
    min-width: 0;
}

.files-row-lines > div {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.files-row-title {
    font-weight: 500;
}

.files-row-content {
    font-size: .8rem;
    color: var(--files-secondary);
}

.files-row-sub {
    font-size: .75rem;
    color: var(--bs-tertiary-color);
}

.files-avatar {
    flex: none;
    width: 4rem;
    height: 4rem;
    border-radius: .65rem;
    object-fit: cover;
    background: var(--files-surface);
    font-size: 1.5rem;
}

.files-avatar.sm {
    width: 1.6rem;
    height: 1.6rem;
    border-radius: .35rem;
    font-size: .8rem;
}

.files-avatar.lg {
    width: 6rem;
    height: 6rem;
    border-radius: 1rem;
    font-size: 2.25rem;
}

/* The title needs the width more than the art does on a phone. */
@media (max-width: 575.98px) {
    .files-avatar:not(.sm):not(.lg) {
        width: 3rem;
        height: 3rem;
        font-size: 1.1rem;
    }
}

.files-avatar-none {
    display: grid;
    place-items: center;
    color: var(--files-secondary);
}

/* Identified, verification, compressed, organized: always in that order, so a column
   of them scans for the one that stands out. */
.files-state {
    display: inline-flex;
    gap: .6rem;
}

.files-off {
    color: var(--files-off);
}

.files-body {
    display: flex;
    align-items: flex-start;
    gap: 1.5rem;
}

.files-main {
    flex: 1;
    min-width: 0;
}

.files-drawer {
    flex: none;
    position: sticky;
    top: 1rem;
    width: 24rem;
    max-height: calc(100vh - 2rem);
    overflow-y: auto;
    padding-left: 1.25rem;
    border-left: 1px solid var(--bs-border-color-translucent);
    display: grid;
    gap: 1.25rem;
    font-size: .85rem;
}

@media (max-width: 991.98px) {
    .files-drawer {
        position: fixed;
        z-index: 1045;
        top: 0;
        right: 0;
        bottom: 0;
        width: min(24rem, 100vw);
        max-height: none;
        padding: 1rem;
        background: var(--bs-body-bg);
        box-shadow: var(--bs-box-shadow-lg);
    }
}

.files-drawer h6 {
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--bs-secondary-color);
    margin-bottom: .5rem !important;
}

.files-drawer-head {
    display: flex;
    align-items: center;
    gap: .75rem;
}

.files-drawer-head > div {
    min-width: 0;
    overflow-wrap: anywhere;
}

.files-drawer-actions {
    display: flex;
    flex-wrap: wrap;
    gap: .4rem;
}

.files-verdicts {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: .4rem;
    margin: .5rem 0;
}

.files-verdicts > div {
    display: grid;
    padding: .35rem .5rem;
    border-radius: .4rem;
    background: var(--files-surface);
    font-size: .75rem;
}

.files-title-link {
    color: inherit;
    text-decoration: none;
}

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

.files-title-link:hover .files-avatar {
    filter: brightness(1.12);
}

.files-error-lines {
    white-space: pre-line;
}

.files-app-id {
    font-family: var(--bs-font-monospace);
    font-size: .8em;
    color: var(--bs-secondary-color);
}

.files-kv {
    display: grid;
    grid-template-columns: 6rem 1fr;
    gap: .3rem .75rem;
    margin: 0;
}

.files-kv dt {
    font-weight: normal;
    color: var(--bs-secondary-color);
}

.files-kv dd {
    margin: 0;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* Duplicates and outdated updates: each group's copies side by side, only the
   attributes that tell them apart. */
.files-groups {
    display: grid;
    gap: 1.75rem;
}

.files-cmp-head {
    display: flex;
    align-items: center;
    gap: .6rem;
    margin-bottom: .4rem;
}

.files-cmp-free {
    margin-left: auto;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.files-cmp {
    width: 100%;
    table-layout: fixed;
    border-collapse: collapse;
    font-size: .85rem;
}

.files-cmp th,
.files-cmp td {
    padding: .4rem .6rem;
    border-top: 1px solid var(--bs-border-color-translucent);
    vertical-align: top;
    overflow-wrap: anywhere;
}

.files-cmp thead th {
    border-top: 0;
    font-weight: normal;
}

.files-cmp th[scope=row] {
    width: 8rem;
    font-weight: normal;
    color: var(--bs-secondary-color);
}

.files-cmp-name {
    cursor: pointer;
}

.files-cmp-name:hover {
    text-decoration: underline;
}

.files-decides {
    background: var(--files-warning-bg);
}

.files-cmp-off .files-cmp,
.files-cmp-off .files-cmp-free {
    opacity: .45;
}

.files-pager {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: .75rem;
    color: var(--bs-secondary-color);
    font-size: .8rem;
}

/* Beside a settings toggle: what it acts on in the library now, opening the Files page on it. */
.setting-stat {
    display: inline-block;
    margin-left: .5rem;
    padding: .05rem .6rem;
    border-radius: 999px;
    background: var(--bs-tertiary-bg);
    color: var(--bs-body-color);
    font-size: .75rem;
    text-decoration: none;
    vertical-align: middle;
}

.setting-stat:hover {
    background: var(--bs-secondary-bg);
    color: var(--bs-body-color);
}

.setting-stat-none {
    color: var(--bs-secondary-color);
}

/* The library grid's one-line summary above the cards. */
.library-stats {
    color: var(--bs-secondary-color);
    text-align: center;
}

.library-stats span + span::before {
    content: " · ";
}

.spin {
    display: inline-block;
    animation: spin 1.4s linear infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
  margin-top: 0 !important;
  margin-bottom: 0 !important;
}

/* Only highlight the active filter label, not the entire row */
.btn-check:active + .btn-outline-primary,
.btn-check:focus + .btn-outline-primary,
.btn-check:checked + .btn-outline-primary {
    background-color: #0d6efd;
    color: #fff;
    outline: none;
    border-color: #0d6efd;
}

.btn-group:focus-within,
.btn-group:active,
.btn-group:focus,
.dropdown-item:active,
.dropdown-item:focus {
    background: none;
    box-shadow: none;
}

/* ---------- title details page ---------- */

/* The banner is the page background: full bleed and fixed behind everything, so the
   panel slides over stationary artwork.
   The navbar rides on top of the artwork rather than boxing it in. */
.title-nav {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 3;
    background: linear-gradient(180deg, rgba(16, 18, 20, .78), rgba(16, 18, 20, 0));
}

.title-banner {
    position: fixed;
    inset: 0;
    z-index: -1;
    background-color: var(--bs-body-bg);
    background-size: cover;
    background-position: center 26%;
}

/* Its own layer rather than a child of the banner: the darkening is there to keep the
   navbar and the panel edge legible, which it can only do by staying where they are. */
.title-scrim {
    position: fixed;
    inset: 0;
    z-index: -1;
    background: linear-gradient(180deg, rgba(18, 20, 23, .3) 0%, rgba(18, 20, 23, .1) 40%, rgba(18, 20, 23, .5) 100%);
}

/* Half the viewport of artwork before the panel takes over. A title with no banner has
   nothing to show there, so the gap shrinks to what the icon needs. */
.title-hero {
    height: 75vh;
}

.title-page--nobanner .title-hero {
    height: 20vh;
}

.title-panel {
    position: relative;
    min-height: 25vh;
    padding: 1rem 1.75rem 2.75rem;
    /* Translucent, so the artwork stays present behind the panel. The blur is what
       carries the legibility at this alpha - without it the text fights the artwork. */
    background: rgba(33, 37, 41, .72);
    backdrop-filter: blur(18px);
    border-top: 1px solid rgba(255, 255, 255, .16);
}

/* The panel's sections are rendered into one container, so the spacing between them
   belongs here - on their own parent - rather than on the panel. */
.title-body {
    display: flex;
    flex-direction: column;
    gap: 1.6rem;
}

/* Only the icon straddles the panel's edge - the name and byline stay below it. */
.title-head {
    display: flex;
    align-items: center;
    gap: 1.6rem;
}

.title-icon {
    width: 256px;
    height: 256px;
    flex: 0 0 auto;
    align-self: flex-start;
    margin-top: calc(-128px - 1rem);
    border-radius: 28px;
    border: 2px solid rgba(255, 255, 255, .24);
    box-shadow: 0 .75rem 1.9rem rgba(0, 0, 0, .6);
    object-fit: cover;
    background-color: var(--bs-secondary-bg);
}

.title-icon--empty {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 3rem;
    color: var(--bs-secondary-color);
}

.title-head-text {
    display: flex;
    flex-direction: column;
    gap: .25rem;
    min-width: 0;
}

.title-name {
    font-size: 2.4rem;
    font-weight: 600;
    letter-spacing: -.01em;
    text-wrap: balance;
}

.title-byline {
    margin: 0;
    color: var(--bs-secondary-color);
    font-size: .9rem;
}

.title-mono {
    font-family: var(--bs-font-monospace);
    font-size: .82rem;
}

/* Metadata beside the description; the content table below spans the whole panel. */
.title-grid {
    display: grid;
    grid-template-columns: 276px minmax(0, 1fr);
    gap: 2.1rem;
    align-items: start;
}

/* The screenshots are a column of this grid, not a box nested in the description, so
   their size is set here rather than left over from whatever the description takes.
   640px is 360px tall at 16:9, which is what the frame below holds them to. */
.title-grid.has-shots {
    grid-template-columns: 276px minmax(0, 78ch) minmax(640px, 1fr);
}

/* The description stops at its own reading width, so on a wide screen the slack all
   piles up between it and the screenshots. Give that slack to the screenshots' track
   and sit them in the middle of it instead of pinned to the far edge. */
.title-grid.has-shots .title-shots {
    justify-self: center;
    /* Centring makes the item shrink to fit, and the frame's layers are absolute - so
       without this it collapses to the width of the dots. */
    width: 640px;
}

.title-facts {
    background-color: var(--bs-tertiary-bg);
    border: 1px solid var(--bs-border-color-translucent);
    border-radius: .55rem;
    padding: .25rem .9rem;
}

.title-fact {
    display: flex;
    justify-content: space-between;
    gap: .9rem;
    padding: .45rem 0;
    border-bottom: 1px solid var(--bs-border-color-translucent);
    font-size: .86rem;
}

.title-fact:last-child {
    border-bottom: 0;
}

.title-fact-key {
    color: var(--bs-secondary-color);
    white-space: nowrap;
}

.title-fact-value {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.title-about {
    display: flex;
    flex-direction: column;
    gap: .75rem;
    min-width: 0;
    max-width: 78ch;
}

.title-intro {
    margin: 0;
    font-size: 1.05rem;
    font-style: italic;
}

.title-desc {
    margin: 0;
    color: var(--bs-secondary-color);
    white-space: pre-line;
}

/* Store descriptions run to several paragraphs; the page opens on the first few lines. */
.title-desc.is-clamped {
    display: -webkit-box;
    -webkit-line-clamp: 5;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.title-more {
    align-self: flex-start;
    background: none;
    border: 0;
    padding: 0;
    color: var(--bs-info);
    font-size: .86rem;
}

/* One screenshot at a time, at the width of its column: six thumbnails were too small
   to read, and the store art is half of what a title page is for. */
.title-shots {
    min-width: 0;
}

.title-slideshow {
    position: relative;
    min-width: 0;
    /* The frame keeps the height, so the layers inside it can be stacked and swapped
       without the column reflowing between two screenshots. */
    aspect-ratio: 16 / 9;
    /* Contain, not cover: the list mixes in the odd portrait promo image, and cropping
       the middle out of one is worse than letterboxing it. */
    background-color: rgba(0, 0, 0, .35);
    border: 1px solid var(--bs-border-color-translucent);
    border-radius: .45rem;
    overflow: hidden;
    cursor: zoom-in;
}

.title-slide {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
    opacity: 0;
    transition: opacity .45s ease;
}

.title-slide.is-current {
    opacity: 1;
}

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

.title-slide-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background-color: rgba(18, 20, 23, .55);
    color: rgba(255, 255, 255, .75);
    font-size: 1.1rem;
    line-height: 1;
    opacity: 0;
    transition: opacity .15s ease;
}

.title-slideshow:hover .title-slide-nav,
.title-slide-nav:focus-visible {
    opacity: 1;
}

.title-slide-nav--prev { left: .5rem; }
.title-slide-nav--next { right: .5rem; }

.title-slide-nav:hover {
    background-color: rgba(18, 20, 23, .8);
    color: #fff;
}

.title-slide-dots {
    display: flex;
    justify-content: center;
    gap: .4rem;
    margin-top: .6rem;
}

.title-slide-dot {
    width: 7px;
    height: 7px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background-color: var(--bs-border-color);
}

.title-slide-dot.is-current {
    background-color: var(--bs-secondary-color);
}

/* Full-size viewer: bare arrows either side, muted until you reach for them. */
.title-lightbox {
    display: flex;
    align-items: center;
    gap: .5rem;
    width: 100%;
}

/* Shrinkable, so the arrows stay inside the dialog rather than either side of it, and
   a tall screenshot cannot outgrow the window. */
.title-lightbox img {
    min-width: 0;
    max-height: 80vh;
    object-fit: contain;
}

.title-shot-nav {
    flex: 0 0 auto;
    background: none;
    border: 0;
    padding: .25rem .5rem;
    font-size: 2rem;
    line-height: 1;
    color: var(--bs-secondary-color);
}

.title-shot-nav:hover {
    color: #fff;
}

.title-shot-count {
    margin: .5rem 0 0;
    text-align: center;
    color: var(--bs-secondary-color);
    font-size: .85rem;
    font-variant-numeric: tabular-nums;
}

.title-missing {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: .75rem;
}

/* The content list reads over the artwork, so Bootstrap's opaque accordion paint comes
   off and the rows are separated by rules alone. */
.title-content .accordion,
.title-content .accordion-item,
.title-content .accordion-button,
.title-content .accordion-body {
    background: transparent;
    color: inherit;
}

.title-content .accordion-item {
    border: 0;
}

.title-content .accordion-button {
    padding: .6rem .25rem;
    font-weight: 600;
    box-shadow: none;
    border-top: 1px solid var(--bs-border-color-translucent);
}

/* The caret is a background image, so it is inverted rather than recoloured. */
.title-content .accordion-button::after {
    margin-left: 0;
    filter: invert(1) grayscale(1) opacity(.6);
}

.title-content .accordion-body {
    padding: 0 0 .5rem;
}

/* One template on the section, inherited by the header and every row, so the columns
   line up across the base row and both accordion bodies. */
.title-content {
    --title-columns: minmax(0, 1fr) 6rem 7rem 7rem 6rem;
}

.title-row {
    display: grid;
    grid-template-columns: var(--title-columns);
    align-items: center;
    gap: .75rem;
    padding: .6rem .25rem;
    border-top: 1px solid var(--bs-border-color-translucent);
}

.title-row--head {
    border-top: 0;
    padding-bottom: .35rem;
    font-size: .85rem;
    letter-spacing: .09em;
    text-transform: uppercase;
    font-weight: 600;
    white-space: nowrap;
    color: var(--bs-secondary-color);
}

/* Geometry is shared with the header; only typography splits. A long DLC name gives
   way to the other columns rather than wrapping the row open. */
.title-col--name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.title-col--size {
    text-align: right;
}

.title-row:not(.title-row--head) .title-col--version,
.title-row:not(.title-row--head) .title-col--released,
.title-row:not(.title-row--head) .title-col--size {
    color: var(--bs-secondary-color);
    font-size: .85rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Version and release date are secondary once the row has to fit a phone; ownership
   and size are not, so those two stay. */
@media (max-width: 767.98px) {
    .title-content {
        --title-columns: minmax(0, 1fr) 7rem 5.5rem;
    }

    .title-col--version,
    .title-col--released {
        display: none;
    }
}

/* Three columns stop paying once 640px of screenshot leaves the description a ribbon:
   below that the screenshots drop under it, still 640 wide and so still 360 tall. */
@media (max-width: 1399.98px) {
    .title-grid.has-shots {
        grid-template-columns: 276px minmax(0, 1fr);
    }

    .title-grid.has-shots .title-shots {
        grid-column: 2;
        justify-self: start;
    }
}

@media (max-width: 991.98px) {
    .title-grid,
    .title-grid.has-shots {
        grid-template-columns: 1fr;
        gap: 1.4rem;
    }

    .title-grid.has-shots .title-shots {
        grid-column: auto;
        justify-self: stretch;
        width: auto;
    }

    .title-panel {
        padding: 1rem 1rem 2rem;
    }

    .title-icon {
        width: 150px;
        height: 150px;
        margin-top: calc(-75px - 1rem);
    }

    .title-name {
        font-size: 1.9rem;
    }
}

/* Portrait phones: side by side, the icon and the name are fighting over 350px. The
   name gets the full width underneath it instead. */
@media (max-width: 575.98px) {
    .title-head {
        flex-direction: column;
        align-items: flex-start;
        gap: .9rem;
    }

    .title-name {
        font-size: 1.6rem;
    }
}

/* The whole card is the link to its title's page; a DLC card opens the game it
   belongs to, not a page of its own. */
.game-link {
    display: block;
    color: inherit;
    text-decoration: none;
}

.game-icon-link {
    display: inline-block;
}

.game-icon-link .game-icon {
    display: block;
    width: 100%;
}

.game-icon--missing {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 1;
    border-radius: 8px;
    background-color: var(--bs-secondary-bg);
}
