:root {
    --primary: #4a90d9;
    --primary-dark: #357abd;
    --secondary: #6c757d;
    --success: #28a745;
    --danger: #dc3545;
    --warning: #ffc107;
    --light: #f8f9fa;
    --dark: #343a40;
    --bg: #1a1a2e;
    --bg-card: #16213e;
    --bg-input: #0f3460;
    --text: #e4e4e4;
    --text-muted: #a0a0a0;
    --border: #2a3f5f;
}

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
    background: var(--bg);
    color: var(--text);
    line-height: 1.6;
    min-height: 100vh;
}

a {
    color: var(--primary);
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
}

/* Layout */
.container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 1rem;
}

/* Header */
.header {
    background: var(--bg-card);
    border-bottom: 1px solid var(--border);
    padding: 1rem 0;
    position: sticky;
    top: 0;
    z-index: 100;
}

.header-content {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.logo {
    font-size: 1.5rem;
    font-weight: bold;
    color: var(--text);
}

.nav {
    display: flex;
    gap: 1.5rem;
    align-items: center;
}

.nav a {
    color: var(--text-muted);
    transition: color 0.2s;
}

.nav a:hover, .nav a.active {
    color: var(--text);
    text-decoration: none;
}

/* Main content */
.main {
    padding: 2rem 0;
    min-height: calc(100vh - 140px);
}

/* Cards */
.card {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 1.5rem;
    margin-bottom: 1rem;
}

.card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1rem;
    padding-bottom: 1rem;
    border-bottom: 1px solid var(--border);
}

.card-title {
    font-size: 1.25rem;
    font-weight: 600;
}

/* Grid */
.grid {
    display: grid;
    gap: 1.5rem;
}

.grid-2 {
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}

.grid-3 {
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}

/* App card */
.app-card {
    display: flex;
    gap: 1rem;
    transition: transform 0.2s, box-shadow 0.2s;
}

.app-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}

.app-icon {
    width: 64px;
    height: 64px;
    background: var(--bg-input);
    border-radius: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.5rem;
    flex-shrink: 0;
}

/* A game that has a picture of its own fills the tile with it; one that does not
   keeps the letter on the plain background. `cover` rather than `contain`: an icon
   and a screenshot have nothing in common but the square they are put in. */
.app-icon img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 12px;
    display: block;
}

.app-info {
    flex: 1;
    min-width: 0;
}

.app-name {
    font-weight: 600;
    margin-bottom: 0.25rem;
}

.app-desc {
    color: var(--text-muted);
    font-size: 0.875rem;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

/* Buttons */
.btn {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 1rem;
    border: none;
    border-radius: 6px;
    font-size: 0.875rem;
    font-weight: 500;
    line-height: 1.2;
    box-sizing: border-box;
    cursor: pointer;
    transition: background 0.2s, transform 0.1s;
    text-decoration: none;
}

.btn:hover {
    text-decoration: none;
    transform: translateY(-1px);
}

.btn:active {
    transform: translateY(0);
}

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

.btn-primary:hover {
    background: var(--primary-dark);
    color: white;
}

.btn-secondary {
    background: var(--secondary);
    color: white;
}

.btn-success {
    background: var(--success);
    color: white;
}

.btn-danger {
    background: var(--danger);
    color: white;
}

.btn-sm {
    padding: 0.25rem 0.75rem;
    font-size: 0.8rem;
}

.btn-lg {
    padding: 0.75rem 1.5rem;
    font-size: 1rem;
}

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

.form-label {
    display: block;
    margin-bottom: 0.5rem;
    font-weight: 500;
    color: var(--text-muted);
}

.form-control {
    width: 100%;
    padding: 0.625rem 0.875rem;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text);
    font-size: 1rem;
    transition: border-color 0.2s, box-shadow 0.2s;
}

.form-control:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(74, 144, 217, 0.2);
}

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

select.form-control {
    cursor: pointer;
}

textarea.form-control {
    min-height: 100px;
    resize: vertical;
}

