/* VideoLoom Custom Styles */

/* Drop zone hover handled in theme.css (GitHub accent) */

:root {
    --primary: var(--gh-accent-fg, #0969da);
    --secondary: var(--gh-fg-muted, #656d76);
    --success: var(--gh-success-fg, #1a7f37);
    --danger: var(--gh-danger-fg, #d1242f);
    --warning: var(--gh-warning-fg, #9a6700);
    --info: var(--gh-info-fg, #0969da);
}

/* General */
body {
    font-family: var(--font-sans);
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

main {
    flex: 1;
}

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

a:hover {
    text-decoration: underline;
}

/* Navbar */
.navbar {
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* 3-zone navbar: left links / center (loom switch + project switcher) / right
   avatar. The center floats absolutely over the bar so it stays put regardless
   of how wide the side groups are — same pattern as .navbar-project-switcher. */
.navbar > .container-fluid {
    position: relative;
}
.navbar-center {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    align-items: center;
    gap: 0.5rem;
    z-index: 2;
}
/* Match the loom switch rounding to the project switcher ("All") pill. */
.navbar-center .loom-switch-btn {
    border-radius: var(--r-md);
}

/* Left icon group (mobile): burger + search, flush to the left edge, no box. */
.navbar-left-icons {
    gap: 0.15rem;
    margin-left: -10px;
}
.navbar-left-icons .navbar-toggler {
    padding: 4px 8px;
    border: 0;
}
.navbar-left-icons .navbar-toggler:focus {
    box-shadow: none;
}
.navbar-search-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 4px 8px;
    font-size: 1.15rem;
    line-height: 1;
    color: rgba(255, 255, 255, 0.85);
    text-decoration: none;
}
.navbar-search-icon:hover,
.navbar-search-icon.active {
    color: #fff;
}

.navbar-brand {
    font-size: 1.5rem;
    letter-spacing: 0.5px;
}

/* Mobile (<lg, navbar collapsed to burger): stretch the navbar AND footer edge-to-edge.
   Desktop keeps them inside the 85vw band (--content-max-width set inline / via theme.css);
   on a phone that band wastes ~7.5vw of gutter each side. !important overrides the inline
   max-width on .navbar > .container-fluid (base.html). */
@media (max-width: 991.98px) {
    .navbar > .container-fluid {
        max-width: 100% !important;
        padding-left: 12px !important;
        padding-right: 12px !important;
    }
}

/* Cards */
.card {
    border: 1px solid #e0e0e0;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
    transition: box-shadow var(--duration-slow) var(--ease-out);
}

.card:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.card-header {
    border-bottom: 1px solid #e0e0e0;
    font-weight: 600;
}

/* Search Results */
.search-result {
    border-left: 4px solid var(--primary);
    margin-bottom: 1.5rem;
}

.search-result:hover {
    border-left-color: var(--info);
}

/* Status Badges */
.badge {
    padding: 0.35rem 0.65rem;
    font-weight: 500;
}

/* Buttons */
.btn {
    transition: all var(--duration-base) var(--ease-out);
}

.btn:hover {
    transform: translateY(-2px);
}

/* Form Controls */
.form-control,
.form-select {
    border: 1px solid #e0e0e0;
    border-radius: 0.375rem;
}

.form-control:focus,
.form-select:focus {
    border-color: var(--primary);
    box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}

/* Tables */
.table {
    margin-bottom: 0;
}

.table thead {
    background-color: #f8f9fa;
    font-weight: 600;
}

.table-hover tbody tr:hover {
    background-color: #f8f9fa;
    cursor: pointer;
}

/* Video Grid */
.video-card {
    transition: border-color var(--duration-fast) var(--ease-out);
}

.video-card:hover {
    transform: none;
    border-color: var(--gh-fg-subtle);
}

.video-card img {
    object-fit: cover;
    height: 200px;
}

/* Shot Grid */
.shot-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 1.5rem;
}

/* Modal Improvements */
.modal-header {
    border-bottom: 1px solid var(--gh-border-default);
    background-color: var(--gh-bg-subtle);
}

.modal-body {
    padding: 2rem;
}

/* Spin animation for inline icons (e.g. embed button loading state) */
@keyframes spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}
.spin-anim {
    display: inline-block;
    animation: spin 0.8s linear infinite;
}

/* Loading Spinner */
.spinner-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 9999;
}

/* Footer */
footer {
    margin-top: auto;
    border-top: 1px solid #e0e0e0;
}

/* Alerts */
.alert {
    border: none;
    border-radius: 0.5rem;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}

.alert-danger {
    background-color: #f8d7da;
    color: #721c24;
}

.alert-warning {
    background-color: #fff3cd;
    color: #856404;
}

.alert-success {
    background-color: #d4edda;
    color: #155724;
}

.alert-info {
    background-color: #d1ecf1;
    color: #0c5460;
}

/* Tabs */
.nav-tabs {
    border-bottom: 2px solid #e0e0e0;
}

.nav-tabs .nav-link {
    color: var(--secondary);
    border: none;
    border-bottom: 3px solid transparent;
    transition: all var(--duration-slow) var(--ease-out);
    outline: none;
    box-shadow: none;
}

.nav-tabs .nav-link:focus,
.nav-tabs .nav-link:focus-visible {
    outline: none;
    box-shadow: none;
}

.nav-tabs .nav-link:hover {
    border-bottom-color: var(--primary);
    color: var(--primary);
}

.nav-tabs .nav-link.active {
    border-bottom-color: var(--primary);
    color: var(--primary);
    font-weight: 600;
}

/* Inner tabs (centered, inside pages) */
.inner-tabs {
    justify-content: center;
}

/* Responsive */
@media (max-width: 768px) {
    .shot-grid {
        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    }

    /* Keep small button groups horizontal on mobile. This used to be
       `flex-direction: column`, which stacked them into a vertical column and broke
       icon toolbars (library filter bar, shot-grid toolbar). Wrapping keeps icons in a
       row and lets wide text groups (topic style chips) flow to a second line instead
       of overflowing the viewport. */
    .btn-group-sm {
        flex-wrap: wrap;
        row-gap: 0.25rem;
    }
}

/* Code Blocks */
pre {
    background-color: #f5f5f5;
    border: 1px solid #e0e0e0;
    border-radius: 0.375rem;
    padding: 1rem;
    overflow-x: auto;
}

code {
    background-color: #f5f5f5;
    padding: 0.2rem 0.4rem;
    border-radius: 0.25rem;
    font-family: 'Courier New', monospace;
}

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

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

/* Utilities */
.object-fit-cover {
    object-fit: cover;
}

.cursor-pointer {
    cursor: pointer;
}

.no-select {
    user-select: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
}

/* Animations */
@keyframes fadeIn {
    from {
        opacity: 0;
        transform: translateY(10px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.fade-in {
    animation: fadeIn var(--duration-slow) var(--ease-out);
}

/* ============================================================================
   Pipeline Steps — 10-step progress indicator (Queue tab)
   ============================================================================ */
.pipeline-steps {
    display: flex;
    align-items: center;
    gap: 0;
    padding: 2px 0;
}
.pipeline-steps .step {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    font-size: 11px;
    line-height: 1;
    flex-shrink: 0;
    transition: all var(--duration-slow) var(--ease-out);
}
.pipeline-steps .step:not(:first-child)::before {
    content: '';
    position: absolute;
    right: 100%;
    top: 50%;
    width: 6px;
    height: 2px;
    background: rgba(128,128,128,0.3);
    transform: translateY(-50%);
}
.pipeline-steps .step.completed:not(:first-child)::before {
    background: rgba(25,135,84,0.6);
}
.pipeline-steps .step.active:not(:first-child)::before {
    background: rgba(0,114,178,0.6);
}
/* Step states */
.pipeline-steps .step.pending {
    background: rgba(128,128,128,0.2);
    color: #888;
}
.pipeline-steps .step.active {
    background: rgba(0,114,178,0.5);
    color: #fff;
    animation: stepPulse 1.5s ease-in-out infinite;
}
.pipeline-steps .step.completed {
    background: rgba(25,135,84,0.5);
    color: #fff;
}
.pipeline-steps .step.failed {
    background: rgba(213,94,0,0.5);
    color: #fff;
}
.pipeline-steps .step.skipped {
    background: rgba(128,128,128,0.15);
    color: #666;
}
.pipeline-steps .step.paused {
    background: rgba(255,193,7,0.5);
    color: #fff;
}
@keyframes stepPulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(0,114,178,0.4); }
    50% { box-shadow: 0 0 0 4px rgba(0,114,178,0); }
}

/* Queue Job Cards */
.queue-job-card {
    background-color: var(--gh-bg-default);
    border: 1px solid var(--gh-border-default);
    border-radius: var(--r-md);
    padding: var(--space-3) var(--space-3);
    margin-bottom: var(--space-2);
    cursor: pointer;
    transition: background-color var(--duration-base) var(--ease-out), border-color var(--duration-base) var(--ease-out);
}
.queue-job-card:hover {
    background-color: var(--gh-bg-subtle);
    border-color: var(--gh-fg-subtle);
}
.queue-job-card.status-started {
    border-left: 3px solid var(--gh-accent-fg);
}
.queue-job-card.status-failed {
    border-left: 3px solid var(--gh-danger-fg);
}
.queue-job-card.status-finished {
    border-left: 3px solid var(--gh-success-fg);
    opacity: 0.7;
}
.queue-job-card.status-paused {
    border-left: 3px solid var(--gh-warning-fg);
}

/* ============================================================================
   Library Grid View
   ============================================================================ */
.library-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: var(--space-3);
    padding: var(--space-3);
}
.library-grid .lib-grid-card {
    border-radius: var(--r-md);
    overflow: hidden;
    background-color: var(--gh-bg-default);
    border: 1px solid var(--gh-border-default);
    transition: border-color var(--duration-base) var(--ease-out);
    cursor: pointer;
}
.library-grid .lib-grid-card:hover {
    border-color: var(--gh-fg-subtle);
    transform: none;
    box-shadow: none;
}
.library-grid .lib-grid-card[data-video-status="paused"] {
    opacity: 0.5;
    filter: saturate(0.5);
}
.library-grid .lib-grid-card[data-video-status="paused"]:hover {
    opacity: 0.75;
    filter: saturate(0.75);
}
tr.video-row[data-is-paused="true"] {
    opacity: 0.5;
    filter: saturate(0.5);
}
tr.video-row[data-is-paused="true"]:hover {
    opacity: 1;
    filter: none;
}
.library-grid .card-thumb {
    position: relative;
    aspect-ratio: 16/9;
    overflow: hidden;
    background: #1a1a1a;
}
.library-grid .card-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.library-grid .card-duration {
    position: absolute;
    bottom: 4px;
    right: 4px;
    background: rgba(0,0,0,0.75);
    color: #fff;
    padding: var(--space-px) var(--space-2);   /* was 1 6 — 6→8 grid-aligned */
    border-radius: var(--r-xs);
    font-size: var(--font-r);
}
.library-grid .card-quality {
    position: absolute;
    top: 4px;
    right: 4px;
    background: rgba(0,0,0,0.6);
    color: #aaa;
    padding: var(--space-px) var(--space-1);   /* was 1 5 — 5→4 grid-aligned */
    border-radius: var(--r-xs);
    font-size: 9px;
}
.library-grid .card-info {
    padding: var(--space-2) var(--space-2);   /* was 6 8 — 6→8 grid */
}
.library-grid .card-title {
    font-size: var(--font-r);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.library-grid .card-meta {
    font-size: var(--font-r);
    color: #888;
    margin-top: 2px;
}

/* Print Styles */
@media print {
    .navbar,
    footer,
    .btn-group {
        display: none;
    }

    .card {
        page-break-inside: avoid;
    }
}

/* ============================================================================
   Drag-and-Drop Collapsible Sections
   ============================================================================ */

/* Drag handle styling */
.drag-handle {
    display: inline-block;
    color: #999;
    font-size: 1.2em;
    transition: color var(--duration-base) var(--ease-out);
    user-select: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
}

.drag-handle:hover {
    color: #555;
}

/* Sortable ghost (element being dragged - visual placeholder) */
.sortable-ghost {
    opacity: 0.4;
    background: #f8f9fa;
}

/* Sortable chosen (element grabbed) */
.sortable-chosen {
    cursor: move !important;
}

/* Sortable drag (while dragging) */
.sortable-drag {
    opacity: 0.8;
    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
    transform: rotate(2deg);
}

/* Collapse icon animation */
.collapse-icon {
    display: inline-block;
    transition: transform var(--duration-base) var(--ease-out);
}

/* Save indicator */
#save-indicator {
    animation: slideIn var(--duration-slow) var(--ease-out);
}

@keyframes slideIn {
    from {
        transform: translateX(100%);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}

/* Reset preferences button */
#reset-preferences-btn {
    transition: all var(--duration-base) var(--ease-out);
}

#reset-preferences-btn:hover {
    background-color: #d55e00;
    color: #fff;
    border-color: #d55e00;
}

/* Dark theme adjustments for drag-and-drop */
body[data-theme="dark"] .drag-handle {
    color: #666;
}

body[data-theme="dark"] .drag-handle:hover {
    color: #aaa;
}

body[data-theme="dark"] .sortable-ghost {
    background: #2d2d2d;
}

body[data-theme="dark"] .sortable-drag {
    box-shadow: 0 4px 12px rgba(255,255,255,0.1);
}

/* ============================================================================
   Modal Backdrop Improvements
   ============================================================================ */

/* Enhanced modal backdrop - subtle */
.modal-backdrop {
    background-color: rgba(0, 0, 0, 0.3) !important;
}

.modal-backdrop.show {
    opacity: 1 !important;
}

/* Dark theme modal backdrop */
body[data-theme="dark"] .modal-backdrop {
    background-color: rgba(0, 0, 0, 0.5) !important;
}

/* Ensure modal is above backdrop (Bootstrap 5 defaults) */
.modal {
    z-index: 1055;
}

.modal-backdrop {
    z-index: 1050;
}

/* Smooth modal appearance */
.modal.fade .modal-dialog {
    transition: transform var(--duration-slow) var(--ease-out), opacity var(--duration-slow) var(--ease-out);
}

/* Processing modal specific styles - GLASS EFFECT */
#processing-modal .modal-dialog,
#progress-modal .modal-dialog {
    max-width: 600px;
}

/* Processing/progress modal content — flat, opaque */
#processing-modal .modal-content,
#progress-modal .modal-content {
    background-color: var(--gh-bg-subtle) !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    border: 1px solid var(--gh-border-default);
    border-radius: var(--r-xl);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
    color: var(--gh-fg-default) !important;
}

/* Modal header for processing modal */
#processing-modal .modal-header,
#progress-modal .modal-header {
    background-color: var(--gh-bg-muted) !important;
    border-bottom: 1px solid var(--gh-border-default);
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
}

