h1:focus {
    outline: none;
}

/* The active language in the selector: visibly marked (not only by color)
   and non-interactive, while remaining readable in the app bar. */
.language-selector-active {
    font-weight: 700;
    text-decoration: underline;
    opacity: 1;
}

.blazor-error-boundary {
    background: #b32121;
    padding: 1rem;
    color: white;
}

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

/* Search form controls: plain form-post-capable inputs (identical behavior
   on SSR and interactive renders) styled to match MudBlazor fields. */
.library-input {
    border: 1px solid var(--mud-palette-lines-inputs);
    border-radius: 4px;
    padding: 8px;
    font: inherit;
    background: var(--mud-palette-surface);
    color: var(--mud-palette-text-primary);
}

/* Page-content match snippets: plain encoded text with our own emphasis. */
.library-snippet mark {
    background: var(--mud-palette-warning-hover, #fff3cd);
    padding: 0 2px;
    border-radius: 2px;
}

/* ---- Application shell (global: these classes land on MudBlazor component
   roots and plain layout markup, so they cannot use scoped CSS). ---- */

/* Brand block in the app bar: logo plus title as one home link. */
.app-brand {
    color: inherit;
    text-decoration: none;
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}

.app-logo {
    height: 34px;
    width: auto;
    display: block;
}

/* User avatar on the validated Primary slot with its AA-checked text color. */
.app-avatar {
    background: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text, #212121);
    font-weight: 700;
    cursor: pointer;
}

/* Main content fills the viewport so the footer sits at the bottom even on
   short pages (border-box height includes the fixed-appbar top offset). */
.app-main {
    min-height: 100vh;
}

/* Footer band on the validated Primary slot, so any admin-configured theme
   keeps the AA-checked primary/on-primary text pairing. The top border is a
   decorative accent only. */
.app-footer {
    background: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text, #212121);
    border-top: 4px solid #d6a84f;
}

    .app-footer a {
        color: var(--mud-palette-primary-text, #212121);
    }

    .app-footer .app-footer-title {
        font-weight: 700;
    }

    .app-footer .app-footer-muted {
        opacity: 0.85;
    }

/* Language dropdown: a native <details> disclosure so it opens and submits
   identically on static SSR pages (login/sign-up) and interactive pages. */
.language-menu {
    position: relative;
}

    .language-menu > summary {
        list-style: none;
        display: flex;
        align-items: center;
        gap: 4px;
        cursor: pointer;
        padding: 6px 10px;
        border-radius: 4px;
        user-select: none;
    }

        .language-menu > summary::-webkit-details-marker {
            display: none;
        }

        .language-menu > summary:hover,
        .language-menu > summary:focus-visible {
            background: rgba(255, 255, 255, 0.12);
        }

    .language-menu .language-menu-list {
        position: absolute;
        inset-inline-end: 0;
        top: calc(100% + 4px);
        z-index: 1500;
        min-width: 10rem;
        display: flex;
        flex-direction: column;
        background: var(--mud-palette-surface, #ffffff);
        color: var(--mud-palette-text-primary, #212121);
        border-radius: 4px;
        box-shadow: var(--mud-elevation-4, 0 2px 8px rgba(0, 0, 0, 0.3));
        padding: 4px 0;
    }

    .language-menu .language-menu-item {
        display: block;
        width: 100%;
        text-align: start;
        padding: 8px 16px;
        background: none;
        border: none;
        font: inherit;
        color: inherit;
        cursor: pointer;
    }

        .language-menu .language-menu-item:hover:not(:disabled),
        .language-menu .language-menu-item:focus-visible {
            background: var(--mud-palette-action-default-hover, rgba(0, 0, 0, 0.06));
        }

        .language-menu .language-menu-item:disabled {
            cursor: default;
        }

/* Auth pages (login/sign-up/reset): centered brand header above the card. */
.auth-brand {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
}

    .auth-brand .app-logo {
        height: 56px;
    }

/* Screen-reader-only text (e.g. the hero search label). */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* Managed cover image (details pages and anywhere outside a catalog card):
   scales down to the container and keeps its own proportions. */
.catalog-cover {
    min-height: 120px;
}

.catalog-cover-image {
    max-width: 100%;
    height: auto;
}

/* Skeleton placeholder shown when a catalog entry has no cover image. */
.cover-skeleton {
    position: relative;
    min-height: 120px;
    height: 100%;
}

    .cover-skeleton .cover-skeleton-fill {
        position: absolute;
        inset: 0;
    }

    .cover-skeleton .cover-skeleton-icon {
        position: absolute;
        inset: 0;
        margin: auto;
        width: 40px;
        height: 40px;
        opacity: 0.45;
    }

/* ---- Public catalog cards (global: every class here lands on a MudBlazor
   component root — MudCard, MudCardContent, MudText, MudLink — which never
   receives a CSS-isolation scope attribute). Sizes use logical properties so
   Arabic/Persian RTL mirrors without a second rule set. ---- */

/* The card is the positioning context for the title link's stretched
   overlay, so clicking anywhere on it opens the details page. */
.catalog-card {
    position: relative;
    display: flex;
    flex-direction: column;
    height: 100%;
    border: 1px solid var(--mud-palette-lines-default, rgba(0, 0, 0, 0.12));
    border-radius: 10px;
    background: var(--mud-palette-surface);
    transition: transform 160ms ease, box-shadow 160ms ease, border-color 160ms ease;
}

    .catalog-card:hover,
    .catalog-card:focus-within {
        transform: translateY(-2px);
        border-color: var(--mud-palette-primary);
        box-shadow: var(--mud-elevation-6, 0 6px 18px rgba(0, 0, 0, 0.18));
    }

    .catalog-card .catalog-card-content {
        display: flex;
        flex-direction: column;
        flex: 1 1 auto;
        padding: 12px;
    }

/* One fixed ratio for every cover, so mismatched uploads and the missing-cover
   skeleton all occupy the same box and grid rows stay level. The height cap
   keeps a full-width card usable on a 320px screen. */
/* Covers are portrait: 2:3 matches the documented upload guidance
   (600 x 900) for both book and newspaper covers. The box is capped by WIDTH
   and centered — capping the height instead would let wide cards flatten the
   ratio into a square. */
.catalog-card .catalog-card-media {
    position: relative;
    aspect-ratio: 2 / 3;
    width: 100%;
    max-width: 220px;
    margin-inline: auto;
    overflow: hidden;
    border-radius: 8px;
    background: var(--mud-palette-background-grey, rgba(0, 0, 0, 0.04));
}

    .catalog-card .catalog-card-media > *,
    .catalog-card .catalog-card-media .cover-skeleton {
        height: 100%;
        min-height: 0;
    }

    .catalog-card .catalog-card-media .catalog-cover-image {
        width: 100%;
        height: 100%;
        max-width: none;
        object-fit: cover;
    }

/* Titles are clamped to two lines and always reserve both, so the metadata
   rows of neighbouring cards line up. */
.catalog-card .catalog-card-title {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    margin-block-start: 12px;
    font-size: 1.0625rem;
    line-height: 1.4;
    min-height: 2.8em;
}

.catalog-card .catalog-card-link {
    color: var(--mud-palette-text-primary);
    font-weight: 600;
    text-decoration: none;
}

.catalog-card:hover .catalog-card-link {
    color: var(--mud-palette-primary);
    text-decoration: underline;
}

/* The stretched overlay: not focusable and not a second link — it belongs to
   the title anchor, which keeps the card's single tab stop. */
.catalog-card .catalog-card-link::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    border-radius: 10px;
}

/* Focus follows the click target: the keyboard ring is drawn on the overlay
   so it outlines the whole card, inset so the card's overflow cannot clip it. */
.catalog-card .catalog-card-link:focus-visible {
    outline: none;
}

    .catalog-card .catalog-card-link:focus-visible::after {
        outline: 3px solid var(--mud-palette-primary);
        outline-offset: -3px;
    }

/* Anything genuinely interactive inside a card must stay clickable above the
   overlay (favorite toggles, chips that link, external links). */
.catalog-card .catalog-card-interactive {
    position: relative;
    z-index: 2;
}

.catalog-card .catalog-card-meta {
    margin-block-start: 6px;
    color: var(--mud-palette-text-secondary);
}

/* Chips sit at the bottom edge, so every card in a row ends the same way. */
.catalog-card .catalog-card-footer {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-block-start: auto;
    padding-block-start: 12px;
}

    .catalog-card .catalog-card-footer .mud-chip {
        margin: 0;
    }

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

        .catalog-card:hover,
        .catalog-card:focus-within {
            transform: none;
        }
}