.form-check {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.form-check input[type="checkbox"] {
    width: 1.25rem;
    height: 1.25rem;
    accent-color: var(--primary);
}

/* Tables */
.table {
    width: 100%;
    border-collapse: collapse;
}

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

.table th {
    color: var(--text-muted);
    font-weight: 500;
    font-size: 0.875rem;
}

.table tr:hover {
    background: rgba(255,255,255,0.02);
}

/* Badges */
.badge {
    display: inline-block;
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
    font-size: 0.75rem;
    font-weight: 500;
}

.badge-success {
    background: rgba(40, 167, 69, 0.2);
    color: var(--success);
}

.badge-warning {
    background: rgba(255, 193, 7, 0.2);
    color: var(--warning);
}

.badge-secondary {
    background: rgba(108, 117, 125, 0.2);
    color: var(--secondary);
}

.badge-primary {
    background: rgba(74, 144, 217, 0.2);
    color: var(--primary);
}

.badge-danger {
    background: rgba(220, 53, 69, 0.2);
    color: var(--danger);
}

/* Hide helpers */
.hide-desktop {
    display: none;
}

/* Alert */
.alert {
    padding: 1rem;
    border-radius: 6px;
    margin-bottom: 1rem;
}

.alert-success {
    background: rgba(40, 167, 69, 0.15);
    border: 1px solid var(--success);
    color: var(--success);
}

.alert-danger {
    background: rgba(220, 53, 69, 0.15);
    border: 1px solid var(--danger);
    color: var(--danger);
}

.alert-warning {
    background: rgba(255, 193, 7, 0.15);
    border: 1px solid var(--warning);
    color: var(--warning);
}

/* Page header */
.page-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 2rem;
}

.page-title {
    font-size: 1.75rem;
    font-weight: 600;
}

/* Footer */
.footer {
    background: var(--bg-card);
    border-top: 1px solid var(--border);
    padding: 1.5rem 0;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.875rem;
}

/* Admin sidebar layout */
.admin-layout {
    display: flex;
    min-height: 100vh;
}

.sidebar {
    width: 250px;
    background: var(--bg-card);
    border-right: 1px solid var(--border);
    padding: 1.5rem 0;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
}

.sidebar-nav {
    flex: 1;
}

.sidebar-footer {
    padding: 1rem 1.5rem;
    border-top: 1px solid var(--border);
}

.lang-switcher {
    display: flex;
    justify-content: center;
}

.lang-select {
    width: 100%;
    padding: 0.5rem 0.75rem;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text);
    font-size: 0.9rem;
    cursor: pointer;
    transition: border-color 0.2s;
}

.lang-select:hover {
    border-color: var(--primary);
}

.lang-select:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 2px rgba(74, 144, 217, 0.2);
}

.lang-select option {
    background: var(--bg-card);
    color: var(--text);
    padding: 0.5rem;
}

.sidebar-header {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem;
    padding: 0 1.5rem 1.5rem;
    border-bottom: 1px solid var(--border);
    margin-bottom: 1rem;
}

/* The name and the way out sit at the far edge, not trailing the title. */
.sidebar-header .logo {
    margin-right: auto;
}

.sidebar-user {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.sidebar-nav a {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem 1.5rem;
    color: var(--text-muted);
    transition: all 0.2s;
}

.sidebar-nav a:hover, .sidebar-nav a.active {
    background: rgba(74, 144, 217, 0.1);
    color: var(--text);
    text-decoration: none;
    border-left: 3px solid var(--primary);
}

.admin-content {
    flex: 1;
    padding: 2rem;
    overflow-x: auto;
}

/* Version list */
.version-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    margin-bottom: 0.5rem;
}

.version-info {
    display: flex;
    align-items: center;
    gap: 1rem;
}

.version-number {
    font-weight: 600;
    font-size: 1.1rem;
}

.version-meta {
    display: flex;
    gap: 0.5rem;
}

/* News item */
.news-item {
    border-bottom: 1px solid var(--border);
    padding-bottom: 1.5rem;
    margin-bottom: 1.5rem;
}

.news-item:last-child {
    border-bottom: none;
    margin-bottom: 0;
}

.news-title {
    font-size: 1.25rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
}

.news-meta {
    color: var(--text-muted);
    font-size: 0.875rem;
    margin-bottom: 0.75rem;
}

.news-content {
    line-height: 1.7;
}

/* Modal */
.modal {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.7);
    z-index: 1000;
    align-items: center;
    justify-content: center;
}

.modal.active {
    display: flex;
}

.modal-content {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 1.5rem;
    max-width: 500px;
    width: 90%;
    max-height: 90vh;
    overflow-y: auto;
}

.modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1rem;
}

.modal-close {
    background: none;
    border: none;
    color: var(--text-muted);
    font-size: 1.5rem;
    cursor: pointer;
}