/* Modal body text colors */
#processing-modal .modal-body,
#progress-modal .modal-body {
    color: var(--gh-fg-default) !important;
}

#processing-modal .text-muted,
#progress-modal .text-muted {
    color: var(--gh-fg-muted) !important;
}

/* Card inside modal */
#progress-modal .card.bg-light {
    background-color: var(--gh-bg-default) !important;
    border: 1px solid var(--gh-border-default);
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
}

#progress-modal .card-body {
    color: var(--gh-fg-default) !important;
}

#progress-modal .card-title {
    color: var(--gh-fg-default) !important;
}

/* Library: active processing row highlight */
.lib-row-active,
.table-hover > tbody > tr.lib-row-active:hover {
    background: var(--gh-accent-subtle) !important;
}

/* Library filter bar buttons use same segmented control style as video_shots_tab */

/* Dimmed shots (no data for current tab) */
.shot-dimmed .card { opacity: 1 !important; transition: opacity var(--duration-base); }
.shot-dimmed:hover .card { opacity: 1 !important; }

/* Shots toolbar */
.shots-toolbar {
    background-color: var(--gh-bg-subtle) !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
}
body[data-theme="dark"] .shots-toolbar {
    background-color: var(--gh-bg-subtle) !important;
}

/* Dark theme adjustments */
body[data-theme="dark"] #processing-modal .modal-content,
body[data-theme="dark"] #progress-modal .modal-content {
    background-color: var(--gh-bg-subtle) !important;
    border: 1px solid var(--gh-border-default);
}

