/* ==========================================================================
   Motion system
   Loaded after site.css. Entrances only animate transform and opacity; hover
   states reuse the colors and shadows already defined in site.css.

   Page load (CSS only):  .animate-fade-up, .animate-scale-in, .animate-sequence
   Scroll reveal (JS):    .reveal / .reveal-up, .reveal-left, .reveal-right,
                          .reveal-scale, .stagger (children enter one by one)
   Cards and images:      .hover-lift, .hover-zoom (cover frame), .img-reveal
   Micro interactions:    navigation, buttons, inputs, links, pagination,
                          submit feedback, scroll-to-top

   Content is only hidden before it is revealed when theme-init.js has added
   html.has-motion (JavaScript running, IntersectionObserver supported and
   motion allowed). Without it every element renders in its final state.
   ========================================================================== */

:root {
    --motion-distance: 16px;
    --motion-distance-sm: 10px;
    --motion-duration: 600ms;
    --motion-duration-fast: 250ms;
    --motion-stagger: 70ms;
    --motion-ease: var(--ease, cubic-bezier(0.2, 0.7, 0.2, 1));
}

@media (max-width: 900px) {
    :root {
        --motion-distance: 12px;
        --motion-distance-sm: 8px;
        --motion-duration: 540ms;
        --motion-stagger: 55ms;
    }
}

@media (max-width: 600px) {
    :root {
        --motion-distance: 10px;
        --motion-distance-sm: 6px;
        --motion-duration: 500ms;
        --motion-stagger: 45ms;
    }
}

/* Keyframes only define the start; the end is the element's own style, so
   elements with a resting opacity or transform keep it. */
@keyframes motion-fade-up {
    from {
        opacity: 0;
        transform: translate3d(0, var(--motion-distance), 0);
    }
}

@keyframes motion-fade-up-sm {
    from {
        opacity: 0;
        transform: translate3d(0, var(--motion-distance-sm), 0);
    }
}

@keyframes motion-fade-left {
    from {
        opacity: 0;
        transform: translate3d(calc(var(--motion-distance) * -1.5), 0, 0);
    }
}

@keyframes motion-fade-right {
    from {
        opacity: 0;
        transform: translate3d(calc(var(--motion-distance) * 1.5), 0, 0);
    }
}

@keyframes motion-scale-in {
    from {
        opacity: 0;
        transform: scale(0.97);
    }
}

@keyframes motion-fade-in {
    from {
        opacity: 0;
    }
}

@keyframes motion-failsafe {
    to {
        opacity: 1;
    }
}

@keyframes motion-shimmer {
    from {
        opacity: 1;
        transform: translateX(-100%);
    }

    to {
        opacity: 1;
        transform: translateX(100%);
    }
}

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

/* ==========================================================================
   Page load — CSS only, never waits for JavaScript
   `backwards` fill applies the start frame during the delay and holds nothing
   afterwards, so hover transforms keep working once the entrance ends.
   ========================================================================== */

.animate-fade-up {
    animation: motion-fade-up var(--motion-duration) var(--motion-ease) var(--animation-delay, 0ms) backwards;
}

.animate-scale-in {
    animation: motion-scale-in 700ms var(--motion-ease) var(--animation-delay, 0ms) backwards;
}

/* Children enter one after another: eyebrow, title, intro, form, link */
.animate-sequence > * {
    animation: motion-fade-up-sm 560ms var(--motion-ease) backwards;
}

.animate-sequence > :nth-child(2) { animation-delay: 80ms; }
.animate-sequence > :nth-child(3) { animation-delay: 160ms; }
.animate-sequence > :nth-child(4) { animation-delay: 240ms; }
.animate-sequence > :nth-child(n + 5) { animation-delay: 320ms; }

.delay-1 { --animation-delay: 120ms; }
.delay-2 { --animation-delay: 220ms; }

