h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

/* MudBlazor's toolbar keeps a fixed 56px layout height even with WrapContent, so a wrapped second/third row
   (e.g. the login links on a narrow phone) visually overflows below it instead of pushing the page content
   down — this lets it grow to fit however many rows it actually needs. */
.mud-appbar.ms-appbar,
.mud-appbar.ms-appbar .mud-toolbar {
    height: auto;
    min-height: 56px;
}

/* Blazor Server toggles components-reconnect-show/-hide/-failed/-rejected on #components-reconnect-modal by
   its fixed id automatically (see blazorReconnect.js) - this only supplies the visibility/appearance, since
   the framework doesn't ship any CSS for it. Without this, a dropped circuit gave the user no feedback at
   all while Blazor quietly retried in the background for up to 5 minutes.
   The z-index below is NOT actually what applies at runtime: MudBlazor.min.css ships its own
   "#components-reconnect-modal{z-index:9999!important;...}" rule for this same well-known id, and an
   !important declaration always wins over a non-!important one regardless of source order or specificity - see
   #ms-offline-overlay's own z-index comment below, which had to be raised past 9999 (not past the harmless-
   looking 2000 here) to actually render on top of this modal. Left in place anyway since it's still the
   fallback if MudBlazor is ever removed, and removing it would make this rule's presence more confusing, not
   less. */
#components-reconnect-modal {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 2000;
    align-items: center;
    justify-content: center;
    text-align: center;
    color: #fff;
    background: rgba(0, 0, 0, .6);
}

#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
    display: flex;
}

#components-reconnect-modal .components-reconnect-show-text,
#components-reconnect-modal .components-reconnect-failed-text,
#components-reconnect-modal .components-reconnect-rejected-text {
    display: none;
}

#components-reconnect-modal.components-reconnect-show .components-reconnect-show-text,
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-text,
#components-reconnect-modal.components-reconnect-rejected .components-reconnect-rejected-text {
    display: block;
}

.ms-reconnect-button {
    margin-top: 1rem;
    padding: .5rem 1.25rem;
    border: none;
    border-radius: 4px;
    background: #fff;
    color: #000;
    cursor: pointer;
}

/* Driven entirely by wwwroot/js/blazorReconnect.js (msOfflineUi)/offlinePlayer.js/offlineLibrary.js via the
   plain "hidden" attribute - no Blazor component behind this, see App.razor. Sits above the reconnect modal
   (z-index 2000) so a device that's both offline AND mid Blazor-reconnect-retry shows the offline UI, which is
   actually usable without a circuit, rather than the reconnect dialog, which is not. */
/* z-index must beat #components-reconnect-modal's actual rendered z-index, which is 9999 !important - set by
   MudBlazor's own bundled CSS (MudBlazor.min.css ships default styling for Blazor's standard reconnect-modal
   id), not the 2000 this file declares further up. An !important declaration always wins over a non-!important
   one regardless of source order or selector specificity, so the modal was silently rendering on top of this
   overlay - fully covering it - even though the overlay itself was correctly shown underneath. Root cause of a
   reported bug: the offline overlay looked completely inert/invisible once the reconnect modal actually
   appeared, indistinguishable from the feature not working at all. Matching !important here for the same
   reason, with a safety margin above 9999 in case a future MudBlazor version bumps its own value further. */
#ms-offline-overlay {
    position: fixed;
    inset: 0;
    z-index: 10000 !important;
    display: flex;
    flex-direction: column;
    background: #121212;
    color: #fff;
    overflow-y: auto;
    padding: 1rem;
}

.ms-offline-banner {
    font-weight: bold;
    font-size: 1.1rem;
    text-align: center;
    padding: .5rem;
}

/* Mirrors MainLayout.razor's own app bar layout (brand top-left, account status/language switcher top-right) -
   plain flexbox instead of MudAppBar/MudStack, since none of this can depend on MudBlazor component rendering
   while offline. Colors match MainLayout.razor.cs's MudTheme exactly (#7c4dff primary), same reasoning as the
   rest of this file's offline-overlay rules: hardcoded rather than var(--mud-palette-primary), since that
   variable is only guaranteed to exist once MudThemeProvider has actually rendered, which this overlay must not
   depend on. */
.ms-offline-header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding-bottom: .5rem;
}

.ms-offline-brand-link {
    display: flex;
    align-items: center;
    gap: .5rem;
    color: inherit;
    text-decoration: none;
}

.ms-offline-brand-link:hover {
    opacity: .85;
}

.ms-offline-app-icon {
    width: 28px;
    height: 28px;
    border-radius: 6px;
    object-fit: cover;
}

.ms-offline-app-name {
    font-size: 1.15rem;
    font-weight: 600;
}

.ms-offline-account {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
}

.ms-offline-account-text {
    font-size: .8rem;
    opacity: .75;
}