/* Step badge pulse animation (used by WS live updates on video detail page) */
@keyframes step-pulse {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0.45; }
}
.step-running {
  animation: step-pulse 1var(--duration-base) var(--ease-in-out) infinite;
}

/* Active step badge — ring pulses, ::before background pulses, icon stays static */
@keyframes step-ring-pulse {
  0%, 100% { box-shadow: 0 0 0 0   rgba(var(--bs-primary-rgb), 0.55); }
  50%       { box-shadow: 0 0 0 3px rgba(var(--bs-primary-rgb), 0); }
}
@keyframes step-bg-pulse {
  0%, 100% { opacity: 0.75; }
  50%       { opacity: 1.00; }
}
.step-btn.step-active {
  position: relative;
  animation: step-ring-pulse 1.5s ease-in-out infinite;
}
.step-btn.step-active::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--r-xs);
  background: var(--bs-primary);
  animation: step-bg-pulse 1.5s ease-in-out infinite;
  z-index: 0;
  pointer-events: none;
}
.step-btn.step-active > i {
  position: relative;
  z-index: 1;
}


/* Global utility overrides — ensure Bootstrap utilities take precedence over theme !important rules */
.d-none { display: none !important; }

/* ── Project switcher (relocated) ─────────────────────────────────────── */
/* Switcher dropdown: solid opaque panel so the library content doesn't bleed. */
.js-project-switcher .dropdown-menu {
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    box-shadow: 0 8px 24px rgba(0, 0, 0, .3) !important;
    min-width: 18rem;
}
body[data-theme="dark"] .js-project-switcher .dropdown-menu {
    background-color: var(--gh-bg-muted) !important;
    border-color: var(--gh-border-default) !important;
}
body:not([data-theme="dark"]) .js-project-switcher .dropdown-menu {
    background-color: var(--gh-bg-overlay) !important;
    border-color: var(--gh-border-default) !important;
}

