/* === base.css === */
/* base.css
 * ============================================
 * Core foundation: Root variables, resets, and base typography.
 * This file loads first and establishes the design system foundation.
 *
 * Purpose: Define CSS custom properties (variables) used throughout
 * the application and minimal typography/reset rules.
 *
 * Last updated: 2025-12-25
 * See: docs/delivery/74-bootstrap-vanilla-css/74-4.md
 */

/* === ROOT VARIABLES ===
 * Centralized theme configuration
 * These variables are used across all CSS files
 */
:root {
    /* Primary Theme Colors */
    --bs-primary: #8B4513;     /* Saddle Brown */
    --bs-primary-rgb: 139, 69, 19;
    --bs-secondary: #2F4F4F;   /* Dark Slate Gray */
    --bs-secondary-rgb: 47, 79, 79;

    /* Background Colors */
    --bs-body-bg: #F5F5DC;     /* Beige background */
    --bs-body-bg-rgb: 245, 245, 220;

    /* Border Colors */
    --bs-border-color: #D2B48C; /* Tan */

    /* Accent Colors */
    --bs-warning: #FFD700;     /* Goldenrod - for collection star */
    --bs-card-bg: #ffffff;     /* White card background */

    /* Bootstrap compiles this too, but bootstrap.css is deferred — inlined
     * critical.css needs the brand link color at first paint. */
    --bs-link-color: var(--bs-primary);

    /* Navigation and Layout */
    --navbar-height: 56px;
    --sticky-top-offset: var(--navbar-height);
    --sidebar-width: 280px;
    --alphabet-rail-width: 30px;
    --bs-primary-dark: #704010; /* Darker shade of primary */

    /* Z-index hierarchy - centralized for consistency */
    --z-index-base: 1;
    --z-index-card: 2;
    --z-index-header: 3;
    --z-index-progress: 4;
    --z-index-dropdown: 1000;
    --z-index-sticky: 1020;
    --z-index-overlay: 1025;
    --z-index-sidebar: 1029;
    --z-index-fixed: 1030;
    --z-index-navbar: 1030;
    --z-index-modal-backdrop: 1040;
    --z-index-floating-button: 1040;
    --z-index-modal: 1050;
    --z-index-popover: 1060;
    --z-index-tooltip: 1070;

    /* Spacing scale (matches Bootstrap) */
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 1rem;
    --space-4: 1.5rem;
    --space-5: 3rem;

    /* Chat FAB geometry. Shown only when the sticky mini-console is hidden
     * (tall desktop). Clearance is bottom padding so last-line copy can
     * scroll above the button — not a permanent right gutter. #644 leftover. */
    --chat-fab-size: 56px;
    --chat-fab-offset: 20px;
    --chat-fab-clearance: calc(var(--chat-fab-size) + var(--chat-fab-offset) + 1rem);

    /* Typography */
    --font-family-base: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-size-base: 1rem;
    --line-height-base: 1.5;

    /* Motion token set (transitions-2026-09 lock). One sitewide pair.
     * micro 150–220ms — toast, check, shake, price pop, tooltip, icon swap
     * content 280–400ms — tabs pill, panel, dissolve, quote-card reveal
     * Do not invent per-pattern durations; alias below. */
    --motion-micro: 180ms;
    --motion-content: 320ms;
    --motion-ease: cubic-bezier(0.22, 1, 0.36, 1);
    --tabs-dur: var(--motion-content);
    --tabs-ease: var(--motion-ease);
    --skeleton-dur: var(--motion-content);
    --skeleton-ease: var(--motion-ease);
    --toast-dur: var(--motion-micro);
    --toast-ease: var(--motion-ease);
    --check-dur: var(--motion-micro);
    --check-ease: var(--motion-ease);
    --shake-dur: var(--motion-micro);
    --panel-dur: var(--motion-content);
    --panel-ease: var(--motion-ease);
    --pop-dur: var(--motion-micro);
    --pop-ease: var(--motion-ease);
    --dissolve-dur: var(--motion-content);
    --dissolve-ease: var(--motion-ease);
    --tooltip-dur: var(--motion-micro);
    --tooltip-ease: var(--motion-ease);
    --icon-dur: var(--motion-micro);
    --icon-ease: var(--motion-ease);
    --stream-dur: var(--motion-micro);
    --tabs-bar-bg: var(--bs-card-bg);
    --tabs-pill-bg: var(--bs-primary);
    --tabs-pill-tint: color-mix(in srgb, var(--bs-primary) 12%, transparent);
    --tabs-text: var(--bs-primary);
    --tabs-text-active: #ffffff;
}

@media (prefers-reduced-motion: reduce) {
    :root {
        --motion-micro: 1ms;
        --motion-content: 1ms;
    }
}

/* 60px bar wherever the desktop nav renders (navbar-expand-md) */
@media (min-width: 768px) {
    :root {
        --navbar-height: 60px;
        --sticky-top-offset: var(--navbar-height);
    }
}

/* === BASE TYPOGRAPHY ===
 * Minimal resets that complement Bootstrap's reboot
 */
html {
    /* Bug fix 038: Removed overflow-x:hidden that masked layout bugs.
     * Root cause fixes applied to individual components instead.
     * See: specs/038-mobile-style-refresh/spec.md FR-001 */
}

/* Installed PWAs have no browser chrome to absorb elastic boundary gestures. */
@media (display-mode: standalone) {
    html,
    body {
        overscroll-behavior: none;
    }
}

body {
    font-family: var(--font-family-base);
    font-size: var(--font-size-base);
    line-height: var(--line-height-base);
    color: #212529;
    background-color: var(--bs-body-bg);
}

/* Heading defaults */
h1, h2, h3, h4, h5, h6 {
    margin-top: 0;
    margin-bottom: 0.5rem;
    font-weight: 500;
    line-height: 1.2;
}

/* Paragraph spacing */
p {
    margin-top: 0;
    margin-bottom: 1rem;
}

/* Link defaults - inherit color, remove underline */
a {
    color: inherit;
    text-decoration: none;
}

/* Image defaults */
img {
    max-width: 100%;
    height: auto;
    vertical-align: middle;
}

/* Navbar brand images - explicit dimensions prevent CLS */
.brand-quotewise .navbar-brand img {
    width: 144px;
    height: 32px;
    aspect-ratio: 144 / 32;
}
.brand-quotosaurus .navbar-brand img {
    width: 28px;
    height: 28px;
    aspect-ratio: 1;
}

/* Focus ring behavior: keyboard-only
 * Prevents persistent highlight after click/tap
 */
a:focus:not(:focus-visible),
button:focus:not(:focus-visible),
.btn:focus:not(:focus-visible),
.btn-link:focus:not(:focus-visible),
[data-bs-toggle]:focus:not(:focus-visible) {
    outline: none !important;
    box-shadow: none !important;
}

/* === BOOTSTRAP ICON ALIGNMENT FIX ===
 * Bootstrap icons default to vertical-align: -0.125em on ::before
 * This causes icons to sit low relative to adjacent text/elements.
 * Global override to middle alignment - no known use case for the default.
 */
.bi::before,
[class^="bi-"]::before,
[class*=" bi-"]::before {
    vertical-align: middle;
}

/* === ACCESSIBILITY: Color Contrast Fix ===
 * Bootstrap's disabled #6c757d is borderline WCAG AA, darken slightly
 */
.page-item.disabled .page-link {
    color: #595959; /* ~7:1 contrast vs white, still visually "muted" */
}

/* === colors.css === */
/* colors.css
 * ============================================
 * Color system with modern color-mix() for dynamic variants.
 * Centralizes all color-related styling and generates hover/active
 * states dynamically instead of hardcoding hex values.
 *
 * Browser support: Chrome 111+, Firefox 113+, Safari 16.2+
 * Fallback: Older browsers use the explicit --bs-* variables
 *
 * Last updated: 2025-12-25
 * See: docs/delivery/74-bootstrap-vanilla-css/74-4.md
 */

/* === DYNAMIC COLOR VARIANTS ===
 * Generate hover/active states using color-mix()
 * This replaces hardcoded hex values like #A0522D with computed blends
 */
:root {
    /* Primary color variants */
    --primary-hover: color-mix(in srgb, var(--bs-primary) 85%, black);
    --primary-active: color-mix(in srgb, var(--bs-primary) 70%, black);
    --primary-light: color-mix(in srgb, var(--bs-primary) 10%, white);

    /* Secondary color variants */
    --secondary-hover: color-mix(in srgb, var(--bs-secondary) 85%, black);
    --secondary-active: color-mix(in srgb, var(--bs-secondary) 70%, black);
    --secondary-light: color-mix(in srgb, var(--bs-secondary) 10%, white);

    /* Warning (gold star) variants */
    --warning-subtle: color-mix(in srgb, var(--bs-warning) 20%, white);
    --warning-bg: color-mix(in srgb, var(--bs-warning) 5%, white);
}

/* === TEXT COLORS === */
.text-warning,
.border-warning {
    color: var(--bs-warning) !important;
}

.bg-warning-subtle {
    background-color: var(--warning-bg) !important;
}

/* === FOCUS RING ===
 * Use primary color for focus states
 */
.btn:focus,
.btn:active,
a:focus {
    box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb), 0.25) !important;
}

/* === DISABLED BUTTON STATES === */
.btn.disabled,
.btn:disabled {
    pointer-events: auto !important;
    background-color: rgba(var(--bs-primary-rgb), 0.4) !important;
    border-color: rgba(var(--bs-primary-rgb), 0.4) !important;
    color: rgba(255, 255, 255, 0.7) !important;
    opacity: 0.7;
    cursor: not-allowed;
}

.btn-outline-primary.disabled,
.btn-outline-primary:disabled {
    pointer-events: auto !important;
    color: rgba(var(--bs-primary-rgb), 0.5) !important;
    border-color: rgba(var(--bs-primary-rgb), 0.3) !important;
    background-color: transparent !important;
}

.btn-outline-secondary.disabled,
.btn-outline-secondary:disabled {
    pointer-events: auto !important;
    color: rgba(var(--bs-secondary-rgb), 0.5) !important;
    border-color: rgba(var(--bs-secondary-rgb), 0.3) !important;
    background-color: transparent !important;
}

/* === ALERT COLORS ===
 * Themed to match vintage book aesthetic
 */
.alert {
    border: 1px solid transparent;
    border-radius: var(--bs-border-radius);
    padding: 1rem;
    margin-bottom: 1rem;
}

.alert-success {
    color: #43683C;
    background-color: #E8F3E6;
    border-color: #C8E6C2;
}

.alert-danger {
    color: #7A2A27;
    background-color: #F8E7E6;
    border-color: #F0CCCA;
}

.alert-info {
    color: var(--bs-secondary);
    background-color: #E6F0F0;
    border-color: #C8DCDC;
}

.alert-warning {
    color: #856404;
    background-color: #FFF8E6;
    border-color: #FFEEBA;
}

/* === CARD LINKS ===
 * Primary color for card links with hover state
 */
.card a:not(.btn),
.card-title a,
.originator-name a {
    color: var(--bs-primary);
    text-decoration: none;

    &:hover {
        color: var(--primary-hover);
    }
}

/* === NAVBAR COLORS === */
.navbar-dark {
    --bs-navbar-color: rgba(255, 255, 255, 0.75);
    --bs-navbar-hover-color: rgba(255, 255, 255, 0.9);
    --bs-navbar-active-color: #fff;
    --bs-navbar-brand-color: #fff;
    --bs-navbar-brand-hover-color: #fff;
}

.navbar.bg-secondary {
    background-color: var(--bs-secondary) !important;
}

/* === FEATURE ICONS === */
.feature-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 4rem;
    height: 4rem;
    margin-bottom: 1rem;
    border-radius: 50%;
    background-color: rgba(var(--bs-primary-rgb), 0.1);
}

/* === ALPHABET NAVIGATION ===
 * Letter buttons with primary color theme
 */
.alphabet-nav .btn {
    min-width: 2.5rem;
    font-weight: 500;
    border-color: var(--bs-primary);
    color: var(--bs-primary);

    &:hover {
        background-color: var(--primary-light);
        border-color: var(--bs-primary);
        color: var(--bs-primary);
    }
}

.alphabet-nav .btn-primary {
    font-size: 1.1em;
    color: white;
    background-color: var(--bs-primary);
    border-color: var(--bs-primary);

    &:hover {
        color: white;
        background-color: var(--primary-hover);
        border-color: var(--primary-hover);
    }
}

.alphabet-nav .nav-link {
    color: var(--bs-body-color);
    background-color: var(--bs-gray-100);
    border-radius: var(--bs-border-radius);
    margin: 0.25rem;
    transition: all 0.2s ease-in-out;

    &:hover {
        background-color: var(--bs-gray-200);
    }

    &.active {
        color: var(--bs-white);
        background-color: var(--bs-primary);
        font-weight: 500;
    }
}

/* === MOBILE ALPHABET SIDEBAR === */
.alphabet-link {
    display: block;
    width: 100%;
    text-align: center;
    padding: 0.2rem 0;
    color: var(--bs-primary);
    text-decoration: none;
    opacity: 0.7;
    transition: all 0.2s ease;

    &:hover {
        color: var(--bs-primary);
        background-color: var(--primary-light);
        opacity: 1;
    }

    &.active {
        color: white;
        background-color: var(--bs-primary);
        opacity: 1;
        border-radius: 0;
    }
}

.alphabet-link-clear {
    color: white;
    background-color: var(--bs-secondary);
    opacity: 1;
    margin: 0.5rem 1px 0.2rem;
    width: calc(100% - 2px);
    display: flex;
    justify-content: center;
    align-items: center;

    &:hover,
    &:active {
        color: white;
        background-color: var(--bs-secondary);
    }
}

/* === layout.css === */
/* layout.css
 * ============================================
 * Grid extensions, spacing utilities, and layout patterns.
 * Contains main-content positioning, sidebar layout, and
 * responsive container adjustments.
 *
 * Last updated: 2025-12-25
 * See: docs/delivery/74-bootstrap-vanilla-css/74-4.md
 */

/* === ANCHOR SCROLL OFFSET === */
/* Offset scroll targets below fixed navbar for anchor navigation */
[id] {
    scroll-margin-top: calc(var(--navbar-height) + 1rem);
}

/* === CONTAINER OVERRIDES === */
/* Neutralize .row negative margins when no sidebar */
main.container-fluid > .row {
    --bs-gutter-x: 0;
}

/* Bug fix 038: Neutralize footer row gutters to prevent overflow */
footer.container-fluid .row {
    --bs-gutter-x: 0;
}