/* Login page */
.login-container {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    padding: 2rem;
}

.login-box {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 2rem;
    width: 100%;
    max-width: 400px;
}

.login-title {
    text-align: center;
    margin-bottom: 1.5rem;
    font-size: 1.5rem;
}

/* Actions */
.actions {
    display: flex;
    gap: 0.5rem;
}

/* Empty state */
.empty-state {
    text-align: center;
    padding: 3rem;
    color: var(--text-muted);
}

.empty-state-icon {
    font-size: 3rem;
    margin-bottom: 1rem;
}

/* Tabs */
.tabs {
    display: flex;
    gap: 0;
    border-bottom: 1px solid var(--border);
    margin-bottom: 1.5rem;
    overflow-x: auto;
}

.tab {
    padding: 0.75rem 1.5rem;
    color: var(--text-muted);
    text-decoration: none;
    border-bottom: 2px solid transparent;
    white-space: nowrap;
    transition: all 0.2s;
}

.tab:hover {
    color: var(--text);
    text-decoration: none;
}

.tab.active {
    color: var(--primary);
    border-bottom-color: var(--primary);
}

.tab-count {
    background: var(--bg-input);
    padding: 0.1rem 0.5rem;
    border-radius: 10px;
    font-size: 0.75rem;
    margin-left: 0.5rem;
}

/* Mobile hamburger */
.mobile-menu-toggle {
    display: none;
    background: none;
    border: none;
    color: var(--text);
    font-size: 1.5rem;
    cursor: pointer;
    padding: 0.5rem;
}

/* Responsive */
@media (max-width: 768px) {
    .admin-layout {
        flex-direction: column;
    }

    .sidebar {
        width: 100%;
        border-right: none;
        border-bottom: 1px solid var(--border);
        position: relative;
    }

    .sidebar-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding-bottom: 0.75rem;
        margin-bottom: 0;
        border-bottom: none;
    }

    .mobile-menu-toggle {
        display: block;
    }

    .sidebar-nav {
        display: none;
        flex-direction: column;
        padding: 0;
    }

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

    .sidebar-nav a {
        padding: 0.75rem 1.5rem;
        border-left: none !important;
        border-bottom: 1px solid var(--border);
    }

    .sidebar-nav a:hover, .sidebar-nav a.active {
        border-left: 3px solid var(--primary) !important;
        border-bottom: 1px solid var(--border);
    }

    .sidebar-footer {
        display: none;
        padding: 0.75rem 1.5rem;
    }

    .sidebar-footer.open {
        display: block;
    }

    /* Show mobile-only content */
    .hide-desktop {
        display: block;
    }

    .admin-content {
        padding: 1rem;
    }

    .page-header {
        flex-direction: column;
        gap: 1rem;
        align-items: stretch;
    }

    .page-header > div {
        flex-wrap: wrap;
    }

    .page-header .actions {
        flex-wrap: wrap;
    }

    /* Responsive tables */
    .table-responsive {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }

    .table {
        min-width: 0;
    }

    .table td:last-child {
        display: flex;
        flex-wrap: wrap;
        gap: 0.25rem;
    }

    .table td:last-child form {
        display: inline-flex !important;
    }

    /* Hide columns on mobile */
    .hide-mobile {
        display: none;
    }

    /* Stack actions */
    .actions,
    .action-buttons {
        display: flex;
        flex-wrap: wrap;
        gap: 0.25rem;
    }

    .action-buttons form {
        display: inline-flex !important;
    }

    /* Cards on mobile */
    .card {
        padding: 1rem;
    }

    .card-header {
        flex-direction: column;
        gap: 0.75rem;
        align-items: flex-start;
    }

    /* Version items */
    .version-item {
        flex-direction: column;
        align-items: flex-start;
        gap: 0.75rem;
    }

    .version-info {
        flex-direction: column;
        align-items: flex-start;
        gap: 0.5rem;
    }

    /* Modal on mobile */
    .modal-content {
        width: 95%;
        max-height: 85vh;
        margin: 1rem;
    }

    /* Tabs on mobile */
    .tabs {
        gap: 0;
    }

    .tab {
        padding: 0.75rem 1rem;
        font-size: 0.9rem;
    }

    /* Grid on mobile */
    .grid-2, .grid-3 {
        grid-template-columns: 1fr;
    }

    /* Page title */
    .page-title {
        font-size: 1.4rem;
    }

    /* Buttons full width on mobile */
    .btn-mobile-full {
        width: 100%;
        justify-content: center;
    }
}