.ms-offline-lang-status {
    font-size: .7rem;
    color: #ffb74d;
}

/* The overlay's "back to online playback" button - same outlined shape as the language buttons next to it, but
   standalone (rounded on both sides) and in the theme's primary color, since it is an action rather than a
   two-state selector. */
.ms-offline-mode-button {
    background: transparent;
    border: 1px solid #7c4dff;
    border-radius: 4px;
    color: #b39ddb;
    cursor: pointer;
    font-size: .7rem;
    font-weight: 500;
    padding: .15rem .4rem;
}

.ms-offline-lang-switcher {
    display: flex;
}

.ms-offline-lang-btn {
    background: transparent;
    border: 1px solid rgba(255, 255, 255, .3);
    border-radius: 4px;
    color: rgba(255, 255, 255, .7);
    cursor: pointer;
    font-size: .7rem;
    font-weight: 500;
    padding: .15rem .4rem;
}

.ms-offline-lang-btn:first-child {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
    border-right: none;
}

.ms-offline-lang-btn:last-child {
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
}

.ms-offline-lang-btn-active {
    background: #7c4dff;
    border-color: #7c4dff;
    color: #fff;
    cursor: default;
    font-weight: 700;
}

/* Mirrors #ms-player-bar's MudPaper card in Home.razor - same surface color/elevation-like shadow AND the same
   sticky-to-the-bottom placement (Home.razor.cs's inline "position: sticky; bottom: 0;"), just built from plain
   CSS instead of a MudBlazor component. Sticky rather than fixed since #ms-offline-overlay is itself the
   scrolling container (overflow-y: auto above) - exactly mirroring how the online bar is sticky within the
   normal page scroll, not fixed to the browser viewport. Theme colors below (#7c4dff primary, #00bfa5
   secondary, #1e1e1e surface) match MainLayout.razor.cs's MudTheme exactly, so the offline overlay reads as the
   same application, not a visually distinct fallback. */
.ms-offline-player {
    display: flex;
    flex-direction: column;
    gap: .35rem;
    padding: .75rem 1rem;
    margin-top: 1rem;
    background: #1e1e1e;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .4);
    position: sticky;
    bottom: 0;
}

.ms-offline-queue-context {
    display: flex;
    align-items: center;
    gap: .5rem;
    font-size: .8rem;
    color: rgba(255, 255, 255, .7);
    min-height: 1.1rem;
}

.ms-offline-now-playing {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: .75rem;
    min-height: 2.5rem;
}

/* Always the app icon (see App.razor's comment) - sized/rounded to match the online player bar's cover image
   (Home.razor's 96x96 MudImage), just smaller to fit the more compact offline bar. */
.ms-offline-track-icon {
    width: 48px;
    height: 48px;
    border-radius: 8px;
    object-fit: cover;
    flex-shrink: 0;
}