/* === MAIN CONTENT LAYOUT === */
.main-content {
    position: relative;
    left: 0 !important;
    margin-left: 0;
    width: 100%;
    min-height: calc(100vh - var(--navbar-height) - 80px);
    padding-top: calc(var(--navbar-height) + 14px);  /* Tracks navbar + breathing room */
    padding-bottom: 2rem;
    padding-left: 0;
    padding-right: 0;
    transition: none;
    /* Bug fix 038: Contain inline size to prevent child overflow from causing horizontal scroll.
     * Defense layer after removing overflow-x:hidden from html. */
    contain: inline-size;
    overflow-x: clip;
}

@media (min-width: 576px) {
    .main-content {
        padding-left: 0;
        padding-right: 0;
    }
}

/* === FOOTER === */
footer.container-fluid {
    margin-top: auto;
    background-color: var(--bs-light);
    padding: 1rem 0;
}

/* === NAVBAR LAYOUT === */
.navbar {
    transition: none !important;
    z-index: var(--z-index-navbar);
    flex-wrap: nowrap !important;

    & * {
        transition: none !important;
    }

    & .btn-link {
        --bs-btn-padding-y: 0.375rem;
        --bs-btn-padding-x: 1rem;
        --bs-btn-font-size: 0.875rem;
        border: none;
        outline: none;

        &:hover,
        &:focus,
        &:active {
            background-color: transparent;
            box-shadow: none;
            outline: none;
        }
    }

    & .btn {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        height: 38px;
        padding-top: 0;
        padding-bottom: 0;
        line-height: 1.5;
        white-space: nowrap;
    }
}

.navbar-brand {
    margin-left: 1rem;
}

/* Mobile navbar adjustments */
@media (max-width: 767.98px) {
    .navbar-brand {
        margin-left: 0.5rem;
        font-size: 1rem;
        white-space: nowrap;
    }

    .mobile-nav-icons {
        gap: 0;
        flex-shrink: 1;
        flex-wrap: nowrap !important;
        min-width: 0;  /* Allow shrinking below content size */
        margin-right: 0.25rem;  /* Breathing room from edge */

        & .nav-link {
            padding: 0.375rem 0.5rem;
        }
    }

    .navbar .container-fluid {
        flex-wrap: nowrap !important;
    }
}

/* === CARD LAYOUT === */
.card {
    --bs-card-border-color: var(--bs-border-color);
    --bs-card-cap-bg: rgba(var(--bs-secondary-rgb), 0.03);
    transition: transform 0.2s ease-in-out;
    position: relative;
    z-index: var(--z-index-card);

    &:hover {
        transform: translateY(-5px);
    }

    & .stretched-link::after {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
    }
}

/* === MODAL LAYOUT === */
.modal-content {
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
}

.modal-footer {
    gap: 0.5rem;
    padding: 0.75rem;
}

/* A <form> wrapping header/body/footer is the flex child of .modal-content, so
   .modal-body never inherits a constrained height and .modal-dialog-scrollable's
   overflow:hidden clips the footer instead of scrolling. Relay the flex chain. */
.modal-dialog-scrollable .modal-content > form {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* === DROPDOWN LAYOUT === */
/* Deferred-Bootstrap mirror: the rules below style .dropdown-menu but the
   `display: none` that HIDES it is Bootstrap's, and Bootstrap is preload+onload.
   Without this every dropdown on the site renders fully open at first paint and
   the navbar collapses ~350px when Bootstrap lands, on every page load.
   .dropdown-menu.show outranks it, so JS-opened menus are unaffected. */
.dropdown-menu { display: none; }
.dropdown-menu.show { display: block; }

.dropdown-menu {
    --bs-dropdown-min-width: 240px;
    --bs-dropdown-padding-y: 0.5rem;
    --bs-dropdown-border-color: var(--bs-border-color);
    --bs-dropdown-border-radius: var(--bs-border-radius);
    --bs-dropdown-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}

.dropdown-item {
    --bs-dropdown-item-padding-y: 0.75rem;
    --bs-dropdown-item-padding-x: 1.25rem;
    display: flex;
    align-items: center;

    &:active {
        background-color: var(--bs-primary);
    }
}

.dropdown-header {
    --bs-dropdown-header-color: var(--bs-secondary);
    --bs-dropdown-header-padding-y: 0.5rem;
    --bs-dropdown-header-padding-x: 1.25rem;
    font-weight: 600;
    /* h6 reboot line-height; group labels are divs so the heading rule no longer applies */
    line-height: 1.2;
}

/* === STICKY HEADER === */
/* Fix: Prevent navbar text wrapping in mid-desktop range instead of increasing height */
@media (min-width: 768px) and (max-width: 1050px) {
    .navbar-nav {
        flex-wrap: nowrap !important;  /* Prevent wrapping to second line */
    }
    .navbar-nav .nav-link {
        padding-left: 0.375rem;
        padding-right: 0.375rem;
        font-size: 0.875rem;  /* Slightly smaller text to fit */
        white-space: nowrap;
    }
}

.sticky-top {
    position: sticky !important;
    top: var(--sticky-top-offset) !important;
    z-index: var(--z-index-sticky);
    backdrop-filter: blur(10px);
    background-color: rgba(255, 255, 255, 0.95) !important;
    box-shadow: 0 -3px 0 0 white;
    padding-bottom: 1rem;
}

/* Cover gap when navbar height varies - use CSS var for consistency */
.sticky-top::before {
    content: '';
    position: absolute;
    top: calc(-1 * var(--navbar-height, 60px));
    left: 0;
    right: 0;
    height: var(--navbar-height, 60px);
    background: inherit;
}

@media (max-width: 767.98px) {
    .sticky-top {
        z-index: calc(var(--z-index-navbar) - 1);
    }
}

.sticky-top + .container {
    padding-top: 0.5rem;
}

#stickyHeader {
    --sticky-header-offset: 24px;
    background-color: white;
    border-bottom: 3px solid var(--bs-primary);
    margin-top: -7px;
    margin-left: calc(-1 * var(--sticky-header-offset));
    margin-right: calc(-1 * var(--sticky-header-offset));
    padding-left: var(--sticky-header-offset);
    padding-right: var(--sticky-header-offset);
    padding-top: 0.75rem;
    padding-bottom: 0.75rem;
    /* Bug fix 038: Prevent negative margins from causing horizontal overflow.
     * Clip content that extends beyond container bounds. */
    overflow-x: clip;

    & .btn-sm {
        padding-top: 0.15rem;
        padding-bottom: 0.15rem;
        font-size: 0.8rem;
    }

    & .sort-pill-toggle .btn {
        padding-top: 0.15rem;
        padding-bottom: 0.15rem;
    }
}

/* === BROWSE DETAIL TOOLBAR === */
/* Everything for this page lives here (core bundle is sync, so it all
   commits at first paint — nothing needs mirroring in critical.css). */
html:has(body.browse-detail-page) {
    scroll-padding-top: calc(var(--navbar-height) + 48px);
}

/* Drop the +14px breathing gap so the bar's in-flow position == its sticky
   top (no float-then-stick). Specificity (0,2,1) beats .main-content above. */
body.browse-detail-page .main-content {
    padding-top: var(--navbar-height);
}

/* Full-bleed: remove the page gutter so the bar spans the viewport like
   .navbar (content re-centers in its own .container). !important + (0,2,2)
   specificity: must beat bug-038's mobile rule below
   (.container-fluid:not(footer) { padding !important } at <576px). */
body.browse-detail-page main.container-fluid {
    padding-inline: 0 !important;
}

.browse-subnav {
    position: sticky;
    top: var(--navbar-height);
    z-index: var(--z-index-sticky);
    display: flex;
    align-items: center;
    gap: var(--space-2);
    height: 48px;
    /* Aligns controls with .navbar > .container-fluid's content edge. Literal
       because --bs-gutter-x is zeroed by main.container-fluid > .row above. */
    padding-inline: 0.75rem;
    background-color: color-mix(in srgb, var(--bs-card-bg) 96%, transparent);
    border-bottom: 3px solid var(--bs-primary);
    backdrop-filter: blur(10px);
}

.browse-subnav-breadcrumb {
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    gap: var(--space-1);
    min-width: 0;
    padding: 0;
    margin: 0;
    overflow: hidden;
    font-size: 0.855rem;
    list-style: none;

    & li {
        flex: none;
        min-width: 0;
        white-space: nowrap;
    }

    & a,
    & .browse-subnav-current {
        display: block;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    & a {
        color: var(--bs-secondary);
    }

    & .browse-subnav-parent {
        flex: 0 4 auto;
    }

    & .browse-subnav-current {
        flex: 0 2 auto;
        font-weight: 600;
    }
}

.browse-subnav-count {
    flex: none;
    color: var(--bs-secondary);
    font-size: 0.8125rem;
    white-space: nowrap;
}

.browse-subnav-tabs {
    display: flex;
    flex: none;
    align-items: stretch;
    height: 34px;
    overflow: hidden;
    background-color: var(--bs-card-bg);
    border: 1px solid var(--bs-primary);
    border-radius: var(--bs-border-radius, 0.375rem);

    & a {
        display: flex;
        align-items: center;
        gap: 0.4rem;
        padding-inline: 0.75rem;
        color: var(--bs-primary);
        font-size: 0.855rem;
        white-space: nowrap;
    }

    & a + a {
        border-left: 1px solid var(--bs-primary);
    }

    & a[aria-selected="true"] {
        color: var(--bs-primary);
        font-weight: 600;
        background-color: transparent;
    }
}

.browse-subnav-sort {
    position: relative;
    flex: none;

    & summary {
        display: flex;
        align-items: center;
        gap: var(--space-1);
        height: 34px;
        padding-inline: 0.625rem;
        color: var(--bs-primary);
        font-size: 0.855rem;
        white-space: nowrap;
        cursor: pointer;
        user-select: none;
        background-color: var(--bs-card-bg);
        border: 1px solid var(--bs-primary);
        border-radius: 0.375rem;
        list-style: none;
    }

    & summary::-webkit-details-marker {
        display: none;
    }

    &[open] summary {
        color: var(--bs-card-bg);
        background-color: var(--bs-primary);
    }
}

.browse-subnav-sort-chevron {
    font-size: 0.65rem;
}

.browse-subnav-sort-menu {
    position: absolute;
    top: calc(100% + var(--space-1));
    right: 0;
    z-index: var(--z-index-dropdown);
    display: grid;
    min-width: 10rem;
    padding: var(--space-1);
    background-color: var(--bs-card-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: 0.375rem;
    box-shadow: 0 10px 26px rgba(0, 0, 0, 0.18);

    & a {
        padding: 0.45rem 0.625rem;
        color: inherit;
        font-size: 0.855rem;
        border-radius: 0.25rem;
    }

    & a:hover {
        background-color: var(--bs-body-bg);
    }

    & a[aria-current="true"] {
        color: var(--bs-primary);
        font-weight: 600;
        background-color: color-mix(in srgb, var(--bs-primary) 12%, transparent);
    }
}

.browse-subnav-shuffle {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    color: var(--bs-secondary);
    background-color: var(--bs-card-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: 0.375rem;
}

.browse-subnav-tabs a:focus-visible,
.browse-subnav-sort summary:focus-visible,
.browse-subnav-sort-menu a:focus-visible,
.browse-subnav-shuffle:focus-visible {
    outline: 2px solid var(--bs-secondary);
    outline-offset: 2px;
}

.originator-tag-page-header {
    padding-top: var(--space-3);
}

/* Byte-equal mirror of deferred Bootstrap's responsive h1 sizing so the page
   h1 doesn't resize (and shift the feed) when Bootstrap lands. */
.originator-tag-page-header h1 {
    font-size: calc(1.375rem + 1.5vw);
}

@media (min-width: 1200px) {
    .originator-tag-page-header h1 {
        font-size: 2.5rem;
    }
}

/* Mirrors the shell's .py-4 (deferred Bootstrap) — without it everything
   below the toolbar shifts 24px down when Bootstrap lands. */
.originator-tag-detail-shell {
    padding-block: 1.5rem;
}

/* <576px: keep the pre-full-bleed content inset (4px former bug-038 mobile
   gutter + 12px container padding), and align the bar's controls with the
   navbar's 4px mobile content edge (bug-038 rule above). */
@media (max-width: 575.98px) {
    .originator-tag-detail-shell {
        padding-inline: 1rem;
    }

    .browse-subnav {
        padding-inline: 0.25rem;
    }
}

/* Feed grid — byte-equal mirror of deferred Bootstrap (.row, .row-cols-1,
   .row-cols-md-2, .row-cols-xl-3, .g-2): commits the 1/2/3-col geometry at
   first paint (core bundle is sync; Bootstrap is preload+onload). */
.browse-feed.row {
    --bs-gutter-x: 0.5rem;
    --bs-gutter-y: 0.5rem;
    display: flex;
    flex-wrap: wrap;
    margin-top: calc(-1 * var(--bs-gutter-y));
    margin-right: calc(-0.5 * var(--bs-gutter-x));
    margin-left: calc(-0.5 * var(--bs-gutter-x));
}

.browse-feed.row > * {
    flex-shrink: 0;
    width: 100%;
    max-width: 100%;
    padding-right: calc(var(--bs-gutter-x) * 0.5);
    padding-left: calc(var(--bs-gutter-x) * 0.5);
    margin-top: var(--bs-gutter-y);
}

@media (min-width: 768px) {
    .browse-feed.row > * {
        width: 50%;
    }
}

@media (min-width: 1200px) {
    .browse-feed.row > * {
        width: 33.3333%;
    }
}

.browse-feed article {
    scroll-margin-top: calc(var(--navbar-height) + 48px + var(--space-2));
}

.browse-feed.row > .originator-tag-feed-wide,
.browse-feed.row > .browse-feed-wide {
    flex: 0 0 100%;
    width: 100%;
}

[data-browse-results][aria-busy="true"] .browse-feed {
    opacity: 0.65;
}

@media (max-width: 767.98px) {
    .browse-subnav {
        gap: var(--space-1);
    }

    .browse-subnav-count-label,
    .browse-subnav-tab-label,
    .browse-subnav-sort-label,
    .browse-subnav-sort-chevron {
        display: none;
    }

    /* Keep the root crumb (the back-link to the browse list) visible on mobile,
       and let it shrink last so a top-level category always has a way back up. */
    .browse-subnav-breadcrumb .browse-subnav-ancestor {
        flex: 0 1 auto;
    }

    .browse-subnav-breadcrumb .browse-subnav-parent {
        max-width: 4.5rem;
    }

    .browse-subnav-tabs a {
        padding-inline: 0.625rem;
    }

    .browse-subnav-sort summary {
        justify-content: center;
        width: 34px;
        padding: 0;
    }
}

/* === SIDEBAR LAYOUT === */
.sidebar {
    position: fixed;
    top: var(--navbar-height);
    left: calc(-1 * var(--sidebar-width));
    height: calc(100vh - var(--navbar-height));
    width: var(--sidebar-width);
    z-index: var(--z-index-sidebar);
    background-color: var(--bs-body-bg);
    border-right: 1px solid var(--bs-border-color);
    padding: 1rem;
    transition: left 0.3s ease-in-out;
    overflow-y: auto;

    &.show {
        left: 0;
    }
}

/* Mobile: Narrower sidebar for better proportions */
@media (max-width: 767.98px) {
    .sidebar {
        --sidebar-width: 260px;
    }
}

.sidebar-overlay {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.5);
    z-index: var(--z-index-overlay);

    &.show {
        display: block;
    }
}

/* === FLOATING ACTION BUTTONS === */
.floating-actions {
    position: fixed;
    right: 1rem;
    bottom: 1rem;
    z-index: var(--z-index-floating-button);
    display: flex;
    flex-direction: column;
    gap: 0.5rem;

    & .btn {
        width: 48px;
        height: 48px;
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
        background-color: var(--bs-primary);
        color: white;
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
        transition: transform 0.2s ease-in-out, background-color 0.2s ease-in-out;

        &:hover {
            transform: scale(1.1);
            background-color: var(--bs-primary-dark);
        }

        &:active {
            transform: scale(0.95);
        }
    }
}

#mobile-collection-floating-button,
#mobile-account-floating-button {
    position: fixed;
    bottom: 1rem;
    right: 1rem;
    z-index: var(--z-index-floating-button);
    width: 48px;
    height: 48px;
    border-radius: 50%;
    background-color: var(--bs-primary);
    color: white;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    padding: 0;
    line-height: 1;

    &:hover {
        background-color: var(--bs-primary-dark);
    }
}

/* Chat entry chrome (#644 leftover / PR 646 follow-up).
 *
 * The sticky mini-console includes Chat whenever it is visible. Hide the
 * chat FAB in that case (default) and show it only on the complementary
 * tall-desktop query — the same condition that sets `.mini-console { display: none }`.
 * Do not keep a parallel max-height-only hide; the two must not drift.
 *
 * Tab bar visible (FAB hidden): (max-width: 767.98px), (max-height: 499.98px)
 * FAB visible (tab bar hidden): (min-width: 768px) and (min-height: 500px)
 */
#chat-toggle-lazy {
    display: none;
}