/* Pinned to the left edge of the navbar; the nav links stay centered. */
.navbar-project-switcher {
    position: absolute;
    left: 1rem;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
}
@media (max-width: 900px) {
    .navbar-project-switcher { display: none; }
}
.ps-toggle {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    border-radius: var(--r-md);
    font-family: var(--font-sans);
    font-weight: 600;
    font-size: calc(var(--font-r) * var(--ui-scale));
    line-height: 1.2;
    cursor: pointer;
    white-space: nowrap;
    border: 1px solid transparent;
    background: none;
}
.ps-toggle .ps-name {
    max-width: 28ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ps-toggle .bi-folder2 { color: var(--bs-primary); }
.ps-toggle .ps-caret { font-size: .7em; opacity: .7; }
/* Label variant — accent pill near the brand */
.ps-toggle--label {
    padding: 2px 10px;
    min-width: 9rem;
    justify-content: space-between;
    background: var(--gh-accent-subtle);
    border-color: var(--gh-accent-muted);
    color: var(--gh-accent-fg);
}
.ps-toggle--label:hover { background: var(--gh-accent-muted); }
/* Pill variant — neutral, attached to the left of the search input */
.ps-toggle--pill {
    padding: 0 14px;
    border: 1px solid var(--gh-border-default);
    border-right: none;
    border-radius: var(--r-md) 0 0 var(--r-md);
    background: var(--gh-bg-muted);
    color: var(--bs-body-color);
}
.ps-toggle--pill:hover { background: var(--gh-border-default); }

/* Touch ergonomics: enlarge tap targets on ANY coarse pointer (catches iPad
   landscape >=1024px and touch laptops). Intentional — do not scope by width. */
@media (any-pointer: coarse) {
  .nav-link, .navbar-toggler, .admin-sidebar-toggle,
  .photo-card .btn, .photo-card input[type="checkbox"],
  .offcanvas .btn-close { min-height: 44px; min-width: 44px; }
}

/* ── Glassmorphism for the admin sidebar drawer ──
   When the burger opens the sidebar as an overlay drawer (detail/search pages, or any
   admin page below the lg rail breakpoint), Bootstrap's default solid panel + heavy black
   backdrop reads as a full-page blackout on the dark theme. Instead: a frosted translucent
   panel you can faintly see through, over a lightly blurred + barely-dimmed page.
   Scoped to .show (the open-drawer state) so the static desktop rail is never affected.
   #adminSidebar.show = (1,1,0) beats theme.css `.has-admin-tabs .admin-tabs-bar` (0,2,0),
   so this wins regardless of stylesheet order and overrides its `backdrop-filter:none`. */
#adminSidebar.show {
  background-color: rgba(13, 17, 23, 0.72) !important;                          /* fallback for no color-mix */
  background-color: color-mix(in srgb, var(--gh-bg-default) 72%, transparent) !important;
  -webkit-backdrop-filter: blur(6px) saturate(1.3) !important;
  backdrop-filter: blur(6px) saturate(1.3) !important;
}
/* Soften the shared offcanvas backdrop: dim overlay, minimal blur.
   0.45 read as a full-page blackout on the dark theme (crushes contrast for
   light text everywhere outside the drawer) — dropped to 0.18, no blur. */