@media (max-width: 480px) {
    .admin-content {
        padding: 0.75rem;
    }

    .table th, .table td {
        padding: 0.5rem;
        font-size: 0.85rem;
    }

    .btn-sm {
        padding: 0.2rem 0.5rem;
        font-size: 0.75rem;
    }

    .badge {
        font-size: 0.7rem;
        padding: 0.2rem 0.4rem;
    }
}

/* Version accordion */
.version-accordion {
    border-bottom: 1px solid var(--border);
}

.version-accordion:last-child {
    border-bottom: none;
}

.version-summary {
    display: flex;
    align-items: center;
    padding: 1rem;
    cursor: pointer;
    list-style: none;
    transition: background 0.2s;
}

.version-summary::-webkit-details-marker {
    display: none;
}

.version-summary::before {
    content: '\25B6';
    font-size: 0.7rem;
    margin-right: 0.75rem;
    color: var(--text-muted);
    transition: transform 0.2s;
}

.version-accordion[open] .version-summary::before {
    transform: rotate(90deg);
}

.version-summary:hover {
    background: rgba(255, 255, 255, 0.03);
}

.version-content {
    padding: 0 1rem 1rem 2.25rem;
}

/* Loading overlay */
.loading-overlay {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.6);
    z-index: 2000;
    align-items: center;
    justify-content: center;
}

.loading-overlay.active {
    display: flex;
}

.loading-box {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 2rem 3rem;
    text-align: center;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}

.loading-spinner {
    width: 40px;
    height: 40px;
    border: 3px solid var(--border);
    border-top-color: var(--primary);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
    margin: 0 auto 1rem;
}

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

.loading-text {
    color: var(--text);
    font-size: 1rem;
    font-weight: 500;
}

/* Asset library
   ---------------------------------------------------------------------------
   These tables carry many small columns *and* inline edit forms, so the usual
   horizontal-scroll treatment is wrong for them: it hides the forms off-screen.
   They stack into blocks instead. Fixed column widths are deliberately absent —
   they were what squeezed text into one-letter-per-line on a phone. */
.lib-table {
    width: 100%;
    font-size: 0.85rem;
}

.lib-table td {
    padding: 0.3rem 0.4rem;
    vertical-align: top;
    word-break: break-word;
}

.lib-form {
    display: flex;
    gap: 0.35rem;
    flex-wrap: wrap;
}

/* Inputs default to a content-based minimum, which stops flex from shrinking
   them and pushes the row wider than the screen. */
.lib-form input,
.lib-form select {
    min-width: 0;
    flex: 1 1 8rem;
}

@media (max-width: 768px) {
    .lib-table,
    .lib-table tbody,
    .lib-table tr,
    .lib-table td {
        display: block;
        width: auto;
    }

    .lib-table tr {
        border-bottom: 1px solid var(--border);
        padding: 0.4rem 0;
    }

    .lib-table td {
        padding: 0.15rem 0;
    }

    .lib-form {
        flex-direction: column;
        align-items: stretch;
    }
}

/* Preview thumbnails. Images are served from /api/files/{id}, which requires a
   session — that works in a browser, so a non-public library file still renders
   in an <img>. Non-image previews (animation clips) show their name instead of
   an inline player: a video tag per row would pull megabytes on page load. */