/* Hero: the dotted texture settles in behind the copy (one time, no loop) */
.hero-section::before {
    animation: motion-fade-in 1200ms var(--motion-ease) 100ms backwards;
}

/* ==========================================================================
   Scroll reveal — driven by animations.js (one shared IntersectionObserver)
   ========================================================================== */

.reveal,
.reveal-up,
.stagger > * {
    --reveal-name: motion-fade-up;
}

.reveal-left { --reveal-name: motion-fade-left; }
.reveal-right { --reveal-name: motion-fade-right; }
.reveal-scale { --reveal-name: motion-scale-in; }

/* Sideways travel becomes vertical on phones (no risk of horizontal overflow) */
@media (max-width: 600px) {
    .reveal-left,
    .reveal-right {
        --reveal-name: motion-fade-up;
    }
}

.has-motion :is(.reveal, .reveal-up, .reveal-left, .reveal-right, .reveal-scale, .stagger > *):not(.is-revealed) {
    opacity: 0;
}

.has-motion .is-revealed {
    animation: var(--reveal-name, motion-fade-up) var(--motion-duration) var(--motion-ease) calc(var(--reveal-index, 0) * var(--motion-stagger)) backwards;
}

/* If animations.js never runs, hidden content still appears after a short wait. */
.has-motion:not(.motion-ready) :is(.reveal, .reveal-up, .reveal-left, .reveal-right, .reveal-scale, .stagger > *):not(.is-revealed) {
    animation: motion-failsafe 1ms linear 2.5s forwards;
}

/* ==========================================================================
   Cards
   ========================================================================== */

.hover-lift {
    transition: transform 300ms var(--motion-ease), box-shadow 300ms var(--motion-ease), border-color 300ms var(--motion-ease), background-color 300ms var(--motion-ease);
}

.hover-lift:focus-visible {
    transform: translateY(-4px);
}

/* Short press feedback, mostly for touch */
.hover-lift:active {
    transform: scale(0.99);
    transition-duration: 90ms;
}

/* Touch screens keep :hover "stuck" after a tap, so cards only lift with a real pointer. */
@media (hover: hover) {
    .hover-lift:hover {
        transform: translateY(-4px);
    }

    .hover-lift:active {
        transform: translateY(-4px) scale(0.99);
    }
}

/* Category, author and publisher cards: the initial (avatar) and count badge respond with the card */
.card-initial,
.category-count,
.directory-card > span:last-child,
.directory-card-label {
    transition: transform 300ms var(--motion-ease), background-color 200ms var(--motion-ease), color 200ms var(--motion-ease), opacity 200ms var(--motion-ease);
}

.directory-card-label {
    opacity: 0.88;
}

.category-card:hover .card-initial,
.directory-card:hover .card-initial,
.category-card:focus-visible .card-initial,
.directory-card:focus-visible .card-initial {
    transform: scale(1.06);
}

.category-card:hover .category-count,
.directory-card:hover > span:last-child,
.category-card:focus-visible .category-count,
.directory-card:focus-visible > span:last-child {
    background: var(--accent-strong);
}

.directory-card:hover .directory-card-label,
.directory-card:focus-visible .directory-card-label {
    color: var(--on-dark);
    opacity: 1;
}

.category-card:focus-visible::after,
.directory-card:focus-visible::after {
    transform: scale(1.04);
}

.category-card:focus-visible .category-count span,
.directory-card:focus-visible i {
    transform: translateX(3px);
}

/* Card title underline grows left to right (names stay readable without hover) */
.category-name,
.directory-card strong {
    position: relative;
}

.category-name::after,
.directory-card strong::after {
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    height: 2px;
    border-radius: 2px;
    background: var(--accent);
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 300ms var(--motion-ease);
    content: '';
}

.category-card:hover .category-name::after,
.directory-card:hover strong::after,
.category-card:focus-visible .category-name::after,
.directory-card:focus-visible strong::after {
    transform: scaleX(1);
}