.offcanvas-backdrop.show {
  opacity: 1;
  background-color: rgba(0, 0, 0, 0.18);
}



/* --- /help/videos: YouTube description under each row --------------------
   Two-line clamp with a pure-CSS expander. The checkbox is the state; it is
   kept in the flow (not display:none) so it stays reachable from the keyboard
   via its label. pre-wrap preserves the line breaks YouTube descriptions carry
   — without it a multi-paragraph description collapses into one run-on line.

   The cut is max-height rather than -webkit-line-clamp: line-clamp always
   appends its own "…" and offers no way to suppress it, which left the row
   saying the same thing twice — once in dots, once in words. The word below
   the text carries it alone, and unlike the dots it can be clicked. */
.vl-desc {
    position: relative;
}

.vl-desc-toggle {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
}

.vl-desc-body {
    display: block;
    cursor: pointer;
}

.vl-desc-text {
    /* line-height is pinned so the two-line height below is exact arithmetic
       and not a guess against whatever the theme inherits. */
    display: block;
    line-height: 1.5;
    max-height: 3em;
    overflow: hidden;
    white-space: pre-wrap;
}

.vl-desc-toggle:checked ~ .vl-desc-body .vl-desc-text {
    max-height: none;
}

/* Hidden by opacity, not display: the word still occupies its line, so the row
   does not jump a line taller the moment the pointer enters it. Opacity also
   leaves the text in the accessibility tree, which display:none would remove —
   a keyboard user reaches the toggle and the focus rule below reveals it. */
.vl-desc-more {
    display: block;
    color: var(--bs-secondary-color);
    text-decoration: underline dotted;
    opacity: 0;
    transition: opacity 0.12s ease-in-out;
}

.vl-desc-body:hover .vl-desc-more,
.vl-desc-toggle:focus-visible ~ .vl-desc-body .vl-desc-more {
    opacity: 1;
    color: var(--bs-body-color);
}

.vl-desc-toggle:focus-visible ~ .vl-desc-body .vl-desc-more {
    outline: 2px solid var(--bs-primary);
    outline-offset: 2px;
}

.vl-desc-more-close {
    display: none;
}

.vl-desc-toggle:checked ~ .vl-desc-body .vl-desc-more-open {
    display: none;
}

.vl-desc-toggle:checked ~ .vl-desc-body .vl-desc-more-close {
    display: inline;
}