.lib-previews {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

.lib-preview {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
}

.lib-preview img {
    height: 3.5rem;
    width: auto;
    max-width: 6rem;
    object-fit: cover;
    border: 1px solid var(--border);
    border-radius: 3px;
    display: block;
}


/* ---------------------------------------------------------------------------
   Media store gallery (/admin/media).

   Phone first: tiles are sized so two or three fit a narrow screen, and every
   image is a 320px lazily-fetched JPEG rather than an original — a grid of
   originals is exactly what made the owner say he could not look at screenshots
   from his phone. Nothing here depends on JavaScript.
   --------------------------------------------------------------------------- */
.media-intro {
    color: var(--text-muted);
    font-size: 0.9rem;
    margin-bottom: 1rem;
}

.media-filters {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: flex-end;
    margin-bottom: 1.25rem;
}

.media-field {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    font-size: 0.8rem;
    color: var(--text-muted);
}

.media-field .form-control {
    min-width: 9rem;
}

.media-actions {
    flex-direction: row;
    gap: 0.5rem;
}

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

.media-cell {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.media-thumb {
    display: block;
    aspect-ratio: 16 / 10;
    background: var(--bg-input);
    border-radius: 4px;
    overflow: hidden;
}

.media-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.media-placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.25rem;
    height: 100%;
    padding: 0.25rem;
    color: var(--text-muted);
}

.media-placeholder-mark {
    font-size: 1.4rem;
}

.media-placeholder-name {
    font-size: 0.7rem;
    text-align: center;
    word-break: break-all;
}

.media-meta {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    font-size: 0.75rem;
}

.media-owner,
.media-note {
    color: var(--text-muted);
    font-size: 0.7rem;
}

.media-taglist {
    display: flex;
    flex-wrap: wrap;
    gap: 0.2rem;
}

.media-tag {
    background: var(--bg-input);
    border-radius: 3px;
    padding: 0 0.3rem;
    font-size: 0.7rem;
    color: var(--text-muted);
    text-decoration: none;
}

.media-tagform {
    display: flex;
    gap: 0.25rem;
}

.media-tagform .form-control {
    font-size: 0.75rem;
    padding: 0.15rem 0.3rem;
    min-width: 0;
}

/* Cards on the public applications list, which now carry Play and Media. */
.app-card-link {
    display: flex;
    gap: 1rem;
    align-items: center;
    text-decoration: none;
    color: inherit;
}

.app-card-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    margin-top: 0.6rem;
}

/* The viewer (/admin/media/{id}). One picture, arrows either side of it and a
   way to keep it — sized for a thumb, since this is the page the phone opens
   after tapping a tile. */
.viewer {
    max-width: 900px;
}

.viewer-stage {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 6px;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 40vh;
    overflow: hidden;
    /* The picture is zoomed inside this box by the script in media-item.html,
       never by zooming the page — see the note there. `pan-y` takes pinch and
       double-tap zoom away from the browser while leaving ordinary scrolling
       alone, and the script switches this to `none` once the picture is zoomed
       so a drag pans the picture instead of the page. */
    touch-action: pan-y;
}

.viewer-media {
    max-width: 100%;
    max-height: 70vh;
    display: block;
    /* The zoom is applied here: the element's own box never changes size, so
       nothing outside it can be affected by the gesture. */
    transform-origin: 50% 50%;
    will-change: transform;
}

.viewer-fallback {
    padding: 2rem 1rem;
    color: var(--text-muted);
    font-size: 0.9rem;
    text-align: center;
}

.viewer-bar {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0.6rem 0;
}

/* Arrows: a thumb target first, a glyph second. They were 3.5rem and read as
   small; the page cannot be pinched to make them bigger, because that gesture
   zooms the picture, so they have to be right at their own size. */
.viewer-arrow {
    flex: 0 0 auto;
    min-width: 4.5rem;
    min-height: 3rem;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    font-size: 1.6rem;
    line-height: 1;
}

.viewer-arrow.disabled {
    opacity: 0.35;
    pointer-events: none;
}

.viewer-save {
    flex: 1 1 auto;
    text-align: center;
}

.viewer-meta {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    font-size: 1rem;
}

.viewer-name {
    font-weight: 500;
    font-size: 1.05rem;
    word-break: break-all;
}

.viewer-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    color: var(--text-muted);
    font-size: 0.9rem;
}

.viewer-tags {
    margin-top: 0.75rem;
}

/* The two controls a thumb uses most, in the viewer and in the bar. */
.viewer-save {
    font-size: 1rem;
    padding-top: 0.6rem;
    padding-bottom: 0.6rem;
}

.viewer-zoom {
    font-size: 0.95rem;
}

.viewer-tags .form-control {
    font-size: 0.95rem;
}

/* Full screen in the viewer — "almost full screen" on purpose.

   Deliberately not the Fullscreen API: iOS Safari has no
   Element.requestFullscreen, so a control built on it would do nothing on the
   phone this page exists for. What is dropped instead is every piece of chrome
   we own — the sidebar, the padding — and the picture takes the viewport, which
   lands within one URL bar of the same result on every browser and keeps the
   arrows working across pictures. */
.viewer-fs .sidebar {
    display: none;
}