/* ==========================================================================
   Book covers
   ========================================================================== */

/* Overflow-hidden frame that takes the cover's place in the card grid, so the
   zoomed image never spills outside its rounded corners. */
.hover-zoom {
    position: relative;
    display: block;
    overflow: hidden;
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    box-shadow: var(--shadow-sm);
}

.book-card > .hover-zoom,
.catalog-book-card > .hover-zoom {
    align-self: start;
}

.related-book-card > .hover-zoom {
    border-radius: 6px;
    box-shadow: none;
}

.hover-zoom > img {
    box-shadow: none;
    transition: transform 400ms var(--motion-ease), opacity 400ms var(--motion-ease);
}

/* The cover zooms a little more than the card lifts; text never moves. */
@media (hover: hover) {
    .hover-zoom:hover > img,
    :is(.book-card, .catalog-book-card, .related-book-card):hover .hover-zoom > img {
        transform: scale(1.05);
    }
}

:is(.book-card, .catalog-book-card, .related-book-card):focus-within .hover-zoom > img {
    transform: scale(1.05);
}

/* Covers fade in once loaded. animations.js only marks images still loading,
   so cached covers appear immediately and broken ones are never hidden. */
.img-reveal {
    transition: opacity 400ms var(--motion-ease), transform 400ms var(--motion-ease);
}

.has-motion .img-reveal.is-loading {
    opacity: 0;
}

/* Skeleton sweep while a cover loads; it only shows after 300ms, so fast loads never flash. */
.has-motion .hover-zoom.is-loading::after {
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg, transparent, var(--line), transparent);
    opacity: 0;
    animation: motion-shimmer 1.4s ease-in-out 300ms infinite;
    pointer-events: none;
    content: '';
}

/* ==========================================================================
   Header and navigation
   ========================================================================== */

.brand-logo {
    transition: transform 300ms var(--motion-ease);
}

@media (hover: hover) {
    .brand:hover .brand-logo {
        transform: scale(1.05);
    }
}

.primary-navigation a {
    transition: color 200ms var(--motion-ease), background-color 200ms var(--motion-ease);
}

@media (min-width: 901px) {
    .primary-navigation a {
        position: relative;
    }

    /* Gold underline grows left to right on hover and shrinks away to the right */
    .primary-navigation a::after {
        position: absolute;
        right: 14px;
        bottom: 4px;
        left: 14px;
        height: 2px;
        border-radius: 2px;
        background: var(--accent);
        transform: scaleX(0);
        transform-origin: right center;
        transition: transform 300ms var(--motion-ease);
        content: '';
    }

    .primary-navigation a:hover::after,
    .primary-navigation a:focus-visible::after,
    .primary-navigation a.is-active::after {
        transform: scaleX(1);
        transform-origin: left center;
    }
}

@media (min-width: 901px) and (max-width: 1080px) {
    .primary-navigation a::after {
        right: 10px;
        left: 10px;
    }
}

/* Mobile menu: the panel slides in (site.css) and its items follow in a quick cascade */
@media (max-width: 900px) {
    .primary-navigation.is-open li {
        animation: motion-fade-up-sm 280ms var(--motion-ease) backwards;
    }

    .primary-navigation.is-open li:nth-child(2) { animation-delay: 30ms; }
    .primary-navigation.is-open li:nth-child(3) { animation-delay: 60ms; }
    .primary-navigation.is-open li:nth-child(4) { animation-delay: 90ms; }
    .primary-navigation.is-open li:nth-child(5) { animation-delay: 120ms; }
    .primary-navigation.is-open li:nth-child(6) { animation-delay: 150ms; }
    .primary-navigation.is-open li:nth-child(n + 7) { animation-delay: 180ms; }
}

.language-switcher a {
    transition: color 200ms var(--motion-ease), background-color 200ms var(--motion-ease), box-shadow 200ms var(--motion-ease);
}