@media (min-width: 768px) and (min-height: 500px) {
    #chat-toggle-lazy {
        display: flex;
    }

    /* End-of-scroll padding so a last line can move above the remaining FAB.
     * No right gutter — overlap on wide windows without a tab bar is fine. */
    html {
        scroll-padding-bottom: var(--chat-fab-clearance);
    }

    body {
        padding-bottom: var(--chat-fab-clearance);
    }
}

/* Mini-console chrome: lift other floating buttons above the tab bar. */
@media (max-width: 767.98px), (max-height: 499.98px) {
    .floating-actions,
    #mobile-collection-floating-button,
    #mobile-account-floating-button {
        bottom: calc(50px + 1rem + env(safe-area-inset-bottom, 0px));
    }
}

/* === MOBILE ALPHABET SIDEBAR === */
.alphabet-sidebar {
    position: fixed;
    top: calc(var(--navbar-height) + 1rem);
    right: 0;
    height: calc(100vh - var(--navbar-height) - 2rem);
    width: var(--alphabet-rail-width);
    background-color: rgba(255, 255, 255, 0.9);
    z-index: var(--z-index-sidebar);
    display: flex;
    flex-direction: column;
    padding: 0;
    box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1);
}

/* Alphabetical originators only: inset the pane so the fixed rail has its own
   column. Random/portraits omit the rail and keep the full-width pane.
   Specificity beats deferred Bootstrap `.container` padding. */
@media (max-width: 767.98px) {
    .container.originator-list-container--az {
        padding-right: calc((var(--bs-gutter-x, 1.5rem) * 0.5) + var(--alphabet-rail-width));
    }
}

/* === DESKTOP OVERRIDES === */
@media (min-width: 768px) {
    .alphabet-sidebar {
        display: none;
    }

    .has-sidebar .sidebar {
        position: fixed;
        top: var(--navbar-height);
        left: 0;
        height: calc(100vh - var(--navbar-height));
        width: var(--sidebar-width);
        z-index: var(--z-index-sidebar);
        background-color: var(--bs-body-bg);
        border-right: 1px solid var(--bs-border-color);
        padding: 1rem;
        overflow-y: auto;
    }

    .has-sidebar .main-content {
        margin-left: var(--sidebar-width);
        width: calc(100% - var(--sidebar-width));
        padding-left: 15px;
        padding-right: 15px;
        box-sizing: border-box;
    }

    .has-sidebar .row {
        margin-left: 0;
        margin-right: 0;
    }

    .sidebar-overlay,
    .floating-actions {
        display: none;
    }

    .has-sidebar footer .container {
        margin-left: var(--sidebar-width);
        width: calc(100% - var(--sidebar-width));
    }
}

/* Collection workbench keeps the sidebar as a mobile overlay until Bootstrap lg. */
body.collection-workbench-page #mobile-collection-floating-button {
    display: flex;
}

@media (min-width: 768px) and (max-width: 991.98px) {
    body.collection-workbench-page.has-sidebar .sidebar {
        left: calc(-1 * var(--sidebar-width));
    }

    body.collection-workbench-page.has-sidebar .sidebar.show {
        left: 0;
    }

    body.collection-workbench-page.has-sidebar .main-content {
        margin-left: 0;
        width: 100%;
    }

    body.collection-workbench-page .floating-actions {
        display: flex;
    }

    body.collection-workbench-page #mobile-collection-floating-button {
        bottom: 5rem;
    }

    body.collection-workbench-page .sidebar-overlay.show {
        display: block;
    }

    body.collection-workbench-page.has-sidebar footer .container {
        width: auto;
        margin-left: auto;
    }
}

@media (min-width: 992px) {
    body.collection-workbench-page .sidebar-overlay,
    body.collection-workbench-page #mobile-collection-floating-button {
        display: none !important;
    }

    body.collection-workbench-page .floating-actions {
        display: flex;
        bottom: 5rem;
    }
}

/* === MOBILE LAYOUT OVERRIDES === */
@media (max-width: 767.98px) {
    /* Let content flow naturally on mobile - don't force viewport height */
    .main-content {
        min-height: auto;
        padding-bottom: 0.5rem; /* Tighter on mobile (was 2rem) */
    }
}

@media (max-width: 575.98px) {
    /* Make bg-light transparent on mobile */
    .container-fluid.bg-light {
        background-color: transparent !important;
    }

    /* Bug fix 038: Tighter mobile edge padding for cards (0.25rem from 0.5rem).
     * BEFORE: 0.5rem (~18px total with card padding)
     * AFTER: 0.25rem (~14px total with card padding)
     * Footer excluded - keeps standard padding for readability.
     * See: specs/038-mobile-style-refresh/spec.md FR-005 */
    .container-fluid:not(footer) {
        padding-left: 0.25rem !important;
        padding-right: 0.25rem !important;
    }

    /* Footer keeps standard padding on mobile */
    footer.container-fluid {
        padding-left: 0.5rem !important;
        padding-right: 0.5rem !important;
    }

    /* Bug fix 038: Restore inner container padding for footer readability */
    footer.container-fluid > .container {
        padding-left: 0.5rem !important;
        padding-right: 0.5rem !important;
    }

    .container-fluid.py-3 {
        padding-top: 0.5rem !important;
        padding-bottom: 0.5rem !important;
    }

    .container-fluid > .container {
        padding-left: 0 !important;
        padding-right: 0 !important;
        max-width: 100% !important;
    }

    /* Bug fix 038: Neutralize row gutters when container padding is zeroed.
     * Without this, .row negative margins (-12px) cause 4px overflow. */
    .container-fluid > .container .row {
        --bs-gutter-x: 0;
    }

    /* Bug fix 038: Eliminate negative margins on mobile to prevent overflow.
     * Desktop uses negative margins for full-bleed effect, mobile uses tight padding. */
    #stickyHeader {
        --sticky-header-offset: 0;
        margin-left: 0;
        margin-right: 0;
        padding-left: 0.5rem;
        padding-right: 0.5rem;
    }

    .sticky-top {
        top: var(--sticky-top-offset) !important;
    }

    .row.mb-4 {
        margin-bottom: 0.75rem !important;
    }

    h2.text-center.mb-3 {
        font-size: 1.25rem;
        margin-bottom: 0.5rem !important;
    }
}

/* === HERO SECTION LAYOUT === */
.display-4 {
    font-size: 1.8rem;
    line-height: 1.2;
    margin-bottom: 1rem;
}

.lead {
    font-size: 1.1rem;
}

/* === FILTER/SORT LAYOUT === */
.filter-sort-container-wrapper {
    flex-grow: 1;
    width: 100%;
    box-sizing: border-box;
}

.filter-sort-flex {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 1rem;
    margin-top: 1rem;
}

.filter-item {
    width: 100%;
}

@media (min-width: 768px) {
    .filter-sort-flex {
        flex-direction: row;
        align-items: center;
    }

    .filter-item {
        width: auto;
    }
}

/* === SORT PILL TOGGLE === */
.sort-pill-toggle {
    border-radius: var(--bs-border-radius, 0.375rem);
    overflow: hidden;

    & .btn {
        border-radius: 0;
        padding: 0.25rem 0.5rem;

        &:first-child {
            border-top-left-radius: var(--bs-btn-border-radius, 0.375rem);
            border-bottom-left-radius: var(--bs-btn-border-radius, 0.375rem);
        }

        &:last-child {
            border-top-right-radius: var(--bs-btn-border-radius, 0.375rem);
            border-bottom-right-radius: var(--bs-btn-border-radius, 0.375rem);
        }
    }
}

/* === INFO TOGGLE CHEVRON === */
/* First-line band: the button is one .small line box so the glyph sits on
   the description's first-line midline. The row uses flex-start — never
   align-items:center — so a wrapping description cannot pull the chevron
   down to the block midpoint. */
.info-toggle-row {
    --info-first-line: calc(0.875em * var(--line-height-base, 1.5));
    display: flex;
    align-items: flex-start;
    gap: 0.25rem;
    margin-top: 0.25rem;
}

.info-toggle-icon {
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    /* Kill .btn border so a 1px transparent edge does not shrink the band. */
    border: 0;
    height: var(--info-first-line);
    width: var(--info-first-line);
    min-height: 0;
    padding: 0;
    line-height: 1;
    flex-shrink: 0;

    &:hover {
        text-decoration: none;
    }
}

.info-chevron {
    display: block;
    line-height: 1;
    transition: transform 0.35s ease;
    font-size: 1.1rem;
    font-weight: bold;
    /* The 1.5 line-box center sits below the visual midline of `.small`
       text (descender leading). Nudge up in parent space — `translate`
       is not rotated with `transform`, so collapsed rotate(-90deg) and
       expanded stay on the same first-line band. */
    translate: 0 -0.12em;
}

/* Chevron points right when collapsed, down when expanded */
.info-toggle-icon[aria-expanded="false"] .info-chevron {
    transform: rotate(-90deg);
}

.info-toggle-icon[aria-expanded="true"] .info-chevron {
    transform: rotate(0deg);
}

/* Larger chevron on mobile for better touch targets */
@media (max-width: 768px) {
    .info-chevron {
        font-size: 1.3rem;
    }
}

/* === SOURCE GENRE CHICLETS ===
   One wrapping row in the source byline's text column (beside the info
   chevron's indent). flex-start keeps the icon on the first chip line's
   midline when chips wrap. Chips are 24px tall (WCAG 2.5.8 target size). */
.source-genres {
    --source-genre-chip-h: 1.5rem;
    display: flex;
    align-items: flex-start;
    gap: 0.375rem;
    margin-top: 0.375rem;
    min-width: 0;
}

/* Scoped under .source-genres to beat the later global .bi { height: 1em }
   in utilities.css, which would otherwise shrink the box off the chip row. */
.source-genres .source-genres-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    /* Fixed box: the bookshelf mask lands with the deferred bundle, so the
       slot is reserved here to keep the row from shifting. */
    width: 1em;
    height: var(--source-genre-chip-h);
    color: var(--bs-secondary-color, #6c757d);
    font-size: 0.875rem;
    line-height: 1;
}

.source-genres-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.375rem;
    min-width: 0;
    margin: 0;
    padding: 0;
    list-style: none;

    & > li {
        min-width: 0;
        max-width: 100%;
    }
}