/* The two bars sit at the top and bottom of the window.
   They are pinned, and this time it holds, because the page they are pinned to
   can no longer be zoomed: `touch-action: pan-y` on the full-screen content
   refuses pinch and double-tap zoom, and the gesture goes to the picture instead
   (media-item.html). Two earlier attempts tried to *survive* page zoom —
   `position: fixed` alone, then a correction driven by `window.visualViewport` —
   and both came back from a real phone wrong: on that browser the bars were
   magnified with the picture and the right arrow ended up off the edge. Page zoom
   is semantics each engine implements its own way; a layout that never changes
   has no such question to answer. Translucent, because the picture behind them is
   the reason you are here. */
.viewer-fs .admin-content {
    padding: 0;
    display: block;
    height: auto;
    /* Self-contained and scrolled inside itself: the viewer's page never scrolls,
       so the bars pinned to the top and bottom of it cannot be scrolled away
       either. Pinch and double-tap zoom are refused here as well — the gesture
       belongs to the picture (media-item.html), and a page that never zooms is a
       page whose chrome cannot move. */
    position: fixed;
    inset: 0;
    overflow-y: auto;
    touch-action: pan-y;
}

.viewer-fs .page-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 20;
    margin: 0;
    padding: 0.4rem 0.5rem;
    background: rgba(0, 0, 0, 0.55);
}

.viewer-fs .viewer {
    max-width: none;
    display: block;
}

.viewer-fs .viewer-stage {
    /* The whole window, less the pinned bar at the bottom of it. */
    height: calc(100vh - 3.4rem);
    height: calc(100dvh - 3.4rem);
    min-height: 0;
    border: none;
    border-radius: 0;
    background: #000;
}

.viewer-fs .viewer-media {
    max-height: 100%;
    max-width: 100%;
    object-fit: contain;
}

.viewer-fs .viewer-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 20;
    margin: 0;
    padding: 0.4rem 0.5rem;
    background: rgba(0, 0, 0, 0.55);
}

/* In full screen the page header keeps only what is about this picture — where
   it sits in the run, and the way back out. The gallery link and the caption
   switch are one tap away by leaving, and on a phone every extra button in that
   row is a row of picture. */
.viewer-fs .viewer-back,
.viewer-fs .viewer-labels {
    display: none;
}

.viewer-fs .page-header {
    justify-content: space-between;
}

/* The details block, when it is on, scrolls under the pinned bar — keep its last
   line reachable rather than behind the buttons. */
.viewer-fs .viewer-meta,
.viewer-fs .viewer-tags {
    margin: 0.5rem 0.5rem 0;
}

.viewer-fs .viewer-tags {
    margin-bottom: 3.4rem;
}

/* ---------------------------------------------------------------------------
   Navigation: sections in the sidebar, and a bar at the bottom on a phone.

   The list had grown to fourteen identical entries, which is a list nobody
   reads — grouping it puts what is opened daily at the top. The `.active` class
   was styled from the start and never set by anything; AdminModelAdvice now
   passes the current path so the section you are in is lit up.
   --------------------------------------------------------------------------- */
.nav-group {
    padding: 1.1rem 1.5rem 0.3rem;
    font-size: 0.68rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
    opacity: 0.8;
}

.admin-bottom {
    display: none;
}

@media (max-width: 768px) {
    /* Reaching the menu meant aiming at a hamburger at the top of the page —
         which on a phone is the far end of the screen from your thumb. */
    .admin-bottom {
        display: flex;
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 30;
        background: var(--bg-card);
        border-top: 1px solid var(--border);
        padding: 0.3rem 0.25rem;
        padding-bottom: calc(0.3rem + env(safe-area-inset-bottom, 0px));
    }

    .admin-bottom a,
    .admin-bottom button {
        flex: 1 1 0;
        text-align: center;
        font-size: 0.75rem;
        padding: 0.45rem 0.15rem;
        background: none;
        border: none;
        color: var(--text-muted);
        text-decoration: none;
        font-family: inherit;
    }

    .admin-bottom a.active {
        color: var(--primary);
        font-weight: 500;
    }

    /* Room for the bar, so the last row of a page is not behind it. */
    .admin-content {
        padding-bottom: 4.5rem;
    }

    /* Never in the viewer's full screen: there the picture is the point, and the
         viewer has a bar of its own. */
    .viewer-fs .admin-bottom {
        display: none;
    }
}