.theme-toggle,
.nav-toggle {
    transition: background-color 200ms var(--motion-ease), color 200ms var(--motion-ease), border-color 200ms var(--motion-ease), transform 200ms var(--motion-ease);
}

/* ==========================================================================
   Buttons
   ========================================================================== */

.filter-submit,
.directory-search button,
.catalog-search button,
.cta-button,
.outline-link,
.details-link {
    transition: background-color 200ms var(--motion-ease), border-color 200ms var(--motion-ease), color 200ms var(--motion-ease), box-shadow 250ms var(--motion-ease), transform 250ms var(--motion-ease);
}

@media (hover: hover) {
    .filter-submit:hover,
    .directory-search button:hover,
    .catalog-search button:hover,
    .cta-button:hover,
    .outline-link:hover,
    .details-link:hover {
        transform: translateY(-2px);
    }

    .filter-submit:hover,
    .directory-search button:hover,
    .catalog-search button:hover {
        box-shadow: var(--shadow-md);
    }

    .cta-button:hover {
        box-shadow: var(--shadow-lg);
    }
}

/* Press: quick, and never blocks the click or the form submit */
.filter-submit:active,
.directory-search button:active,
.catalog-search button:active,
.cta-button:active,
.outline-link:active,
.details-link:active,
.pagination > a:active,
.pagination-pages a:active,
.theme-toggle:active,
.nav-toggle:active,
.slider-arrow:active,
.scroll-top:active {
    transform: scale(0.97);
    transition-duration: 80ms;
}

/* Submit feedback: a spinner replaces the label while the next page loads.
   animations.js only adds .is-submitting after a short delay, so fast responses never flash it. */
.catalog-search button { --spinner-color: var(--on-accent); }
.filter-submit,
.directory-search button { --spinner-color: var(--on-brand); }

.is-submitting {
    position: relative;
    color: transparent !important;
    cursor: progress;
}

.is-submitting::after {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 18px;
    height: 18px;
    margin: -9px 0 0 -9px;
    border: 2px solid var(--spinner-color, currentColor);
    border-right-color: transparent;
    border-radius: 50%;
    animation: motion-spin 700ms linear infinite;
    content: '';
}

/* ==========================================================================
   Forms
   ========================================================================== */

.directory-search,
.book-filter-form {
    transition: box-shadow 300ms var(--motion-ease), border-color 300ms var(--motion-ease);
}

.directory-search:focus-within,
.book-filter-form:focus-within {
    border-color: var(--brand-line);
    box-shadow: var(--shadow-md);
}

.filter-control input,
.filter-control select,
.directory-search input,
.catalog-search input {
    transition: border-color 200ms var(--motion-ease), box-shadow 200ms var(--motion-ease), background-color 200ms var(--motion-ease);
}

/* The select chevron follows focus */
.filter-control:has(select)::after {
    transition: background-color 200ms var(--motion-ease);
}

.filter-control:has(select:focus)::after {
    background: var(--brand);
}

.filter-control:focus-within > label {
    color: var(--brand-strong);
}

.filter-control > label,
.directory-search > label {
    transition: color 200ms var(--motion-ease);
}

/* ==========================================================================
   Text links (underline grows left to right)
   ========================================================================== */

.info-details a,
.reading-details a,
.clear-filters-link,
.site-footer p a {
    background: linear-gradient(currentColor, currentColor) left 100% / 0 1px no-repeat;
    transition: color 200ms var(--motion-ease), background-size 300ms var(--motion-ease);
}

.info-details a:hover,
.reading-details a:hover,
.clear-filters-link:hover,
.site-footer p a:hover,
.info-details a:focus-visible,
.reading-details a:focus-visible,
.clear-filters-link:focus-visible,
.site-footer p a:focus-visible {
    background-size: 100% 1px;
    text-decoration: none;
}

/* Back link arrow nudges toward its destination */
.back-link .link-arrow {
    display: inline-block;
    transition: transform 200ms var(--motion-ease);
}

