.reload-indicator {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    padding: .35rem .6rem;
    border: 1px solid var(--bs-border-color);
    border-radius: 999px;
    background: var(--bs-body-bg);
    font-size: .875rem;
    line-height: 1;
    white-space: nowrap;
}

.reload-dot {
    position: relative;
    width: .6rem;
    height: .6rem;
    border-radius: 999px;
    background: currentColor;
    flex: 0 0 auto;
}

.reload-dot.ping::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 999px;
    background: currentColor;
    opacity: .35;
    animation: reload-ping 1.25s ease-out infinite;
}

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

.sort-hint {
    display: inline-block;
    width: .9em;
}

.sort-hint::before {
    content: '';
}

th.sortable[data-sort-dir="asc"] .sort-hint::before {
    content: '↑';
}

th.sortable[data-sort-dir="desc"] .sort-hint::before {
    content: '↓';
}

@keyframes reload-ping {
    0% { transform: scale(1); opacity: .35; }
    70% { transform: scale(2.25); opacity: 0; }
    100% { transform: scale(2.25); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .reload-dot::before { animation: none; }
}

/* Welcome hero: the classic Grails cups, back as the page's signature.
   The panel stays charcoal in both themes so the white mark always reads;
   the warm halo picks up the brand accent from the logo. */
.welcome-hero {
    color: #fff;
    /* Same border the cards use, so the hero reads as part of the same set —
       most visibly on its right edge, where the light cups would otherwise
       blend into the page. --bs-border-color-translucent is theme-aware, so one
       rule matches the cards in both light and dark. */
    border: 1px solid var(--bs-border-color-translucent);
    background:
        radial-gradient(46rem 30rem at 74% 52%, rgba(254, 182, 114, .32), transparent 58%),
        linear-gradient(135deg, #14161b 0%, #1e222a 58%, #262b35 100%);
}

.welcome-hero-cups {
    position: absolute;
    top: 50%;
    right: 1.75rem;
    height: 100%;
    transform: translateY(-50%);
    color: #fff;
    opacity: .95;
    pointer-events: none;
    filter: drop-shadow(0 .75rem 1.75rem rgba(0, 0, 0, .45));
}

.welcome-hero-body {
    max-width: 30rem;
    /* Keep the white hero copy legible where it crosses the light cups. A pure
       stroke would leave the letters' interiors white (same as the cups), so pair
       a hairline dark stroke (Photoshop's "stroke"/outline) with a soft dark halo:
       the halo darkens the area around each glyph so the fill still reads. Both are
       invisible on the dark panel and only do visible work over the artwork. */
    -webkit-text-stroke: 0.6px rgba(0, 0, 0, 0.6);
    paint-order: stroke fill;
    text-shadow: 0 0 4px rgba(0, 0, 0, 0.85);
}

.welcome-hero-note {
    color: rgba(255, 255, 255, .72);
}

@media (max-width: 767.98px) {
    .welcome-hero-cups {
        right: -30%;
        opacity: .14;
        filter: none;
    }
    .welcome-hero-body {
        max-width: none;
    }
}

@media (prefers-reduced-motion: no-preference) {
    .welcome-hero-cups {
        animation: welcome-cups-in .7s ease-out both;
    }
    .welcome-hero-body {
        animation: welcome-body-in .55s ease-out both;
    }
}

@keyframes welcome-cups-in {
    from { opacity: 0; transform: translateY(-50%) translateX(1.25rem); }
    to   { opacity: .95; transform: translateY(-50%) translateX(0); }
}

@keyframes welcome-body-in {
    from { opacity: 0; transform: translateY(.4rem); }
    to   { opacity: 1; transform: none; }
}

.filter-input {
    min-width: 12rem;
}

/* Jump-to-id control on controllers that expose a `show` action.
   Collapsed it is a compact "id" pill; focusing or typing widens it, and
   the Show button is revealed only once an id has been entered. Colors and
   focus ring come from Bootstrap's form-control/btn, so light & dark both work. */
.show-jump {
    display: inline-flex;
    align-items: stretch;
}

.show-jump-input {
    width: 2.75rem;
    font-variant-numeric: tabular-nums;
}

.show-jump-input:focus,
.show-jump-input:not(:placeholder-shown) {
    width: 8.5rem;
}

.show-jump-input:focus {
    position: relative;
    z-index: 2;
}

@media (prefers-reduced-motion: no-preference) {
    .show-jump-input {
        transition: width .18s ease;
    }
}

/* Hidden until the field holds an id; then it joins the input as one control. */
.show-jump-go {
    display: none;
    white-space: nowrap;
}

.show-jump-input:not(:placeholder-shown) {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}

.show-jump-input:not(:placeholder-shown) + .show-jump-go {
    display: inline-flex;
    align-items: center;
    margin-left: -1px;
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
}

/* Reserve the input's height on every controller row so revealing the control
   never changes the row height, and rows stay a uniform size. */
.controller-row {
    min-height: 2rem;
}

/* Responsive visibility is Bootstrap's job: the form carries `d-lg-none`, so it
   is always visible below lg — the same breakpoint at which the navbar collapses
   to a hamburger ("mobile"). At lg and up (no hamburger) the control is hidden and
   revealed on row hover or keyboard focus instead, so the two behaviors agree on
   what "mobile" means. Bootstrap has no hover utility, so this stays custom;
   !important beats d-lg-none's own rule. */
@media (min-width: 992px) {
    .list-group-item:hover .show-jump,
    .list-group-item:focus-within .show-jump {
        display: inline-flex !important;
    }
}