/* The magnification, while it changes and for a moment after.
   The pinch has no other feedback — the picture grows, but "how far in" is the
   question the gesture raises, and the answer costs a pill the size of a word.
   Fixed and centred above the bar; it never takes a tap (pointer-events: none),
   because a badge that swallows a thumb tap on the picture would be worse than
   no badge. */
.viewer-zoom {
    position: fixed;
    left: 50%;
    bottom: 3.9rem;
    transform: translateX(-50%);
    z-index: 25;
    padding: 0.15rem 0.6rem;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    font-size: 0.8rem;
    opacity: 0;
    transition: opacity 0.25s;
    pointer-events: none;
}

.viewer-zoom.shown {
    opacity: 1;
}

/* ---------------------------------------------------------------------------
   Tasks: what a tester reported, and the conversation about it.

   One shape used by both sides — the admin's page and the tester's — because it
   is the same thing seen from two chairs: a statement, then replies, either of
   which may be mostly a picture.
   --------------------------------------------------------------------------- */
.thread {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    margin-top: 0.75rem;
}

.thread-entry {
    border-left: 3px solid var(--border);
    padding-left: 0.75rem;
}

.thread-who {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    font-size: 0.75rem;
    color: var(--text-muted);
}

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

.thread-when {
    color: var(--text-muted);
    font-size: 0.75rem;
}

.thread-images {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 0.4rem;
}

/* Big enough to see what a screenshot is of, small enough that four of them fit
   a phone. The viewer is one tap away for a proper look. */
.thread-images img {
    height: 7rem;
    width: auto;
    max-width: 12rem;
    object-fit: cover;
    border: 1px solid var(--border);
    border-radius: 4px;
    display: block;
}

.task-card {
    margin-bottom: 0.75rem;
}

.task-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.6rem;
}

.task-game {
    font-weight: 500;
}

.task-who {
    color: var(--text-muted);
    font-size: 0.8rem;
}

.task-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 0.75rem;
}

/* The tester's own form: room to write, and the labels above rather than beside,
   because on a phone a label in a column is a column of nothing. */
.remark-form {
    display: flex;
    flex-direction: column;
}

.remark-label {
    margin-top: 0.75rem;
    margin-bottom: 0.25rem;
    font-size: 0.85rem;
    color: var(--text-muted);
}

.remark-mine {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0;
    border-bottom: 1px solid var(--border);
    color: inherit;
    text-decoration: none;
}

/* A picture in a list of media, at the top of its card. Sized like a contact
   sheet rather than a gallery: this is a list somebody scans for the right file,
   and the viewer is one tap away for a proper look. */
.media-tile {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 10;
    object-fit: cover;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 4px;
    margin-bottom: 0.5rem;
}

/* Nothing to show — a model, an archive — so the card says what it is instead of
   leaving a hole where a picture would be. */
.media-tile-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
    font-size: 0.8rem;
}

/* The same idea in the asset library's rows, which are a table rather than a
   grid: small enough to keep the row scannable. */
.lib-thumb {
    display: block;
    height: 4rem;
    width: auto;
    max-width: 8rem;
    object-fit: cover;
    border: 1px solid var(--border);
    border-radius: 4px;
}

/* One task in the tester's list: a whole row is a link, because on a phone the
   whole row is what a thumb aims at. */
.remark-task {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--border);
    color: inherit;
    text-decoration: none;
}

/* The task list and a task's thread, read on a phone: the same reason as the
   viewer — nothing here can be pinched bigger, so the sizes have to be right. */
.thread-body {
    font-size: 1rem;
}

.thread-who,
.thread-when {
    font-size: 0.85rem;
}

.task-who,
.media-owner,
.media-note {
    font-size: 0.85rem;
}

.remark-label {
    font-size: 0.95rem;
}

.remark-mine,
.remark-task {
    font-size: 1rem;
}

/* A task's name in a list: the thing somebody scans for, above the paragraph. */
.task-title {
    font-weight: 500;
    font-size: 1.05rem;
    margin-top: 0.35rem;
}

.remark-task-title {
    font-weight: 500;
}

/* The task list: one row per task, and a row is a name and where it stands.
   Everything else about a task — the words, the pictures, the buttons — is on
   its page, one tap away; a list that carries all of it is not a list. */
.task-list {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--border);
    border-radius: 6px;
    overflow: hidden;
}

.task-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem;
    padding: 0.7rem 0.8rem;
    border-bottom: 1px solid var(--border);
    color: inherit;
    text-decoration: none;
}