.back-link:hover .link-arrow,
.back-link:focus-visible .link-arrow {
    transform: translateX(-3px);
}

/* Footer brand: the logo responds like the header logo */
@media (hover: hover) {
    .brand-footer:hover .brand-logo {
        transform: scale(1.05);
    }
}

/* ==========================================================================
   Pagination and badges
   ========================================================================== */

.pagination > a,
.pagination-pages a {
    transition: background-color 200ms var(--motion-ease), border-color 200ms var(--motion-ease), color 200ms var(--motion-ease), box-shadow 200ms var(--motion-ease), transform 200ms var(--motion-ease);
}

@media (hover: hover) {
    .pagination > a:hover,
    .pagination-pages a:not(.is-current):hover {
        transform: translateY(-2px);
    }
}

.availability {
    transition: background-color 200ms var(--motion-ease), color 200ms var(--motion-ease);
}

/* ==========================================================================
   Scroll to top
   ========================================================================== */

.scroll-top {
    position: fixed;
    z-index: 40;
    right: 20px;
    bottom: 20px;
    display: grid;
    width: 44px;
    height: 44px;
    padding: 0;
    place-items: center;
    border: 1px solid var(--brand-line);
    border-radius: 50%;
    background: var(--surface);
    color: var(--brand-strong);
    box-shadow: var(--shadow-md);
    cursor: pointer;
    opacity: 0;
    visibility: hidden;
    transform: translateY(12px);
    transition: opacity 250ms var(--motion-ease), transform 250ms var(--motion-ease), background-color 200ms var(--motion-ease), color 200ms var(--motion-ease), visibility 0s linear 250ms;
}

.scroll-top.is-visible {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition: opacity 250ms var(--motion-ease), transform 250ms var(--motion-ease), background-color 200ms var(--motion-ease), color 200ms var(--motion-ease);
}

.scroll-top svg {
    display: block;
    transition: transform 200ms var(--motion-ease);
}

.scroll-top:hover {
    background: var(--brand);
    color: var(--on-brand);
}

.scroll-top:hover svg,
.scroll-top:focus-visible svg {
    transform: translateY(-2px);
}

.scroll-top:focus-visible {
    outline: 3px solid var(--focus);
    outline-offset: 3px;
}

@media (max-width: 600px) {
    .scroll-top {
        right: 16px;
        bottom: 16px;
        width: 42px;
        height: 42px;
    }
}

/* ==========================================================================
   Reduced motion and print
   site.css already shortens every transition and animation globally; these
   rules also drop the movement itself and show hidden content at once.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
    .animate-fade-up,
    .animate-scale-in,
    .animate-sequence > *,
    .hero-section::before,
    .is-revealed,
    .primary-navigation.is-open li,
    .hover-zoom.is-loading::after {
        animation: none !important;
    }

    :is(.reveal, .reveal-up, .reveal-left, .reveal-right, .reveal-scale, .stagger > *),
    .img-reveal.is-loading {
        opacity: 1 !important;
    }

    .hover-lift:hover,
    .hover-lift:focus-visible,
    .hover-lift:active,
    .hover-zoom > img,
    .brand-logo,
    .card-initial,
    .link-arrow,
    .scroll-top,
    .scroll-top svg {
        transform: none !important;
    }

    /* Keep the loading spinner meaningful without spinning */
    .is-submitting::after {
        animation: none !important;
        border-right-color: var(--spinner-color, currentColor);
        opacity: 0.6;
    }
}

@media print {
    *,
    *::before,
    *::after {
        animation: none !important;
        transition: none !important;
    }

    :is(.reveal, .reveal-up, .reveal-left, .reveal-right, .reveal-scale, .stagger > *),
    .img-reveal {
        opacity: 1 !important;
        transform: none !important;
    }

    .scroll-top {
        display: none !important;
    }
}