.source-genre-chip {
    display: inline-block;
    box-sizing: border-box;
    max-width: 100%;
    height: var(--source-genre-chip-h);
    padding: 0 0.625rem;
    overflow: hidden;
    color: var(--bs-secondary);
    font-size: 0.75rem;
    font-weight: 500;
    line-height: calc(var(--source-genre-chip-h) - 2px);
    text-decoration: none;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: top;
    background-color: var(--bs-light, #f8f9fa);
    border: 1px solid var(--bs-border-color);
    border-radius: 999px;
    transition: background-color 0.2s ease, color 0.2s ease;

    &:hover {
        color: var(--bs-primary);
        text-decoration: none;
        background-color: var(--bs-gray-200, #e9ecef);
    }

    &:focus-visible {
        outline: 3px solid rgba(var(--bs-primary-rgb), 0.45);
        outline-offset: 2px;
    }
}

/* === TOUCH DEVICE OPTIMIZATIONS === */
@media (hover: none) {
    .sidebar {
        -webkit-overflow-scrolling: touch;
    }
}

/* Site footer grid — byte-equal mirror of deferred Bootstrap (.row, .col-md-4,
   .mb-3, h5.fw-bold, .small). Bootstrap is preload+onload while this bundle is
   sync, so without the mirror the footer paints as a full-width stack and
   collapses ~330px into three columns when Bootstrap lands — on every page.
   Same technique, and same reason, as .browse-feed.row above. */
.site-footer .row {
    --bs-gutter-x: 1.5rem;
    --bs-gutter-y: 0;
    display: flex;
    flex-wrap: wrap;
    margin-top: calc(-1 * var(--bs-gutter-y));
    margin-right: calc(-0.5 * var(--bs-gutter-x));
    margin-left: calc(-0.5 * var(--bs-gutter-x));
}

.site-footer .row > * {
    flex-shrink: 0;
    width: 100%;
    max-width: 100%;
    padding-right: calc(var(--bs-gutter-x) * 0.5);
    padding-left: calc(var(--bs-gutter-x) * 0.5);
    margin-top: var(--bs-gutter-y);
}

.site-footer .mb-3 {
    margin-bottom: 1rem;
}

.site-footer h5 {
    margin-top: 0;
    margin-bottom: 0.5rem;
    font-size: 1.25rem;
    line-height: 1.2;
}

.site-footer .small {
    font-size: 0.875em;
}

.site-footer .list-unstyled {
    padding-left: 0;
    list-style: none;
    margin-top: 0;
    margin-bottom: 1rem;
}

@media (min-width: 768px) {
    .site-footer .col-md-4 {
        flex: 0 0 auto;
        width: 33.3333%;
    }
}

/* === RESPONSIVE DISPLAY UTILITIES (deferred-Bootstrap mirror) ===
   Bootstrap is preload+onload; without these a `d-none d-md-flex` row is hidden
   at first paint and pops in when Bootstrap lands (the originator browse control
   row, for one). critical.css mirrors .d-none/.d-md-none/.d-md-block already.

   Deliberately NOT here: a generic .row / .col-* mirror. It looks like the
   obvious fix for the remaining grid shifts, but this bundle loads after
   Bootstrap, so a global `.row { display: flex }` outranks layouts that use a
   different display mode on the same element -- it broke the shaped quote hero
   (flow-root) and pushed the originator page's CLS from ~0 to 0.74. Grids that
   must not move get a SCOPED mirror instead: see .browse-feed.row above,
   .site-footer .row below, and .originator-card-grid in originator-card.css. */
@media (min-width: 768px) {
    .d-md-flex { display: flex !important; }
    .d-md-inline { display: inline !important; }
    .d-md-inline-block { display: inline-block !important; }
}

/* Collection workbench header-stack + reorder rail geometry (qw-e07gp Finding 3).
   collection-workbench.css lives entirely in the DEFERRED components bundle
   (media=print/onload -- see base.html and build-css.js's COMPONENT_FILES), but the
   workbench template renders these elements at first paint. Without this mirror the
   bulk toolbar shows at full height instead of being tucked under visibility:hidden
   in the shared grid-stack cell, and the reorder gutter/rail render unpositioned,
   until the deferred bundle lands and the page reflows.
   Mirrors ONLY the structural/geometry rules from collection-workbench.css -- colors,
   cursors, and the grip button's own styling stay deferred. Keep in step with that
   file, which remains the single source of truth for the workbench. */
.workbench-header-stack {
    display: grid;
}

.workbench-header-stack > * {
    grid-area: 1 / 1;
}

#collection-bulk-toolbar-slot {
    visibility: hidden;
}

.collection-reorder-gutter {
    padding-left: 3rem;
}

.collection-sort-rail {
    position: absolute;
    top: 0;
    right: 100%;
    bottom: 0;
    width: 3rem;
}

/* Collection browse tabs (#643): Featured / Recent / Popular / All must stay
   one row at 320px. Bootstrap .nav wraps at the default 1rem link padding.
   Two-class selector beats deferred `.nav { flex-wrap: wrap }`. Token +
   padding-inline together cover first paint (core, sync) and the Bootstrap
   landing (`padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x)`). */
.collection-browse-tabs.nav {
    --bs-nav-link-padding-x: 0.5rem;
    display: flex;
    flex-wrap: nowrap;
    max-width: 100%;
    overflow-x: auto;
}

.collection-browse-tabs .nav-link {
    padding-inline: 0.5rem;
    white-space: nowrap;
}

@media (max-width: 389.98px) {
    .collection-browse-tabs.nav {
        --bs-nav-link-padding-x: 0.375rem;
        --bs-nav-link-font-size: 0.875rem;
    }

    .collection-browse-tabs .nav-link {
        padding-inline: 0.375rem;
        font-size: 0.875rem;
    }
}

/* === utilities.css === */
/* utilities.css
 * ============================================
 * Semantic wrapper classes that replace common Bootstrap utility combos.
 * These classes self-document intent and reduce template verbosity.
 *
 * MAPPING:
 * | Bootstrap Combo                                  | Semantic Class   |
 * |--------------------------------------------------|------------------|
 * | d-flex align-items-center justify-content-between| .flex-spread     |
 * | d-flex align-items-center                        | .flex-center     |
 * | d-flex flex-column                               | .flex-column     |
 * | d-flex d-md-none align-items-center              | .mobile-nav      |
 * | d-inline-block d-md-none                         | .mobile-only     |
 * | d-none d-md-inline-block                         | .desktop-only    |
 * | bi fs-5                                          | .nav-icon        |
 * | flex-shrink-0                                    | .flex-shrink-0   |
 * | min-width: 0                                     | .min-width-0     |
 *
 * Last updated: 2025-12-25
 * See: docs/delivery/74-bootstrap-vanilla-css/74-4.md
 */

/* === FLEXBOX UTILITIES === */

/* Flex with space-between - replaces 3 utility classes */
.flex-spread {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

/* Flex with center alignment - replaces 2 utility classes */
.flex-center {
    display: flex;
    align-items: center;
}

/* Flex column layout */
.flex-column {
    display: flex;
    flex-direction: column;
}

/* Prevent flex item from shrinking */
.flex-shrink-0 {
    flex-shrink: 0;
}

/* Allow shrinking below content width */
.min-width-0 {
    min-width: 0;
}

/* === RESPONSIVE VISIBILITY === */

/* Visible on mobile only - replaces d-inline-block d-md-none */
.mobile-only {
    display: inline-block;

    @media (min-width: 768px) {
        display: none;
    }
}

/* Visible on desktop only - replaces d-none d-md-inline-block */
.desktop-only {
    display: none;

    @media (min-width: 768px) {
        display: inline-block;
    }
}

/* Block version for desktop */
.desktop-only-block {
    display: none;

    @media (min-width: 768px) {
        display: block;
    }
}

/* Flex on desktop only */
.desktop-only-flex {
    display: none;

    @media (min-width: 768px) {
        display: flex;
    }
}

/* === NAVIGATION UTILITIES === */

/* Mobile navigation icons container - replaces 4+ utility classes */
.mobile-nav {
    display: flex;
    align-items: center;
    gap: 0.5rem;

    @media (min-width: 768px) {
        display: none;
    }
}

/* Icon sizing for nav items - replaces bi fs-5 */
.nav-icon {
    display: inline-block;
    width: 1.25rem;
    height: 1.25rem;
}

/* === ICON UTILITIES === */

/* Base Bootstrap icon sizing */
.bi {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    vertical-align: -0.125em;
    width: 1em;
    height: 1em;
    fill: currentcolor;
}

.bi.fs-5 {
    width: 1.25rem;
    height: 1.25rem;
}

/* === ORIGINATOR ADD FLOW === */
.originator-add-flow .data-card {
    --bs-card-bg: #ffffff;
    background-color: var(--bs-card-bg);
}

.originator-add-flow .originator-result-list {
    --bs-card-bg: #ffffff;
    --bs-list-group-bg: var(--bs-card-bg);
    --bs-list-group-action-hover-bg: color-mix(in srgb, var(--bs-card-bg) 92%, var(--bs-primary) 8%);
}

.originator-add-flow .originator-result-list .originator-result-item:hover,
.originator-add-flow .originator-result-list .originator-result-item:focus,
.originator-add-flow .originator-result-list .originator-result-item:focus-within {
    background-color: var(--bs-list-group-action-hover-bg);
}

/* Collect icon: fixed dimensions prevent CLS on font load */
.collect-icon {
    width: var(--qs-collect-icon-size, 1.25rem);
    height: var(--qs-collect-icon-size, 1.25rem);
}

/* Icon alignment with wrapping inline text. vertical-align is ignored for
 * flex items — source-row glyphs use --source-row-glyph-nudge in
 * quote-card.css instead. -0.2em is for remaining inline BI uses. */
.icon-align {
    flex-shrink: 0;
    vertical-align: -0.2em;
}

/* Favicon sized to sit like a bootstrap-icons glyph on a text line */
.favicon-inline {
    width: 1em;
    height: 1em;
    object-fit: contain;
    vertical-align: -0.125em;
}

/* === HTMX UTILITIES === */

/* HTMX loading indicator base */
.htmx-indicator {
    display: none;
}

.htmx-request .htmx-indicator {
    display: block;
}

.htmx-request.htmx-indicator {
    display: block;
}

/* Submit button loading state: show spinner, hide normal text */
.submit-with-loading.htmx-request .submit-btn-text,
#submit-quote-btn.htmx-request .submit-btn-text,
#submit-originator-btn.htmx-request .submit-btn-text {
    display: none;
}

.submit-with-loading.htmx-request .htmx-indicator,
#submit-quote-btn.htmx-request .htmx-indicator,
#submit-originator-btn.htmx-request .htmx-indicator {
    display: inline-flex;
    align-items: center;
}


/* Loading indicator - hidden by default, shown during HTMX requests */
#loading-indicator {
    display: none;
}

.htmx-request #loading-indicator {
    display: block;
}

/* === TOOLTIP STYLING === */
.tooltip {
    --bs-tooltip-bg: var(--bs-secondary);
    --bs-tooltip-color: var(--bs-white);
    --bs-tooltip-opacity: 1;
    --bs-tooltip-arrow-height: 0.5rem;
}

/* === PAGINATION === */
.pagination {
    --bs-pagination-padding-x: 1rem;
    --bs-pagination-padding-y: 0.5rem;
    --bs-pagination-color: var(--bs-primary);
    --bs-pagination-bg: var(--bs-white);
    --bs-pagination-border-color: var(--bs-border-color);
    --bs-pagination-hover-color: var(--bs-primary);
    --bs-pagination-hover-bg: rgba(var(--bs-primary-rgb), 0.1);
    --bs-pagination-focus-color: var(--bs-primary);
    --bs-pagination-active-bg: var(--bs-primary);
    --bs-pagination-active-border-color: var(--bs-primary);
    --bs-pagination-disabled-color: var(--bs-gray-400);
    --bs-pagination-disabled-bg: var(--bs-white);
    --bs-pagination-disabled-border-color: var(--bs-border-color);
    font-size: 0.875rem;
}

/* Structural mirror of deferred Bootstrap. The block above sets the pagination
   custom properties, but the rules that make it a pagination control at all --
   flex row, no list bullets, padded bordered links -- are Bootstrap's, and
   Bootstrap is preload+onload. Without these the nav paints as a default
   bulleted <ul>: raw link text stacked vertically under the cards, 105px tall,
   snapping to a 42px row when Bootstrap lands.
   Values read through Bootstrap's own properties so the block above stays
   authoritative; see the .btn note below for why they are never hard-coded. */
.pagination {
    display: flex;
    padding-left: 0;
    list-style: none;
}

.page-link {
    position: relative;
    display: block;
    padding: var(--bs-pagination-padding-y, 0.375rem) var(--bs-pagination-padding-x, 0.75rem);
    font-size: var(--bs-pagination-font-size, 1rem);
    color: var(--bs-pagination-color);
    text-decoration: none;
    background-color: var(--bs-pagination-bg);
    border: var(--bs-pagination-border-width, 1px) solid var(--bs-pagination-border-color);
    transition: all 0.2s ease-in-out;
    min-width: 2.5rem;
    text-align: center;
}

.page-item:not(:first-child) .page-link {
    margin-left: calc(var(--bs-border-width, 1px) * -1);
}

.justify-content-center {
    justify-content: center !important;
}

.my-4 {
    margin-top: 1.5rem !important;
    margin-bottom: 1.5rem !important;
}

@media (min-width: 576px) {
    .d-sm-inline { display: inline !important; }
}

.page-item.active .page-link {
    font-weight: 500;
}

/* === FORM UTILITIES === */
.form-control,
.form-select {
    background-color: var(--bs-white);
    border-color: var(--bs-border-color);

    &:focus {
        background-color: var(--bs-white);
    }
}

.form-control-lg {
    &::placeholder {
        color: var(--bs-gray-400);
    }

    &:focus {
        border-color: var(--bs-primary);
        box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb), 0.25);
    }
}

/* === SEARCH UTILITIES === */
.search-container,
.search-originators-container {
    position: relative;
}

.search-container .htmx-indicator,
.search-originators-container .htmx-indicator {
    right: 36px !important;
}

.search-container {
    width: 300px;
}

.search-originators-container {
    width: 100%;
}

@media (min-width: 768px) {
    .search-originators-container {
        width: 400px;
    }
}

@media (min-width: 992px) {
    .search-originators-container {
        width: 500px;
    }
}

.search-results-dropdown {
    position: absolute;
    width: 100%;
    top: calc(100% + 0.25rem);
    left: 0;
    background: white;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
    overflow-y: auto;
    z-index: var(--z-index-dropdown);
    max-height: 400px;
}

.search-result-item {
    transition: background-color 0.2s ease;

    &:hover,
    &:focus {
        background-color: var(--bs-light);
    }
}

#originator-search-results,
#tag-search-results {
    z-index: var(--z-index-dropdown);
}

/* Autocomplete rows (originator, tag, drawer tag suggestions).
   The quote-count pill is pinned to the right of the name line only, so every
   pill's right edge shares one column. The description is the next line and
   runs the full text width, under the pill. A portrait is a left thumb and
   does not move that column. */
.search-ac-row {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    min-width: 0;
}

.search-ac-body {
    flex: 1 1 auto;
    min-width: 0;
}

.search-ac-title {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 0.5rem;
    width: 100%;
    margin: 0;
    line-height: 1.35;
    text-align: start;
}