.ms-offline-now-playing-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.ms-offline-track-title {
    font-size: 1.05rem;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ms-offline-track-artist {
    font-size: .8rem;
    color: rgba(255, 255, 255, .7);
}

/* Mirrors #ms-player-next-track's MudText online: same secondary caption style as .ms-offline-track-artist,
   empty (and collapsed via :empty below) whenever offlinePlayer.js's _updateNextTrackDisplay finds no next
   track (queue exhausted, or a lone single-track queue). */
.ms-offline-next-track {
    font-size: .75rem;
    color: rgba(255, 255, 255, .7);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ms-offline-next-track:empty {
    display: none;
}

.ms-offline-controls {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: .35rem;
}

.ms-offline-control-button {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: .5rem;
    border: none;
    border-radius: 999px;
    background: transparent;
    color: #fff;
    cursor: pointer;
}

.ms-offline-control-button svg {
    width: 1.5rem;
    height: 1.5rem;
    fill: currentColor;
}

.ms-offline-control-button:hover:not(:disabled) {
    background: rgba(255, 255, 255, .12);
}

.ms-offline-control-button:disabled {
    color: rgba(255, 255, 255, .3);
    cursor: default;
}

.ms-offline-control-button--small svg {
    width: 1.25rem;
    height: 1.25rem;
}

.ms-offline-control-button--primary {
    background: #7c4dff;
    padding: .6rem;
}

.ms-offline-control-button--primary:hover:not(:disabled) {
    background: #9370ff;
}

.ms-offline-control-button--active {
    color: #7c4dff;
    background: rgba(124, 77, 255, .18);
}

.ms-offline-progress {
    display: flex;
    align-items: center;
    gap: .5rem;
}

.ms-offline-time {
    font-size: .75rem;
    color: rgba(255, 255, 255, .7);
    min-width: 2.5rem;
    text-align: center;
}

.ms-offline-seek {
    flex-grow: 1;
    accent-color: #7c4dff;
}

/* Mirrors the online search box (MudAutocomplete) closely enough to read as the same control, without pulling
   in MudBlazor's own styling (which needs the Blazor circuit this page doesn't have). */
.ms-offline-library-search {
    margin-bottom: .5rem;
}

.ms-offline-search-input {
    width: 100%;
    box-sizing: border-box;
    padding: .5rem .75rem;
    border: 1px solid rgba(255, 255, 255, .3);
    border-radius: 4px;
    background: #1e1e1e;
    color: #fff;
    font-size: .95rem;
}

.ms-offline-search-input:focus {
    outline: none;
    border-color: #7c4dff;
}

/* Mirrors MudTabs' underline-style tab row. */
.ms-offline-tabs {
    display: flex;
    gap: 1.5rem;
    border-bottom: 1px solid rgba(255, 255, 255, .2);
    margin-bottom: .75rem;
}

.ms-offline-tab {
    padding: .5rem 0;
    border: none;
    border-bottom: 2px solid transparent;
    background: transparent;
    color: rgba(255, 255, 255, .7);
    font-size: .95rem;
    cursor: pointer;
}

.ms-offline-tab--active {
    color: #fff;
    border-bottom-color: #7c4dff;
}

.ms-offline-section {
    margin-top: 1rem;
}

.ms-offline-empty {
    opacity: .8;
}

/* Mirrors an album/playlist card (MudPaper) in Home.razor/PlaylistsPanel.razor. */
.ms-offline-group {
    margin-bottom: .75rem;
    padding: .5rem .75rem;
    background: #1e1e1e;
    border-radius: 8px;
}

.ms-offline-group-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: .5rem;
}

.ms-offline-group-header-main {
    display: flex;
    align-items: center;
    gap: .5rem;
    min-width: 0;
}

.ms-offline-group-title {
    font-size: 1.1rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ms-offline-group-meta {
    font-size: .75rem;
    color: rgba(255, 255, 255, .7);
    white-space: nowrap;
}

.ms-offline-group-header-actions {
    display: flex;
    align-items: center;
    gap: .25rem;
}

.ms-offline-icon-button {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: .4rem;
    border: none;
    border-radius: 999px;
    background: transparent;
    color: #fff;
    cursor: pointer;
}

.ms-offline-icon-button svg {
    width: 1.25rem;
    height: 1.25rem;
    fill: currentColor;
}

.ms-offline-icon-button:hover {
    background: rgba(255, 255, 255, .12);
}

.ms-offline-icon-button--primary {
    color: #7c4dff;
}

.ms-offline-track-list {
    list-style: none;
    margin: .5rem 0 0;
    padding: 0;
}

.ms-offline-track-row {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .35rem 0;
}

.ms-offline-track-play-button {
    flex-shrink: 0;
}

.ms-offline-track-info {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex-grow: 1;
}

.ms-offline-track-row-title {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ms-offline-track-row-artist {
    font-size: .75rem;
    color: rgba(255, 255, 255, .7);
}

.ms-offline-track-remove-button {
    flex-shrink: 0;
}

.ms-offline-track-info-button {
    flex-shrink: 0;
}

/* Mirrors TrackInfoDialog (a MudDialog) as closely as a plain, circuit-free component can: a centered card
   over a dimmed backdrop, same surface color as the rest of the offline UI. Fixed positioning (rather than
   living inside the scrolling #ms-offline-overlay flow) so it stays centered in the viewport regardless of
   where the library list/player bar currently scroll to. z-index above #ms-offline-overlay's own (10000) for
   the same reason that one has to beat the reconnect modal's - see its comment above. */
.ms-offline-info-overlay {
    position: fixed;
    inset: 0;
    z-index: 10001;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    background: rgba(0, 0, 0, .6);
}

.ms-offline-info-panel {
    width: 100%;
    max-width: 320px;
    max-height: 80vh;
    overflow-y: auto;
    background: #1e1e1e;
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, .5);
    padding: 1rem;
}

.ms-offline-info-title {
    font-size: 1.1rem;
    font-weight: 500;
    margin-bottom: .75rem;
}

.ms-offline-info-body {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.ms-offline-info-actions {
    display: flex;
    justify-content: flex-end;
    margin-top: .75rem;
}

/* Mirrors MudButton's default text-button look (TrackInfoDialog's "Close" button online). */
.ms-offline-text-button {
    padding: .5rem .75rem;
    border: none;
    border-radius: 4px;
    background: transparent;
    color: #7c4dff;
    font-weight: 600;
    cursor: pointer;
}

.ms-offline-text-button:hover {
    background: rgba(124, 77, 255, .12);
}

.ms-offline-info-cover {
    width: 100%;
    max-width: 200px;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    border-radius: 8px;
    margin: 0 auto .5rem;
    display: block;
}

.ms-offline-info-field {
    font-size: .9rem;
}

.ms-offline-info-empty {
    opacity: .8;
    font-size: .9rem;
}