.task-row:last-child {
    border-bottom: none;
}

/* The name takes the width, and the rest sits after it — on a phone the metadata
   wraps under the title rather than squeezing it. */
.task-row-title {
    flex: 1 1 12rem;
    font-weight: 500;
}

.task-row-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    color: var(--text-muted);
    font-size: 0.8rem;
}

/* One picture and what it is. The role sits under the thumbnail rather than
   beside it: a badge on every tile is noise, and the word is short. */
.thread-image {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    align-items: center;
}

.thread-image-role {
    font-size: 0.7rem;
    color: var(--text-muted);
}

/* "Take it off the task": as small as the caption it sits in, and quiet, because
   the tap people come for is the picture itself. */
.thread-image-off {
    background: none;
    border: 0;
    padding: 0 0 0 0.35rem;
    font: inherit;
    font-size: 0.7rem;
    color: var(--text-muted);
    text-decoration: underline;
    cursor: pointer;
}

/*
 * A flash message on a phone.
 *
 * The links table is long, and the Save button of the row somebody just edited
 * is nowhere near the top of the page — a confirmation that appears above the
 * fold is a confirmation nobody sees. Sticky keeps it in view while the page
 * stays where it was.
 */
.alert-flash {
    position: sticky;
    top: 0.5rem;
    z-index: 50;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

/*
 * The links table as cards on a phone.
 *
 * Seven columns do not fit: the games column squeezed everything else into a
 * strip of letters, and the headers overlapped. Below 768px each row becomes a
 * card, each cell a "label: value" line — the same data, readable at thumb
 * width.
 */
@media (max-width: 768px) {
    .links-table thead {
        display: none;
    }

    .links-table tr {
        display: block;
        border: 1px solid var(--border);
        border-radius: 6px;
        margin-bottom: 0.75rem;
        padding: 0.6rem 0.75rem;
    }

    .links-table td {
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 0.75rem;
        border: none;
        padding: 0.3rem 0;
    }

    .links-table td::before {
        content: attr(data-label);
        flex: 0 0 auto;
        font-weight: 600;
        color: var(--text-muted);
        font-size: 0.85rem;
    }

    .links-table td:last-child {
        justify-content: flex-end;
    }

    .links-table td:last-child::before {
        content: none;
    }
}

/* Navigation and the person, as one block on the right. */
.header-right {
    display: flex;
    align-items: center;
    gap: 2rem;
    margin-left: auto;
}

/* Who is looking, and the way out. */
.header-user {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.9rem;
    color: var(--text-muted);
}

/* ---------------------------------------------------------------------------
   The task page: three steps, three cards.

   What was reported, what was said about it, and where it stands. The controls
   that belong to a step live inside it — editing behind "Edit the task", adding
   a comment in the composer under the last reply, and closing next to the
   release that fixed it. A card that mixes them turns one question ("what do I
   press?") into several.
   --------------------------------------------------------------------------- */
.task-title-lg {
    font-size: 1.3rem;
    font-weight: 600;
    line-height: 1.3;
    margin: 0.4rem 0 0.2rem;
}

.task-edit,
.task-store {
    margin-top: 0.9rem;
    border-top: 1px solid var(--border);
    padding-top: 0.6rem;
}

.task-store {
    border-top: 0;
    margin-top: 0.75rem;
}

.task-edit > summary,
.task-store > summary {
    cursor: pointer;
    color: var(--text-muted);
    font-size: 0.95rem;
}

/* The composer is the only thing on the page that adds to the conversation, so
   it is separated from the last reply by a line rather than hidden below the
   rest of the page. */
.task-composer {
    margin-top: 1rem;
    border-top: 1px solid var(--border);
    padding-top: 0.75rem;
}

.task-composer h3 {
    margin: 0 0 0.35rem;
    font-size: 1rem;
}

.task-solution {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
}

.task-solution .remark-label {
    margin: 0;
}

/* Deleting a task is not part of working it: quiet, last, and away from the
   buttons somebody presses every day. */
.task-danger {
    margin-top: 1rem;
    border-top: 1px solid var(--border);
    padding-top: 0.6rem;
}

.task-danger button {
    color: var(--danger);
}

/* Picking a picture that is already in the store: the radio and the thumbnail
   are one tap target, sized like the tiles around them. */
.store-pick {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.3rem;
    cursor: pointer;
}

.store-pick input {
    accent-color: var(--primary);
}