.search-ac-name {
    min-width: 0;
    overflow: hidden;
    font-weight: 500;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.search-ac-qualifier {
    color: var(--bs-secondary-color, #6c757d);
    font-weight: 400;
}

/* Dark rounded pill. Mirrored here because Bootstrap is deferred; the
   badge/bg-secondary/rounded-pill classes match once it lands. */
.search-ac-count {
    justify-self: end;
    padding: 0.35em 0.65em;
    border-radius: 50rem;
    background-color: var(--bs-secondary, #2f4f4f);
    color: #fff;
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
}

.search-ac-count--zero {
    color: #fff;
}

.search-ac-description {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    margin: 0.125rem 0 0;
    color: var(--bs-secondary-color, #6c757d);
    font-size: 0.875rem;
    line-height: 1.35;
}

.search-ac-action {
    display: inline-block;
    margin-top: 0.25rem;
    color: var(--bs-primary, #8b4513);
    font-size: 0.875rem;
    font-weight: 600;
}

/* Search-row thumb: published 192 thumb, shown small. No reserved hole when absent.
   The frame stays 40px even when the file fails, so alt text cannot spill into the name. */
.originator-search-portrait-frame {
    flex: 0 0 40px;
    width: 40px;
    height: 40px;
    overflow: hidden;
}

.originator-search-portrait {
    display: block;
    width: 40px;
    height: 40px;
    object-fit: contain;
}


/* === PROGRESS INDICATOR === */
.similar-quotes-progress {
    position: absolute;
    right: 10px;
    top: 50%;
    transform: translateY(-50%);
    display: none;
    z-index: var(--z-index-progress);
}

/* === ALPHABET BUTTON === */
.alphabet-btn {
    font-size: 0.85rem;
    padding: 0.15rem 0.4rem;
    min-width: 1.75rem;
    text-align: center;
    line-height: 1.2;
    height: auto;
}

.d-flex.flex-wrap.gap-2 {
    gap: 0.25rem !important;
}

/* === QUICKSTART === */
.quickstart-rate-limits {
    max-width: 300px;
}

/* === BOOTSTRAP UTILITY MIRRORS (deferred-Bootstrap) ===
   Bootstrap is preload+onload while this bundle is sync, so a .btn has no padding
   until it lands (24px instead of 38px) and the reboot margins .mb-0 assumes are
   absent — both move layout on arrival.

   Every value is read through Bootstrap's own custom property with the default as
   the fallback, NEVER hard-coded. This bundle loads AFTER Bootstrap, so a literal
   `padding: 0.375rem 0.75rem` here would outrank .btn-sm/.btn-lg — which only
   reassign the variables — and silently break every sized button on the site (91
   templates use them). The var() form gives the right first paint and then steps
   aside. */
.btn {
    display: inline-block;
    padding: var(--bs-btn-padding-y, 0.375rem) var(--bs-btn-padding-x, 0.75rem);
    font-size: var(--bs-btn-font-size, 1rem);
    font-weight: var(--bs-btn-font-weight, 400);
    line-height: var(--bs-btn-line-height, 1.5);
    text-align: center;
    vertical-align: middle;
    border: var(--bs-btn-border-width, 1px) solid var(--bs-btn-border-color, transparent);
    border-radius: var(--bs-btn-border-radius, 0.375rem);
}

.mt-0 { margin-top: 0 !important; }
.mb-0 { margin-bottom: 0 !important; }
.mb-2 { margin-bottom: 0.5rem !important; }
.py-4 { padding-top: 1.5rem !important; padding-bottom: 1.5rem !important; }
.fw-bold { font-weight: 700 !important; }
.small { font-size: 0.875em; }
.list-unstyled { padding-left: 0; list-style: none; }

/* === link-patterns.css === */
/**
 * Link Styling Patterns
 *
 * Philosophy: Global text-decoration:none (base.css) correct for standalone
 * links (nav, footer, cards). Add underlines for inline prose links where
 * context doesn't signal clickability.
 *
 * Research basis:
 * - Nielsen Norman Group: "To maximize the perceived affordance of clickability,
 *   color and underline the link text"
 * - Material UI: Inline links underlined always, standalone links underlined on hover
 * - Smart Interface Design Patterns: "An underline is the clearest indicator of a link"
 *
 * Last updated: 2026-01-08
 */

/* ===== INLINE PROSE LINKS ===== */
/* Always underlined - embedded in flowing text where context doesn't signal clickability */

/* Pricing page - FR-055, FR-056, FR-058 */
/* Brown (primary brand color) + underline for clear affordance */
.pricing-page .card-body p a:not(.btn),
.pricing-page .card-body li a:not(.btn),
.pricing-page .accordion-body a:not(.btn),
.pricing-page .docs-section p a:not(.btn),
.pricing-page .text-muted a:not(.btn) {
    color: var(--bs-primary) !important;
    text-decoration: underline;
    text-decoration-thickness: 1px;
}

/* Developer portal - all documentation and prose */
/* Brown (primary brand color) + underline for clear affordance */
.developer-portal p a:not(.btn),
.developer-portal .text-muted a:not(.btn),
.developer-portal li a:not(.btn) {
    color: var(--bs-primary) !important;
    text-decoration: underline;
    text-decoration-thickness: 1px;
}


/* ===== STANDALONE LINKS ===== */
/* No underline by default (handled by base.css global rule) */
/* Optional: hover underline for footer/utility navigation */

footer a:hover {
    text-decoration: underline;
}

/* ===== ACCESSIBILITY ===== */
/* Ensure focus states remain visible for keyboard navigation */
a:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: 2px;
}

/* Visited link state for documentation/long-form content */
/* Slight opacity reduction to indicate visited, but keep base color */
.pricing-page a:visited:not(.btn),
.developer-portal a:visited:not(.btn) {
    opacity: 0.85;
}

/* === quote-card.css === */
/* components/quote-card.css
 * ============================================
 * Quote card component with modern CSS nesting.
 * Uses :has() for parent-state styling and container queries
 * for component-responsive layouts.
 *
 * Browser support: Chrome 112+, Firefox 117+, Safari 16.4+
 * Graceful degradation: Rules still apply in older browsers
 *
 * Last updated: 2026-01-16
 * See: docs/delivery/74-bootstrap-vanilla-css/74-4.md
 */

/* === COLLECTION ICON COLORS === */
:root {
    /* Card outline (existing gold) */
    --qs-collect-card-outline: #D4AF37;

    /* Bookmark fill colors */
    --qs-collect-fill: #654321;           /* Library Brown - collected state */
    --qs-collect-fill-hover: #7B5229;     /* 15% lighter for hover */
    --qs-collect-outline: #6c757d;        /* Neutral gray - uncollected */

    /* Icon size (single source of truth) */
    --qs-collect-icon-size: 1.25rem;      /* 20px at default font size */
}

/* === QUOTE CARD BASE === */
.quote-card {
    background-color: var(--bs-card-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    position: relative;
    margin-bottom: 0.5rem;
    overflow: visible !important;
    z-index: auto !important;
    /* Note: margin/padding transitions removed to prevent CLS on load.
     * The .removing class has its own transitions for exit animation. */
    transition: opacity 0.5s ease-in-out,
                box-shadow 0.3s ease;

    /* Subtle hover effect without transform (prevents stacking issues) */
    &:hover {
        transform: none !important;
        box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1);
    }

    /* Collected state - gold highlight */
    &.quote-card-collected {
        background-color: rgba(var(--bs-warning-rgb), 0.05);
        border: 2px solid var(--bs-warning);
        box-shadow: 0 0 0 1px var(--bs-warning);
    }

    /* Card body padding */
    & .card-body {
        padding: 1rem;
    }

    /* Horizontal rule */
    & hr {
        margin-top: 0.5rem;
        margin-bottom: 0.5rem;
    }

    /* Dropdown positioning */
    & .dropdown-menu {
        z-index: 1055;
    }

    /* Open split-button menus (collect picker, copy format) must paint above
     * the next quote card. qw-reveal animates transform/opacity with
     * fill-mode both, and the filled identity transform still forms a
     * stacking context, so a later card covers this card's menu even at
     * z-index 1055. Lift only the card whose menu is open: above sibling
     * cards (--z-index-card), below sticky chrome. */
    &:has(.dropdown-menu.show) {
        z-index: calc(var(--z-index-card) + 1) !important;
    }

    /* Override Bootstrap mb-3 (16px) with 8px spacing.
     * Template uses .quote-card.mb-3 - this counters Bootstrap's !important. */
    &.mb-3 {
        margin-bottom: 0.5rem !important;
    }
}

/* === QUOTE TEXT === */
.quote-text {
    color: var(--bs-body-color);
    font-size: 1.1rem;
    line-height: 1.5;
}

/* Long-quote truncation (044 edge case, >500 chars): clamp to 8 lines with
 * ellipsis on scanning surfaces. Destination pages (originator, source) opt out
 * server-side via clamp_long_text=False and render the quote whole. */
.quote-text-clamp {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 8;
    line-clamp: 8;
    overflow: hidden;
}

/* Expanded by the quote-clamp.ts toggle. !important because surfaces clamp the
 * same span at their own specificity — #recent-snippet-wrapper at 6 lines,
 * [data-suggestion-slot] at 4 — and a specificity contest here means a toggle
 * that flips aria-expanded while the text stays visibly clamped. Expanded means
 * expanded, whichever clamp put the body in the box. */
.quote-text-body.is-expanded {
    display: block !important;
    -webkit-line-clamp: unset !important;
    line-clamp: unset !important;
    overflow: visible !important;
}

/* Show more / less toggle (injected by quote-clamp.ts). Sits on its own line
 * under the clamp box — inline-block so the permalink glyph that follows it
 * shares that line. font-style:normal so it doesn't inherit blockquote italic. */
.quote-clamp-toggle {
    display: inline-block;
    margin-top: 0.35rem;
    padding: 0;
    border: 0;
    background: none;
    font-size: 0.875rem;
    font-style: normal;
    line-height: 1.2;
    color: var(--bs-primary);
    cursor: pointer;
}

.quote-clamp-toggle:hover,
.quote-clamp-toggle:focus-visible {
    text-decoration: underline;
}

/* Permalink glyph: 24px minimum tap target (WCAG 2.5.8 AA floor) without
 * growing the text line box (quote line-height is ~26px). */
.quote-permalink {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 24px;
    min-height: 24px;
    vertical-align: middle;
}

/* === COLLECTION ICON === */
.collect-icon {
    color: var(--qs-collect-outline);
    cursor: pointer;
    transition: color var(--icon-dur) var(--icon-ease), transform var(--icon-dur) var(--icon-ease);
    font-size: var(--qs-collect-icon-size);
    width: var(--qs-collect-icon-size);
    height: var(--qs-collect-icon-size);

    &:hover {
        transform: scale(1.1);
        color: var(--qs-collect-fill-hover);
    }

    &.collected {
        color: var(--qs-collect-fill);

        &:hover {
            transform: scale(1.1);
            color: var(--qs-collect-fill-hover);
        }
    }
}

/* === HTMX ANIMATIONS === */
.quote-card {
    /* Slide-in animation */
    &.htmx-added {
        opacity: 0;
        transform: translateX(10px);
        transition: opacity 0.3s ease, transform 0.3s ease;
    }

    &.htmx-added-settle {
        opacity: 1;
        transform: translateX(0);
    }

    /* Swap animation */
    &.htmx-swapping {
        opacity: 0;
        transform: translateX(10px);
        pointer-events: none;
        transition: opacity 0.3s ease, transform 0.3s ease;
    }

    /* Removal animation */
    &.removing {
        transform: scaleY(0);
        opacity: 0;
        margin-top: 0;
        margin-bottom: 0;
        padding: 0;
        transition: transform 0.5s ease-in-out,
                    opacity 0.5s ease-in-out,
                    margin 0.5s ease-in-out,
                    padding 0.5s ease-in-out;
    }
}

/* === METADATA === */
.metadata-content {
    font-size: 0.9rem;
}

.quote-metadata {
    overflow: hidden;

    & a {
        word-wrap: break-word;
        word-break: break-word;
    }
}

.quote-originator {
    max-width: 100%;
    display: inline-flex;
    font-size: 0.99rem;

    & a {
        line-height: 1.4;
        display: inline-block;
        max-width: 100%;
    }

    &.mb-2 {
        margin-bottom: 0 !important;
    }
}

/* Attribution nested inside the blockquote (a11y): always its own line below the
   quote text — the shared .quote-originator span stays inline-flex in the compact card. */
footer.quote-originator {
    display: block;
}

/* === UTTERANCE DATE RAIL (spec 149 FR-004/006/007) === */

/* The date sits right-aligned on the attribution line while the name+description
   stay ONE flex item, so a long description still wraps under the name instead of
   being split by the date. Baseline alignment puts the date on the name's first
   baseline; wrapping lets the rail drop below the attribution on narrow viewports
   rather than squeezing either side. Gated on :has() — an undated card renders no
   rail element at all, so its footer keeps the block layout it has always had. */
footer.quote-originator:has(.quote-date-rail) {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: 0.75rem;

    & > .quote-attribution {
        flex: 1 1 12rem;
        min-width: 0;
    }
}

/* Cards that suppress the attribution (originator + source detail pages) give the
   rail its own row; the auto start-margin keeps it on the same right edge. */
.quote-date-rail-row {
    display: flex;
}

.quote-date-rail {
    margin-inline-start: auto;
    font-size: 0.875em;
    font-variant-numeric: tabular-nums lining-nums;
    white-space: nowrap;
}

.quote-source-line,
.quote-card-source {
    /* One first-line optical band (card + detail). Cells are 1lh and pin
     * to line 1 via flex-start on the row (Shot B). Title wraps in its
     * own item — do not make title links inline-block (tall 1.4 strut).
     * Tune these two tokens only; do not add per-surface translateY.
     * Type marks ↔ title x-height (0.08em sat one increment low).
     * Outbound arrow origin ↔ · (-0.20em sat one increment high). */
    --source-row-glyph-nudge: 0.02em;
    --source-row-outbound-nudge: -0.12em;
    /* YT favicon ink sits low in the ico vs camera / play / title.
     * One increment above the type-mark token; do not reuse on WQ. */
    --source-row-youtube-nudge: calc(var(--source-row-glyph-nudge) - 0.08em);
    max-width: 100%;
    line-height: 1.4;
    display: inline-flex;
    align-items: flex-start;
}

.quote-source-line {
    /* <cite> is italic in the UA stylesheet; cards are roman. Do not put
       font-style here on .quote-card-source — the anonymous source CTA
       reuses that class (source_cta.html) and a shared roman reset
       shifted the card-feed placeholder. */
    font-style: normal;
}

.quote-source-line > a,
.quote-card-source > a {
    min-width: 0;
    flex: 0 1 auto;
    max-width: 100%;
}

.quote-card-source-outbound {
    display: inline-flex;
    align-items: center;
    align-self: flex-start;
    flex: 0 0 auto;
    /* Inherit the row's 1.4 so 1lh == the title first-line box. line-height:1
     * here collapses 1lh to 1em and the cluster cannot share the title band. */
    line-height: inherit;
    height: 1lh;
    white-space: nowrap;

    /* · + outbound (BI or the word Article) are one 1lh-centered cluster. */
    & > a {
        display: inline-flex;
        align-items: center;
        line-height: inherit;
    }

    & .bi {
        line-height: 1;
        transform: translateY(var(--source-row-glyph-nudge));
    }

    /* Geometric bbox mid of box-arrow is above the shaft origin. Type
     * marks keep --source-row-glyph-nudge; this glyph uses its own lift. */
    & .bi-box-arrow-up-right {
        transform: translateY(var(--source-row-outbound-nudge));
    }
}

.quote-source-container {
    display: block;
    width: 100%;
    margin-top: 0.25rem;
}

/* === TAGS === */
.quote-tags .badge {
    transition: background-color 0.2s ease;

    &:hover {
        background-color: var(--bs-gray-200) !important;
    }
}

.quote-tags-and-language {
    flex: 1 1 0;
    max-width: calc(100% - 360px);
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    margin-top: 0.5rem;
    margin-bottom: 0.125rem;

    & .badge {
        margin-bottom: 0.125rem;
        margin-top: 0.125rem;
        margin-right: 0.25rem;
    }

}

/* === ACTIONS === */
.quote-actions {
    margin-left: auto;
    margin-top: 0.25rem;
    white-space: nowrap;
}

.quote-card .quote-footer:not(:has(
    .quote-personal-tags,
    .quote-attribution-status,
    .quote-source-container,
    .quote-actions,
    .quote-tags-row,
    .quote-admin-actions
)) {
    display: none;
}

/* Copy button: flex for icon centering, match star icon height */
.copy-quote-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 1.5rem;
}

/* Copy dropdown (T021): Format selection dropdown
 * Must align vertically with share button and collection star.
 * Margin via me-3 class in template (desktop only).
 */
.copy-quote-dropdown {
    align-self: center;
}

/* Reset copy-quote-btn fixed height when in dropdown */
.btn-group.copy-quote-dropdown .btn.copy-quote-btn {
    height: auto;
}

/* Tight spacing between clipboard icon and dropdown caret */
.copy-quote-dropdown .copy-quote-btn {
    padding: 0.25rem;
    padding-right: 0.0625rem;  /* 1px gap to caret */
}
.copy-quote-dropdown .dropdown-toggle-split {
    padding: 0.25rem;
    padding-left: 0.0625rem;   /* 1px gap from icon */
    border-left: 0;
}

.collect-picker-dropdown {
    align-self: center;
}

.collect-picker-dropdown .collect-icon {
    border: 1px solid var(--bs-border-color, #dee2e6) !important;
    border-radius: 6px 0 0 6px;
    border-right: 0 !important;
    flex: 0 0 auto;
}

/* Drop inner divider so bookmark/caret pair reads as one button (mirrors copy-quote-dropdown). */
.collect-picker-dropdown .dropdown-toggle-split {
    border: 1px solid var(--bs-border-color, #dee2e6) !important;
    border-radius: 0 6px 6px 0;
    border-left: 0 !important;
    flex: 0 0 auto;
    min-width: 30px;
}

.collect-picker-menu {
    min-width: 16rem;
    max-height: min(20rem, 70vh);
    overflow-y: auto;
}

.collect-picker-row {
    min-height: 2.5rem;
}

.collect-picker-management-row {
    min-width: 16rem;
}

.collect-picker-action.collect-picker-row {
    min-height: 2rem;
}

.collect-picker-row:disabled,
.collect-picker-row[aria-disabled="true"] {
    opacity: 0.8;
    cursor: default;
}

.collect-picker-row-member {
    background-color: color-mix(in srgb, var(--bs-success, #198754) 8%, transparent);
}

.collect-picker-row-member .bi-check-circle-fill {
    color: var(--bs-success, #198754);
}

/* Harmonize outline buttons in the quote-actions row (Find Similar, Full Quote,
 * admin workflow dropdown) with the copy/share/collect aesthetic — softer
 * --bs-border-color outline, transparent fill, primary-blue hover. */
.quote-actions .btn-outline-secondary,
.quote-actions .btn-group > .btn-outline-secondary.dropdown-toggle {
    background: transparent;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: 6px;
    color: var(--bs-secondary, #6c757d);
    transition: all 0.15s ease;
}

.quote-actions .btn-outline-secondary:hover,
.quote-actions .btn-outline-secondary:focus-visible,
.quote-actions .btn-outline-secondary.show,
.quote-actions .btn-group.show > .btn-outline-secondary.dropdown-toggle {
    background: var(--bs-light, #f8f9fa);
    border-color: var(--bs-primary, #0d6efd);
    /* !important needed to beat the global `a.btn-outline-secondary:hover {
     * color: #fff !important }` in colors.css. Without it, the white text wins
     * while this rule keeps the light fill (higher specificity) → invisible label. */
    color: var(--bs-primary, #0d6efd) !important;
}

/* Regular desktop + mobile: hide top-right buttons, show in bottom row
 * Only huge desktops (≥1400px) get split layout with top-right buttons
 */
@media (max-width: 1399.98px) {
    /* Hide top-right collect/copy/share area */
    .quote-card-collect {
        display: none !important;
    }

    /* Mobile action buttons join the bottom quote-actions flex */
    .mobile-action-buttons {
        display: contents !important;
    }

    /* Mobile bounded button style (outline buttons) */
    .mobile-action-btn {
        background: transparent;
        border: 1px solid var(--bs-border-color, #dee2e6);
        border-radius: 6px;
        padding: 6px 10px;
        color: var(--bs-secondary, #6c757d);
        cursor: pointer;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 38px;
        min-height: 38px;
        transition: all 0.15s ease;
    }

    .mobile-action-btn:hover {
        background: var(--bs-light, #f8f9fa);
        border-color: var(--bs-secondary, #6c757d);
    }

    /* Mobile copy dropdown split button */
    .mobile-copy-dropdown,
    .mobile-collect-picker-dropdown {
        display: inline-flex;
    }

    .mobile-copy-dropdown .copy-quote-btn,
    .mobile-collect-picker-dropdown .collect-icon {
        border-radius: 6px 0 0 6px;
        border-right: 0;
    }

    .mobile-copy-dropdown .dropdown-toggle-split,
    .mobile-collect-picker-dropdown .dropdown-toggle-split {
        border-radius: 0 6px 6px 0;
        padding: 6px 6px;
        min-width: auto;
    }

    /* Copy button hover - primary blue */
    .mobile-action-btn.copy-quote-btn:hover,
    .mobile-copy-dropdown:hover .copy-quote-btn,
    .mobile-copy-dropdown:hover .dropdown-toggle-split {
        border-color: var(--bs-primary, #0d6efd);
        color: var(--bs-primary, #0d6efd);
    }

    /* Share button hover - primary blue */
    .mobile-action-btn.share-quote-btn:hover {
        border-color: var(--bs-primary, #0d6efd);
        color: var(--bs-primary, #0d6efd);
    }

    /* Collect button - library brown */
    .mobile-action-btn.collect-icon {
        color: var(--qs-collect-outline, #6c757d);
    }

    .mobile-action-btn.collect-icon:hover,
    .mobile-collect-picker-dropdown:hover .collect-icon,
    .mobile-collect-picker-dropdown:hover .dropdown-toggle-split {
        border-color: var(--qs-collect-fill-hover, #7B5229);
        color: var(--qs-collect-fill-hover, #7B5229);
    }

    .mobile-action-btn.collect-icon.collected {
        border-color: var(--qs-collect-fill, #654321);
        color: var(--qs-collect-fill, #654321);
    }

    /* Report and Find Similar - icon buttons match the other quote actions */
    .quote-detail-collect .report-menu > .btn,
    .quote-actions .report-menu > .btn,
    .quote-actions .similar-quotes-link {
        padding: 6px 10px;
        min-width: 38px;
        min-height: 38px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    .quote-actions .similar-quotes-link .find-similar-text {
        display: none;
    }

    .quote-actions .similar-quotes-link .icon-search-heart {
        margin: 0;
        font-size: 1rem;
    }

    /* Full Quote - icon only, match other button sizes */
    .quote-actions .extended-version-link.btn {
        padding: 6px 10px !important;
        width: 38px !important;
        min-width: 38px !important;
        max-width: 38px !important;
        min-height: 38px;
        display: inline-flex !important;
        align-items: center;
        justify-content: center;
        box-sizing: border-box !important;
        font-size: 0 !important; /* Collapse whitespace text node */
        margin-right: 0 !important; /* Reset Bootstrap me-1, use gap instead */
    }

    .quote-actions .extended-version-link i {
        margin: 0 !important;
        font-size: 1rem !important; /* Restore icon size */
    }

    .quote-actions .extended-version-link small {
        display: none !important;
    }

    /* quote-actions layout for regular desktop + mobile */
    .quote-actions {
        display: flex;
        justify-content: flex-end;
        align-items: center;
        gap: 0.5rem;
    }
}

/* Find Similar keeps its text label wherever there's room: only true phone
 * widths (<768px) drop to icon-only. ≥1400 already shows it; this covers
 * the 768–1399 gap where the icon-only search-heart glyph is cryptic. */
@media (min-width: 768px) and (max-width: 1399.98px) {
    .quote-actions .similar-quotes-link .find-similar-text {
        display: inline;
    }

    .quote-actions .similar-quotes-link .icon-search-heart {
        margin-right: 0.25rem;
    }

    /* Full Quote gets its label back too (same rationale; !important to
     * override the icon-only collapse rules in the ≤1399.98px block). */
    .quote-actions .extended-version-link.btn {
        width: auto !important;
        max-width: none !important;
        font-size: 0.875rem !important;
    }

    .quote-actions .extended-version-link small {
        display: inline !important;
    }

    .quote-actions .extended-version-link i {
        margin-right: 0.25rem !important;
    }
}

/* === HUGE DESKTOP (≥1400px) ===
 * Split layout: copy/share/collect in top-right, Find Similar in bottom
 * Styled with outline borders to match mobile aesthetic
 */
@media (min-width: 1400px) {
    /* Show top-right buttons on huge desktops */
    .quote-card-collect {
        display: flex !important;
        align-items: center;
        gap: 0.5rem;
    }

    /* Hide mobile action buttons in bottom row */
    .mobile-action-buttons {
        display: none !important;
    }

    /* Style top-right buttons with outline aesthetic */
    .quote-card-collect .copy-quote-dropdown .btn,
    .quote-card-collect .collect-picker-dropdown .btn,
    .quote-card-collect .share-quote-btn,
    .quote-card-collect .collect-icon {
        background: transparent;
        border: 1px solid var(--bs-border-color, #dee2e6) !important;
        border-radius: 6px;
        padding: 6px 10px !important;
        min-width: 38px;
        min-height: 38px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        transition: all 0.15s ease;
        text-decoration: none !important;
    }

    .quote-card-collect .copy-quote-dropdown .btn:hover,
    .quote-card-collect .share-quote-btn:hover {
        background: var(--bs-light, #f8f9fa);
        border-color: var(--bs-primary, #0d6efd);
        color: var(--bs-primary, #0d6efd);
    }

    /* Copy dropdown special handling */
    .quote-card-collect .copy-quote-dropdown,
    .quote-card-collect .collect-picker-dropdown {
        display: inline-flex;
    }

    .quote-card-collect .copy-quote-dropdown .copy-quote-btn,
    .quote-card-collect .collect-picker-dropdown .collect-icon {
        border-radius: 6px 0 0 6px;
        border-right: 0;
    }

    .quote-card-collect .copy-quote-dropdown .dropdown-toggle-split,
    .quote-card-collect .collect-picker-dropdown .dropdown-toggle-split {
        border-radius: 0 6px 6px 0;
        padding: 6px 6px;
        min-width: auto;
    }

    /* Collect icon hover */
    .quote-card-collect .collect-icon:hover,
    .quote-card-collect .collect-picker-dropdown:hover .collect-icon,
    .quote-card-collect .collect-picker-dropdown:hover .dropdown-toggle-split {
        border-color: var(--qs-collect-fill-hover, #7B5229);
    }

    .quote-card-collect .collect-icon.collected {
        border-color: var(--qs-collect-fill, #654321);
    }

    /* Reset Bootstrap margins - use gap instead */
    .quote-card-collect .me-1,
    .quote-card-collect .me-2 {
        margin-right: 0 !important;
    }

}

/* With the optional action cluster hidden, this is the only copy control. */
.quote-card-collect.quote-card-copy-only {
    display: flex !important;
}

/* === COLLECT = PRIMARY ACTION ===
 * Appendix A strategic priority (collect > copy > copy-link > permalink):
 * the collect control carries the library-brown identity at rest so it reads
 * as the card's primary action; copy/share stay neutral. Collected vs
 * uncollected still differ by icon fill + gold card border (044 FR-004/005).
 * Placed after both breakpoint blocks; border needs !important to override
 * their `border: ... !important` shorthands. */
.mobile-action-btn.collect-icon,
.mobile-collect-picker-dropdown .dropdown-toggle-split,
.quote-card-collect .collect-icon,
.quote-card-collect .collect-picker-dropdown .dropdown-toggle-split,
.quote-detail-collect .collect-icon,
.quote-detail-collect .collect-picker-dropdown .dropdown-toggle-split {
    border-color: var(--qs-collect-fill, #654321) !important;
    color: var(--qs-collect-fill, #654321);
    background: color-mix(in srgb, var(--qs-collect-fill, #654321) 5%, transparent);
}

/* Split-button carets (copy-format, collect-picker): 24px minimum width
 * (WCAG 2.5.8 AA floor — they measured 21–22px). Height already 38px.
 * Overrides the per-breakpoint `min-width: auto` rules above. */
.mobile-copy-dropdown .dropdown-toggle-split,
.mobile-collect-picker-dropdown .dropdown-toggle-split,
.quote-card-collect .copy-quote-dropdown .dropdown-toggle-split,
.quote-card-collect .collect-picker-dropdown .dropdown-toggle-split,
.quote-detail-collect .copy-quote-dropdown .dropdown-toggle-split,
.quote-detail-collect .collect-picker-dropdown .dropdown-toggle-split {
    min-width: 24px;
}

/* Anonymous source CTA: dimmed placeholder treatment — greyed, subtle
 * opacity, no lock icon (090 FR-013, 044 US5.3). Keeps the collect control
 * the most prominent anonymous action (FR-G-004). Opacity chosen to hold
 * small-text contrast above 4.5:1 on white. */
.source-cta .source-cta-preview {
    opacity: 0.8;
}

.source-cta .source-cta-icons .bi {
    line-height: 1;
}

.source-cta .source-cta-link {
    color: var(--bs-secondary, #6c757d);
}

/* Sparse-teaser conversion line: own row under Find Similar / copy / collect. */
.quote-footer > .source-teaser-conversion {
    margin-top: 0.5rem;
    text-align: left;
}

/* === QUOTE DETAIL PAGE STYLES === */

/* One actions row: external shares opposite the in-site cluster (card chrome). */
.quote-detail-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
}

.quote-detail-external {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.quote-detail-collect {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-left: auto;
}

/* Social share buttons styling (always visible) */
.social-share-btn {
    background: transparent;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: 6px;
    padding: 6px 10px;
    color: var(--bs-secondary, #6c757d);
    min-width: 38px;
    min-height: 38px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: all 0.15s ease;
    text-decoration: none;
}

.social-share-btn:hover {
    background: var(--bs-light, #f8f9fa);
    border-color: var(--bs-primary, #0d6efd);
    color: var(--bs-primary, #0d6efd);
}

/* Match quote-card-collect chrome at every breakpoint (no dual-row split). */
.quote-detail-collect .copy-quote-dropdown .btn,
.quote-detail-collect .collect-picker-dropdown .btn,
.quote-detail-collect .report-menu > .btn,
.quote-detail-collect .share-quote-btn,
.quote-detail-collect .collect-icon {
    background: transparent;
    border: 1px solid var(--bs-border-color, #dee2e6) !important;
    border-radius: 6px;
    padding: 6px 10px !important;
    min-width: 38px;
    min-height: 38px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: all 0.15s ease;
    text-decoration: none !important;
}

.quote-detail-collect .copy-quote-dropdown .btn:hover,
.quote-detail-collect .report-menu > .btn:hover,
.quote-detail-collect .share-quote-btn:hover {
    background: var(--bs-light, #f8f9fa);
    border-color: var(--bs-primary, #0d6efd);
    color: var(--bs-primary, #0d6efd);
}

.quote-detail-collect .copy-quote-dropdown,
.quote-detail-collect .collect-picker-dropdown {
    display: inline-flex;
}

.quote-detail-collect .copy-quote-dropdown .copy-quote-btn,
.quote-detail-collect .collect-picker-dropdown .collect-icon {
    border-radius: 6px 0 0 6px;
    border-right: 0;
}

.quote-detail-collect .copy-quote-dropdown .dropdown-toggle-split,
.quote-detail-collect .collect-picker-dropdown .dropdown-toggle-split {
    border-radius: 0 6px 6px 0;
    padding: 6px 6px;
    min-width: auto;
}

.quote-detail-collect .collect-icon:hover,
.quote-detail-collect .collect-picker-dropdown:hover .collect-icon,
.quote-detail-collect .collect-picker-dropdown:hover .dropdown-toggle-split {
    border-color: var(--qs-collect-fill-hover, #7B5229);
}

.quote-detail-collect .collect-icon.collected {
    border-color: var(--qs-collect-fill, #654321);
}

.quote-detail-collect .me-1,
.quote-detail-collect .me-2 {
    margin-right: 0 !important;
}

.quote-detail-collect .report-menu > .btn {
    padding: 6px 10px;
    min-width: 38px;
    min-height: 38px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* Quote text wraps beside a floated portrait. Do not add overflow/flex/grid
 * on .quote-detail-prose (that BFC pins the hero). */
.quote-detail-prose {
    min-width: 0;
}

.quote-detail-prose > .quote-text {
    margin: 0;
}

.copy-format-menu {
    min-width: 10rem;
    font-size: 0.875rem;
}

.copy-format-menu .dropdown-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
}

.copy-format-menu .dropdown-item i {
    width: 1rem;
    text-align: center;
    flex-shrink: 0;
}

/* Loading state */
.htmx-request .collection-star {
    opacity: 0.5;
    pointer-events: none;
}

/* === HIGHLIGHT === */
.highlight-match {
    background-color: rgba(var(--bs-warning-rgb), 0.2);
    padding: 0.1em 0;
    border-radius: 0.2em;
}

/* === SEARCH HEART ICON === */
.icon-search-heart {
    font-size: 1.2em;
    line-height: 1;
    vertical-align: middle;
    margin-bottom: 2px;
    display: inline-block;
}

/* === SOURCE ICONS === */
.source-icons {
    display: inline-flex;
    /* Center inside the 1lh cell. Inherit the row's 1.4 so 1lh is the title
     * first-line box; the cell stays flex-start on the row so wraps cannot
     * pull marks mid-block. */
    align-items: center;
    align-self: flex-start;
    gap: 0.25rem;
    flex-shrink: 0;
    line-height: inherit;
    height: 1lh;
    min-height: 1lh;
    overflow: visible;

    & .bi,
    & img {
        line-height: 1;
        /* Same token on BI + WQ so those pairs stay locked to the title
         * x-height. YouTube uses --source-row-youtube-nudge below. */
        transform: translateY(var(--source-row-glyph-nudge));
    }
}

.wikiquote-icon {
    width: 16px;
    height: 16px;
    aspect-ratio: 1;
    opacity: 0.8;

    &:hover {
        opacity: 1;
    }
}

/* The WQ mark is wrapped in an <a>. Keep that link a 1em first-line flex
 * item — stretching it + align-items:center sat the logo mid-block. */
.source-icons > a {
    display: inline-flex;
    align-items: center;
    align-self: center;
    line-height: 1;
    height: 1em;
}

.source-icons > a img {
    display: block;
}

/* Source-line icon system: every mark on a card source line renders at 1em
 * (tracks the line's font size: 14px desktop, 12.8px mobile — identical to
 * the em-sized bi glyphs) and middle-aligned. Kills the 16px Wikiquote
 * outlier and sub-pixel drift from non-square brand SVGs. Scoped to card
 * source lines; other surfaces keep their own sizing. */
.quote-card-source img,
.quote-source-line img,
.source-icons img {
    width: 1em;
    height: 1em;
    object-fit: contain;
    vertical-align: middle;
    margin-top: 0;
    transform: translateY(var(--source-row-glyph-nudge));
}

.quote-card-source .youtube-icon,
.quote-source-line .youtube-icon {
    /* Favicon ink sits ~one increment below camera / play / title. */
    transform: translateY(var(--source-row-youtube-nudge));
}

/* === ADSENSE === */
.adsbygoogle[data-adsbygoogle-status="done"] + .ad-placeholder {
    display: none;
}

.ad-placeholder {
    min-height: 100px;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: #f8f9fa;
}

/* === MOBILE RESPONSIVE === */
@media (max-width: 768px) {
    .quote-tags-and-language {
        max-width: calc(100% - 260px);
    }
}

/* === MOBILE-ONLY STYLES (<576px) ===
 * Smaller fonts/padding for phone screens
 * Button styles inherited from 1399.98px block above
 */
@media (max-width: 576px) {
    .quote-card {
        & .card-body {
            padding: 0.625rem;
        }

        & hr {
            margin-top: 0.375rem;
            margin-bottom: 0.375rem;
        }
    }

    .quote-text {
        font-size: 1rem;
        line-height: 1.4;
    }

    .quote-tags-row {
        display: block !important;
        margin-top: 0.375rem !important;
    }

    .quote-no-tags-row {
        display: block !important;
    }

    .quote-metadata {
        max-width: 100%;
        width: 100%;
        margin-bottom: 0;
        font-size: 0.8rem;
    }

    .quote-originator {
        font-size: 0.88rem;

        &.mb-2 {
            margin-bottom: 0.25rem !important;
        }
    }

    .quote-card-source {
        font-size: 0.8rem;
    }

    .quote-tags-and-language {
        flex: none;
        max-width: 100% !important;
        min-width: 100% !important;
        width: 100% !important;
        margin-bottom: 0;
        display: flex !important;
        flex-wrap: wrap !important;

        & .badge {
            display: inline-block !important;
            width: auto !important;
            margin-bottom: 0.125rem;
            padding: 0.2rem 0.4rem;
            font-size: 0.7rem;
        }
    }

    .quote-actions {
        margin-left: 0 !important;
        margin-top: 0.25rem !important;
        margin-bottom: 0 !important;
        width: 100%;

        & .btn-sm {
            padding: 0.25rem 0.5rem;
            font-size: 0.75rem;

            & small {
                font-size: 0.7rem;
            }
        }
    }

    .quote-admin-actions .btn-sm {
        padding: 0.25rem 0.5rem;
        font-size: 0.75rem;

        & small {
            font-size: 0.7rem;
        }
    }
}

/* === :has() EXAMPLE ===
 * Hide quote card if text is empty (no JS needed)
 * Gracefully degrades: older browsers just show the card
 */
.quote-card:has(.quote-text:empty) {
    display: none;
}

.report-form-card {
    max-width: 50rem;
}

.report-form-layout {
    display: grid;
    gap: 1.5rem;
}

.report-quote-context {
    align-self: start;
    margin-bottom: 0;
    overflow-wrap: anywhere;
    background-color: color-mix(in srgb, var(--bs-secondary) 18%, var(--bs-card-bg));
}

.report-add-item,
.report-open-banner {
    display: none;
}

body.report-group-open .report-add-item,
body:has(#open-banner[data-open]) .report-add-item {
    display: list-item;
}

.report-open-banner[data-open] {
    position: fixed;
    right: 1rem;
    bottom: 1rem;
    z-index: 1080;
    display: flex;
    gap: 1rem;
    align-items: center;
    max-width: calc(100vw - 2rem);
    padding: 0.75rem 1rem;
    color: white;
    background: var(--bs-secondary, #2f4f4f);
    border-radius: 0.5rem;
    box-shadow: 0 0.25rem 1rem rgba(0, 0, 0, 0.2);
}

/* Organize Standard: cluster is last in the card-body DOM so screen readers
   reach the quote first (qw-1zv2g #5c). order:-1 places annotate/pin/cover/select
   and the quote/source-style rule above the quote. Gated on
   .quote-card-body--organize so originator/source/home/search cards stay unchanged. */
.quote-card-body--organize {
    display: grid;
}

.quote-card-organize-actions {
    order: -1;
}

/* === originator-card.css === */
/* Originator browse card (spec 144 US2).
 *
 * The grid geometry below is mirrored from deferred Bootstrap on purpose. Bootstrap
 * CSS is preload+onload (partials/bootstrap_head.html) while the core bundle is
 * sync, so anything the grid needs at first paint has to be committed here -- else
 * the page paints as a full-width stack and snaps into columns when Bootstrap
 * lands, a multi-thousand-pixel jump on every paginated navigation.
 * `.browse-feed.row` in layout.css does the same thing for the same reason.
 *
 * Mirrors: .row + .row-cols-1 .row-cols-sm-2 .row-cols-md-3 .row-cols-lg-4
 * .row-cols-xl-5 + .g-2 + .mt-0, plus the .card-body padding. Keep in step with the
 * classes on the grid element; test_originator_grid_stability.py is the guard.
 */

.originator-card-grid.row {
  --bs-gutter-x: 0.5rem;
  --bs-gutter-y: 0.5rem;
  display: flex;
  flex-wrap: wrap;
  /* .mt-0 on the element wins over .row's negative margin in the settled layout. */
  margin-top: 0;
  margin-right: calc(-0.5 * var(--bs-gutter-x));
  margin-left: calc(-0.5 * var(--bs-gutter-x));
}

.originator-card-grid.row > * {
  flex: 0 0 auto;
  width: 100%;
  max-width: 100%;
  padding-right: calc(var(--bs-gutter-x) * 0.5);
  padding-left: calc(var(--bs-gutter-x) * 0.5);
  margin-top: var(--bs-gutter-y);
}

@media (min-width: 576px) {
  .originator-card-grid.row > * {
    width: 50%;
  }
}

@media (min-width: 768px) {
  .originator-card-grid.row > * {
    width: 33.3333%;
  }
}

@media (min-width: 992px) {
  .originator-card-grid.row > * {
    width: 25%;
  }
}

@media (min-width: 1200px) {
  .originator-card-grid.row > * {
    width: 20%;
  }
}

/* Bootstrap's --bs-card-spacer-*; without it the body has no padding until
   Bootstrap lands, which is a per-row height shift on top of the column snap. */
.originator-card-grid .card-body {
  padding: 1rem;
}

/* .h-100 on the card: commits per-row height equalisation, which matters as soon
   as a row mixes portrait-bearing and text-only cards. The border is Bootstrap's
   and is worth 2px per row — the grid's whole residual shift without it. */
.originator-card-grid .card {
  height: 100%;
  border: 1px solid var(--bs-card-border-color, var(--bs-border-color));
  border-radius: 0.375rem;
}

/* The card's own type. Left to browser defaults the h2 renders at 1.5em with
   0.83em margins and the p at 1em margins, so each card is ~16px taller than its
   settled height -- a per-row shift that survives fixing the columns.
   Mirrors .h6 + .mb-2 on the title and .small + .mb-0 on the count. */
.originator-card-grid .card-title {
  margin: 0 0 0.5rem;
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.2;
}

.originator-card-grid .card-text {
  margin: 0;
  font-size: 0.875em;
}

/* Wikidata blurbs can be a sentence or two. Clamp so a long description
   cannot stretch the card past the portrait row. */
.originator-card-grid .originator-card-description {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  margin: 0 0 0.25rem;
  font-size: 0.875em;
}

.originator-card-body {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.originator-card-media {
  flex: 0 0 calc(50% - 0.375rem);
  height: 88px;
  min-width: 0;
}

.originator-card-portrait {
  display: block;
  width: 100%;
  height: 100%;
  /* Engravings, not photographs: cropping would cut the subject's head (FR-016). */
  object-fit: contain;
}

.originator-card-text {
  flex: 1 1 calc(50% - 0.375rem);
  min-width: 0;
}

/* FR-017: a card without a portrait spans the full card rather than reserving
   space for one, so it must not carry a portrait-sized minimum height either. */
.originator-card-text--full {
  flex-basis: 100%;
}

.btn.originator-portrait-filter:focus-visible {
  outline: 3px solid var(--bs-primary);
  outline-offset: 2px;
}

/* === sticky-search.css === */
/**
 * Sticky Search: Compact bottom-sticky search bar
 * Feature: 038-mobile-style-refresh (US1)
 *
 * Appears when hero search scrolls out of view.
 * Positioned above mini-console (60px from viewport bottom).
 * Mobile only (<768px).
 *
 * Z-index: 1020 (--z-index-sticky) - below fixed elements like navbar
 */

/* ==========================================================================
   Sticky Search Container
   ========================================================================== */

.sticky-search {
    position: fixed;
    bottom: 60px; /* Above mini-console (44px content + safe-area) */
    left: 0;
    right: 0;
    z-index: var(--z-index-sticky, 1020);
    padding: 0 16px;
    /* Hidden by default - shown via JS when hero scrolls out */
    opacity: 0;
    transform: translateY(20px);
    pointer-events: none;
    transition: opacity 0.2s ease, transform 0.2s ease;
}

/* Visible state - applied by JavaScript */
.sticky-search.visible {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}

/* Hidden attribute override for initial state */
.sticky-search[hidden] {
    display: block !important; /* Override hidden but keep invisible via opacity */
}

/* ==========================================================================
   Search Form
   ========================================================================== */

.sticky-search-form {
    max-width: 600px;
    margin: 0 auto;
}

.sticky-search-input-wrapper {
    display: flex;
    align-items: center;
    background: white;
    border: 1px solid var(--bs-border-color, #D2B48C);
    border-radius: 24px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    overflow: hidden;
}

.sticky-search-input {
    flex: 1;
    padding: 12px 16px;
    border: none;
    background: transparent;
    font-size: 1rem;
    outline: none;
    min-width: 0; /* Allow flex shrink */
}

.sticky-search-input::placeholder {
    color: var(--bs-secondary, #6c757d);
}

.sticky-search-input:focus {
    outline: none;
}

/* Focus ring on wrapper instead */
.sticky-search-input-wrapper:focus-within {
    border-color: var(--bs-primary, #8B4513);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 0 0 2px rgba(139, 69, 19, 0.2);
}

/* ==========================================================================
   Submit Button
   ========================================================================== */

.sticky-search-submit {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    min-width: 44px; /* Touch target */
    min-height: 44px; /* Touch target */
    /* Fill wrapper height - let overflow:hidden clip to shape */
    align-self: stretch;
    background: var(--bs-primary, #8B4513);
    color: white;
    border: none;
    border-radius: 0; /* Let wrapper's overflow:hidden create the rounded edge */
    cursor: pointer;
    transition: background 0.15s ease;
}

.sticky-search-submit:hover {
    background: var(--bs-primary-dark, #704010);
}

.sticky-search-submit:active {
    transform: scale(0.95);
}

.sticky-search-submit .bi {
    font-size: 1.125rem;
    line-height: 1;
}

/* ==========================================================================
   Voice Input Button
   ========================================================================== */

.sticky-search-voice {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    min-width: 44px; /* Touch target */
    min-height: 44px; /* Touch target */
    background: transparent;
    color: var(--bs-secondary, #6c757d);
    border: none;
    cursor: pointer;
    transition: color 0.15s ease;
}

.sticky-search-voice:hover {
    color: var(--bs-primary, #8B4513);
}

.sticky-search-voice .bi {
    font-size: 1.125rem;
    line-height: 1;
}

/* Listening state */
.sticky-search-voice.voice-input-listening {
    color: var(--bs-danger, #dc3545);
    animation: pulse 1s ease-in-out infinite;
}

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }
}

/* ==========================================================================
   Responsive: Hide on desktop (>=768px)
   ========================================================================== */

@media (min-width: 768px) {
    .sticky-search {
        display: none !important;
    }
}

/* ==========================================================================
   Safe Area Adjustment (notched devices)
   ========================================================================== */

@media (max-width: 767.98px) {
    .sticky-search {
        /* Adjust bottom position to account for mini-console + safe area */
        bottom: calc(60px + env(safe-area-inset-bottom, 0));
    }

    /* Focused: tab bar is hidden, so sit on the keyboard (or the screen edge).
     * Drop the show-animation translate so the bar is not left 20px below the edge. */
    html.bottom-composer-focused .sticky-search {
        bottom: calc(var(--keyboard-bottom, 0) + env(safe-area-inset-bottom, 0));
        transform: none;
        transition: none;
    }
}

/* ==========================================================================
   Accessibility
   ========================================================================== */

/* Focus visible for keyboard navigation */
.sticky-search-submit:focus-visible {
    outline: 2px solid white;
    outline-offset: -4px;
}

/* Reduced motion preference */
@media (prefers-reduced-motion: reduce) {
    .sticky-search {
        transition: none;
    }

    .sticky-search-submit:active {
        transform: none;
    }
}

/* ==========================================================================
   No IntersectionObserver Fallback
   ========================================================================== */

/* If IntersectionObserver not supported, never show sticky search */
.no-intersection-observer .sticky-search {
    display: none !important;
}

/* ==========================================================================
   Chat Expanded: Hide sticky search when chat is open
   Chat takes priority for mobile bottom screen real estate.
   ========================================================================== */

body:has(.chat-sidebar.chat-expanded) .sticky-search {
    opacity: 0;
    pointer-events: none;
}

/* === motion.css === */
/* motion.css
 * Production motion patterns. Tokens live in base.css (:root).
 * Home search tabs + collection browse + browse-subnav share --tabs-dur / --tabs-ease.
 */

/* -------------------------------------------------------------------------- */
/* Sliding tabs (home, collections, browse-subnav)                            */
/* -------------------------------------------------------------------------- */

.qw-tabs {
    position: relative;
}

.qw-tabs-pill {
    position: absolute;
    top: 0;
    left: 0;
    width: 0;
    height: 100%;
    background: var(--tabs-pill-bg);
    border-radius: var(--bs-btn-border-radius, 0.375rem);
    transform: translateX(0);
    transition:
        transform var(--tabs-dur) var(--tabs-ease),
        width var(--tabs-dur) var(--tabs-ease);
    z-index: 0;
    pointer-events: none;
}

.qw-tabs--segmented {
    --tabs-pill-bg: var(--bs-primary);
    --tabs-text-active: #ffffff;
    --bs-btn-active-color: #ffffff;
    --bs-btn-active-bg: transparent;
    display: inline-flex;
    align-items: stretch;
    align-self: flex-start;
    flex: 0 0 auto;
    width: max-content;
    max-width: 100%;
    padding: 0;
    background: var(--tabs-bar-bg);
    border: 1px solid var(--bs-primary);
    border-radius: var(--bs-border-radius, 0.375rem);
    overflow: hidden;
}

.qw-tabs--segmented.btn-group {
    display: inline-flex;
    width: max-content;
}

#search-form .qw-tabs--segmented.btn-group {
    display: flex;
    flex: 1 1 auto;
    width: 100%;
    max-width: 600px;
}

.recent-toggle.qw-tabs--segmented {
    display: inline-flex;
    width: max-content;
    flex: 0 0 auto;
}

.qw-tabs--segmented > .qw-tabs-pill {
    top: 0;
    height: 100%;
    /* Inner curve = shell radius minus the 1px border. A full
     * --bs-btn-border-radius leaves --tabs-bar-bg slivers at the corners. */
    border-radius: calc(var(--bs-border-radius, 0.375rem) - 1px);
}

.qw-tabs--segmented > .btn,
.qw-tabs--segmented > [role="tab"] {
    position: relative;
    z-index: 1;
    border: 0;
    background: transparent;
    color: var(--tabs-text);
    box-shadow: none;
    transition: color var(--tabs-dur) var(--tabs-ease);
}

.qw-tabs--segmented > .btn-check:checked + .btn,
.qw-tabs--segmented > .btn-check:active + .btn,
.qw-tabs--segmented > .btn-primary,
.qw-tabs--segmented > .btn.active,
.qw-tabs--segmented > [role="tab"][aria-selected="true"],
.qw-tabs--segmented > [aria-pressed="true"],
.qw-tabs--segmented > [aria-current="true"] {
    color: var(--tabs-text-active);
    background: transparent;
    border-color: transparent;
    box-shadow: none;
}

.qw-tabs--segmented > .btn:hover,
.qw-tabs--segmented > .btn-check:hover + .btn {
    background: var(--tabs-pill-tint);
    color: var(--tabs-text);
}

.qw-tabs--segmented > .btn-check:checked + .btn:hover,
.qw-tabs--segmented > .btn-primary:hover,
.qw-tabs--segmented > .btn.active:hover,
.qw-tabs--segmented > [aria-pressed="true"]:hover,
.qw-tabs--segmented > [aria-current="true"]:hover {
    background: transparent;
    color: var(--tabs-text-active);
}

.qw-tabs--underline > .qw-tabs-pill {
    top: auto;
    bottom: 0;
    height: 2px;
    border-radius: 2px;
}

.browse-subnav-tabs.qw-tabs {
    position: relative;
    overflow: hidden;
}

.browse-subnav-tabs.qw-tabs--segmented {
    /* .qw-tabs--segmented is align-self: flex-start, which pins this 34px
       control to the top of the 48px bar while the sort and shuffle stay
       centered. Share their center; keep the fixed height so it doesn't stretch. */
    align-self: center;
    border-radius: var(--bs-border-radius, 0.375rem);
}

.browse-subnav-tabs.qw-tabs > .qw-tabs-pill {
    top: 0;
    /* moveTabPill translates from the border box; the pill's origin is the
       padding edge, so the 1px shell border insets the fill. Pull it back
       flush. Square ends: the shell's overflow + radius clips the outer
       corners, and the segment join stays square like a btn-group. */
    left: -1px;
    height: 100%;
    border-radius: 0;
}

.browse-subnav-tabs.qw-tabs a {
    position: relative;
    z-index: 1;
}

.browse-subnav-tabs.qw-tabs a + a {
    border-left: 0;
}

.browse-subnav-tabs.qw-tabs a[aria-selected="true"] {
    background-color: transparent;
    color: var(--tabs-text-active);
}

.originator-list-container .qw-tabs--segmented > .btn,
.tag-list-container .qw-tabs--segmented > .btn {
    padding: 0.25rem 0.5rem;
    font-size: 0.875rem;
    line-height: 1.2;
}

.sort-pill-toggle.qw-tabs--segmented {
    border-radius: var(--bs-border-radius, 0.375rem);
}

.sort-pill-toggle.qw-tabs--segmented > .qw-tabs-pill {
    /* Icon-only: slide a full-segment pill. Width snaps so a 0→N tween
     * cannot leave the glyph sitting in empty track. */
    transition: transform var(--tabs-dur) var(--tabs-ease);
}

.sort-pill-toggle.qw-tabs--segmented > .btn {
    flex: 1 1 0;
    min-width: 2.25rem;
    justify-content: center;
}

.sort-pill-toggle.qw-tabs--segmented .btn:first-child,
.sort-pill-toggle.qw-tabs--segmented .btn:last-child {
    border-radius: 0;
}

.collection-toggle-shell.qw-tabs--segmented {
    gap: 0;
}

/* -------------------------------------------------------------------------- */
/* Quote-card reveal (HTMX partials — not a search skeleton)                  */
/* -------------------------------------------------------------------------- */

.quote-card.qw-reveal {
    animation: qw-reveal-in var(--skeleton-dur) var(--skeleton-ease) both;
}

@keyframes qw-reveal-in {
    from {
        opacity: 0;
        transform: translateY(6px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

/* -------------------------------------------------------------------------- */
/* Input clear dissolve                                                       */
/* -------------------------------------------------------------------------- */

.qw-dissolve-layer {
    position: absolute;
    inset: 0;
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    gap: 0.35rem 0.4rem;
    padding: inherit;
    pointer-events: none;
    z-index: 2;
    color: inherit;
    font: inherit;
}

.qw-dissolve-word {
    display: inline-block;
    opacity: 1;
    filter: blur(0);
    transform: translateY(0);
    transition:
        opacity var(--dissolve-dur) var(--dissolve-ease),
        filter var(--dissolve-dur) var(--dissolve-ease),
        transform var(--dissolve-dur) var(--dissolve-ease);
}

.qw-dissolve-word.is-out {
    opacity: 0;
    filter: blur(4px);
    transform: translateY(-4px);
}

/* -------------------------------------------------------------------------- */
/* Icon swaps                                                                 */
/* -------------------------------------------------------------------------- */

.qw-icon-swap {
    display: inline-flex;
    transition:
        opacity var(--icon-dur) var(--icon-ease),
        transform var(--icon-dur) var(--icon-ease);
}

.qw-icon-swap.is-pop,
.qw-icon-swap.is-pop i {
    animation: qw-icon-pop var(--icon-dur) var(--icon-ease);
}

@keyframes qw-icon-pop {
    from {
        opacity: 0;
        transform: scale(0.85);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

/* -------------------------------------------------------------------------- */
/* Plans price pop-in                                                         */
/* -------------------------------------------------------------------------- */

.price-display:has(.monthly-price):has(.annual-price) {
    display: grid;
}

.price-display .monthly-price,
.price-display .annual-price {
    grid-area: 1 / 1;
}

.price-display .monthly-price.is-active,
.price-display .annual-price.is-active {
    animation: qw-price-pop var(--pop-dur) var(--pop-ease);
}

@keyframes qw-price-pop {
    from {
        opacity: 0;
        transform: translateY(8px) scale(0.94);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

/* -------------------------------------------------------------------------- */
/* Auth error shake                                                           */
/* -------------------------------------------------------------------------- */

.auth-card.is-shake {
    animation: qw-form-shake var(--shake-dur) cubic-bezier(0.36, 0.07, 0.19, 0.97);
}

@keyframes qw-form-shake {
    10%,
    90% { transform: translateX(-2px); }

    20%,
    80% { transform: translateX(4px); }

    30%,
    50%,
    70% { transform: translateX(-6px); }

    40%,
    60% { transform: translateX(6px); }
}

/* -------------------------------------------------------------------------- */
/* Tooltip open / close (Bootstrap animation stays false — CSS owns the fade) */
/* -------------------------------------------------------------------------- */

.tooltip {
    transition: opacity var(--tooltip-dur) var(--tooltip-ease);
}

/* -------------------------------------------------------------------------- */
/* Reduced motion: instant or opacity-only. No translate / scale / shake.     */
/* -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    .qw-tabs-pill {
        transition: none;
    }

    .quote-card.qw-reveal,
    .qw-icon-swap.is-pop,
    .price-display .monthly-price.is-active,
    .price-display .annual-price.is-active {
        animation: qw-fade-in var(--motion-micro) var(--motion-ease) both;
        transform: none;
    }

    .qw-dissolve-word,
    .qw-dissolve-word.is-out {
        filter: none;
        transform: none;
        transition: opacity var(--dissolve-dur) var(--dissolve-ease);
    }

    .auth-card.is-shake {
        animation: none;
    }

    .collect-icon:hover,
    .collect-icon.collected:hover {
        transform: none;
    }
}

@keyframes qw-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}
