/* ============================================================
   gallery-custom.css — Tailwind migration residue
   ============================================================
   CSS that CANNOT be expressed as Tailwind utility classes.
   Covers: justified gallery layout, gallery overlay, PhotoSwipe
   customisations, rating modal, video badge, album-card hover,
   explorer sidebar collapse / drawer animations, spinner, and
   color-mix() hover utilities.

   The bulk of gallery.css, gallery-explorer.css and
   members-gallery.css will migrate to Tailwind @apply / utility
   classes in templates.  This file holds only the residue.
   ============================================================ */

/* ── Justified gallery layout ──────────────────────────────── */

.justified-gallery {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    /* Items carry `flex-basis: Npx` hints sized for the photo's aspect
     * ratio (e.g. 447px for a 16:9 thumb). On narrow viewports the
     * basis can exceed the container; without these guards the grid
     * widens the whole document and Firefox re-anchors position:fixed
     * elements (FAB, salon-bubble) to the intrinsic scroll width
     * instead of the visual viewport. Confirmed via runtime probe:
     * `document.documentElement.scrollWidth` jumped from 392 → 450
     * right after the AJAX grid populated, pushing the FAB to x=374
     * in a vw=359 viewport. */
    max-width: 100%;
    min-width: 0;
    overflow-x: clip;
}

/* Prevent last row from stretching */
.justified-gallery::after {
    content: '';
    flex-grow: 999999;
}

/* Hidden gallery — used for podium lightbox triggers */
.justified-gallery--hidden {
    position: absolute;
    width: 0;
    height: 0;
    overflow: hidden;
    opacity: 0;
    pointer-events: none;
}

.justified-gallery__item {
    position: relative;
    overflow: hidden;
    cursor: pointer;
    text-decoration: none;
    container-type: inline-size;
}

.justified-gallery__item img {
    height: 250px;
    object-fit: cover;
    width: 100%;
    display: block;
    transition: transform 0.3s ease;
}

/* Hover desktop only */
@media (hover: hover) {
    .justified-gallery__item:hover img {
        transform: scale(1.03);
    }
}

/* Focal zoom: image rendered at natural pixel size (2x rendition)
   inside the smaller container — ~2x zoom on the zone of interest.
   object-position centres the view on the focal point. */
.justified-gallery__item--focal img {
    object-fit: none;
}

/* Compact variant (profile pages) */
.justified-gallery--compact .justified-gallery__item img {
    height: 180px;
}

/* JS-enhanced: heights are set inline, override default */
.justified-gallery--enhanced .justified-gallery__item img {
    height: 100%;
}

/* Video items */
.justified-gallery__item--video {
    position: relative;
}

/* Hide author on very narrow thumbnails */
@container (max-width: 160px) {
    .gallery-overlay__info span {
        display: none;
    }
}

/* Responsive justified gallery heights */
@media (width <= 768px) {
    .justified-gallery__item img {
        height: 150px;
    }

    .justified-gallery--compact .justified-gallery__item img {
        height: 130px;
    }
}

@media (width <= 480px) {
    .justified-gallery__item img {
        height: 120px;
    }

    .justified-gallery--compact .justified-gallery__item img {
        height: 100px;
    }
}

/* ── Gallery overlay (hover actions on thumbnails) ─────────── */

.gallery-overlay {
    position: absolute;
    z-index: 2;
    inset: 0;
    padding: 7px 8px;
    background: linear-gradient(to bottom, rgb(0 0 0 / 30%) 0%, transparent 35%, transparent 55%, rgb(0 0 0 / 78%) 100%);
    display: flex;
    flex-flow: column wrap;
    justify-content: space-between;
    align-items: flex-end;
    gap: 4px 8px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease;
    box-sizing: border-box;
}

@media (hover: hover) {
    .justified-gallery__item:hover .gallery-overlay {
        opacity: 1;
        pointer-events: auto;
    }
}

/* Touch: overlay activated by JS via class */
.justified-gallery__item--touch-active .gallery-overlay {
    opacity: 1;
    pointer-events: auto;
}

.gallery-overlay__info {
    display: flex;
    flex-direction: column;
    align-items: center;
    align-self: stretch;
    margin-top: auto;
    color: #fff;
    font-size: var(--fs-sm);
    line-height: 1.3;
    text-shadow: 0 1px 3px rgb(0 0 0 / 60%);
    text-align: center;
}

.gallery-overlay__info strong {
    display: block;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.gallery-overlay__info span {
    display: block;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    opacity: 0.85;
    font-size: var(--fs-xs);
}

.gallery-overlay__actions {
    display: flex;
    gap: 4px;
    order: -1;
    align-self: flex-start;
    align-items: center;
}

button.gallery-overlay__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    min-width: 0;
    min-height: 0;
    padding: 0;
    margin: 0;
    border-radius: 50%;
    border: none;
    background: rgb(0 0 0 / 45%);
    backdrop-filter: blur(6px);
    color: #fff;
    font-size: var(--fs-md);
    font-weight: normal;
    line-height: 1;
    cursor: pointer;
    text-decoration: none;
    transition: background 0.2s;
    box-shadow: none;
}

button.gallery-overlay__btn:hover {
    background: rgb(0 0 0 / 65%);
    text-decoration: none;
    transform: none;
}

.gallery-overlay__btn--like.liked {
    color: var(--color-danger);
}

/* Read-only mode (e.g. owner viewing own photo): heart visible to read the
   likes received, but click is disabled. */
.gallery-overlay__btn--readonly,
.gallery-overlay__btn:disabled {
    cursor: default;
    opacity: 0.85;
}

/* "Mine" indicator — current user has personally liked / rated this media.
   Tints the button background with the action color (red for liked, gold
   for rated) so the user instantly spots their own contribution. Same
   visual treatment for overlay (round) and lightbox (rectangle) — only
   the background changes, no extra DOM or risky positioning.
   Layered on top of `.liked` / `.rated`:
   - bare icon  → nobody acted
   - filled icon, neutral bg  → at least one other person acted
   - filled icon, tinted bg   → I am one of them. */
.gallery-overlay__btn--like.mine,
.pswp__button--like-button.pswp__button--mine {
    background: color-mix(in srgb, var(--color-danger) 35%, transparent);
}

.gallery-overlay__btn--rating.mine,
.pswp__button--rating-button.pswp__button--mine {
    background: color-mix(in srgb, #fbbf24 35%, transparent);
}

.gallery-overlay__btn--readonly:hover,
.gallery-overlay__btn:disabled:hover {
    background: rgb(0 0 0 / 45%);
}

/* Buttons that host an inner badge (likes count, unread comments) need
   relative positioning + overflow visible so the badge can sit outside. */
button.gallery-overlay__btn--like,
button.gallery-overlay__btn--comment {
    position: relative;
    overflow: visible;
}

.gallery-overlay__badge {
    position: absolute;
    top: -4px;
    right: -4px;
    min-width: 15px;
    height: 15px;
    padding: 0 4px;
    border-radius: 999px;
    background: rgb(0 0 0 / 60%);
    color: #fff;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-weight: 700;
    line-height: 15px;
    text-align: center;
    pointer-events: none;
}

.gallery-overlay__badge--unread {
    background: var(--color-danger);
}

/* Rating button — same circular footprint as the like button (38×38).
 * The avg score lives inside the badge (overlap top-right) when rated. */
button.gallery-overlay__btn--rating {
    position: relative;
    overflow: visible;
    color: #fff;
}

/* Rated state — at least one user has rated. Star fills gold. */
.gallery-overlay__btn--rating.rated {
    color: #fbbf24;
}

.gallery-overlay__btn--rating.rated svg {
    fill: currentColor;
}

/* Rating badge holds the avg score (e.g. "4,3"), wider than the like
 * count badge to fit 3 characters. */
.gallery-overlay__btn--rating .gallery-overlay__badge {
    min-width: 22px;
    padding: 0 5px;
    background: var(--color-warning, #b14d1e);
    color: #fff;
}

/* ── Mobile FAB — filters drawer trigger ───────────────────────
 * Replaces the previously-broken Tailwind arbitrary-value class on the
 * button (`bg-[var(--color-primary)]` is not generated in the compiled
 * tailwind.css bundle, so the bouton was transparent — issue #87).
 * Style follows the Claude Design fab-drawer preview: 56×56 (touch AA),
 * primary background with a colored shadow, palette-aware. */
/* ── Collective gallery header (Pattern C) ─────────────────────────
 * Mobile-first. Strategy lives entirely here so a desktop tweak
 * doesn't accidentally break mobile (and vice-versa).
 *
 * Mobile (default):
 *   ┌────────────────────────┐
 *   │ Title                  │
 *   │ Description            │
 *   ├────────────────────────┤
 *   │ [🔍 Search] (full w)   │
 *   ├────────────────────────┤
 *   │ [Sort▼][Page▼][×][≡] [Ajouter ml-auto] (wraps as needed) │
 *   └────────────────────────┘
 *
 * Desktop (≥ 769px):
 *   Title / description block
 *   [Search flex-1] [Sort▼][Page▼][×][≡]                [Ajouter]
 *   (single row, search takes the available space, CTA pushed right)
 * ----------------------------------------------------------------- */

.gallery-header {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    margin-bottom: 1.5rem;
}

.gallery-header__title {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.gallery-header__title h1 { margin: 0; }
.gallery-header__title p {
    margin: 0;
    color: var(--color-text-secondary);
}

.gallery-header__controls {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-width: 0;
}

.gallery-header__search {
    width: 100%;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-surface);
    color: var(--color-text);
    font-size: var(--fs-sm);
    box-sizing: border-box;
}

.gallery-header__row2 {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

.gallery-header__select {
    padding: 0.5rem 0.5rem;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-surface);
    color: var(--color-text);
    font-size: var(--fs-sm);
}

/* Reset filters button — Lucide x icon, danger color, subtle by default,
 * fills with danger on hover to confirm the destructive action. Sits on
 * top of `.btn .btn--small`, only overrides the color/border bits. */
.gallery-header__reset {
    background: transparent;
    border: 1px solid var(--color-danger);
    color: var(--color-danger);
}

.gallery-header__reset:hover {
    background: color-mix(in srgb, var(--color-danger) 12%, transparent);
    color: var(--color-danger);
    border-color: var(--color-danger);
}

.gallery-header__cta {
    margin-left: auto;
}

/* Sidebar toggle: hidden on phones (FAB takes over) */
@media (max-width: 768px) {
    .gallery-header__sidebar-toggle { display: none; }
}

/* ── Desktop: single row layout ── */
@media (min-width: 769px) {
    .gallery-header__controls {
        flex-direction: row;
        align-items: center;
        gap: 0.5rem;
    }
    .gallery-header__search {
        flex: 1 1 0;
        min-width: 12rem;
        max-width: 500px;
    }
    /* `display: contents` flattens the row2 wrapper so its children
     * become direct flex items of .gallery-header__controls — same single
     * horizontal line as before the restructure. */
    .gallery-header__row2 {
        display: contents;
    }
    /* Lock every non-search widget at its natural width so the search
     * (the only flex-grow item) absorbs every remaining pixel instead of
     * being squeezed by selects/buttons that have a wide min-content. */
    .gallery-header__controls > *:not(.gallery-header__search),
    .gallery-header__row2 > * {
        flex: 0 0 auto;
    }
    .gallery-header__select {
        width: auto;
    }
}

.gallery-fab {
    /* Hidden by default — only revealed on phones via the @media block
     * below. Defining `display` outside a media query would beat the
     * Tailwind `md:hidden` utility (gallery-custom.css loads after
     * tailwind.css), making the FAB visible on desktop too. */
    display: none;
    position: fixed;
    bottom: 5.5rem;
    right: 1.25rem;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: var(--color-primary);
    color: #fff;
    border: none;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-shadow: 0 6px 20px color-mix(in srgb, var(--color-primary) 40%, transparent);
    z-index: var(--z-modal);
    transition: background 0.15s, transform 0.15s;
}

/* Stacks above the global salon-bubble (bottom: 1.25rem, right: 1.25rem,
 * z-index: var(--z-modal), height ~56px) — leaving ~10px breathing room. */
@media (max-width: 767px) {
    .gallery-fab { display: flex; }
}

.gallery-fab:hover {
    background: var(--color-primary-hover);
    transform: translateY(-1px);
}

.gallery-fab:active {
    transform: translateY(0);
}

/* ── Gallery contextual prompt ─────────────────────────────── */

.gallery-prompt {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.75rem 1rem;
    margin-bottom: 1rem;
    border-radius: var(--radius);
    background: color-mix(in srgb, var(--color-primary) 8%, transparent);
    border: 1px solid color-mix(in srgb, var(--color-primary) 20%, transparent);
}

.gallery-prompt__text {
    flex: 1;
    font-size: var(--fs-base);
    color: var(--color-text);
}

@media (width <= 480px) {
    .gallery-prompt {
        flex-direction: column;
        align-items: stretch;
        text-align: center;
    }
}

/* ── PhotoSwipe custom styles ──────────────────────────────── */

/* Caption hidden in grid, read by PhotoSwipe */
.pswp-caption-content {
    display: none;
}

/* PhotoSwipe toolbar buttons */
.pswp__button--maximize,
.pswp__button--fullscreen,
.pswp__button--caption-toggle,
.pswp__button--bg-toggle,
.pswp__button--rating-button,
.pswp__button--detail-link {
    width: 50px;
    height: 60px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.pswp__button--maximize svg,
.pswp__button--fullscreen svg,
.pswp__button--caption-toggle svg,
.pswp__button--bg-toggle svg,
.pswp__button--rating-button svg,
.pswp__button--detail-link svg,
.pswp__button--like-button svg {
    width: 22px;
    height: 22px;
    fill: none;
    stroke: #fff;
}

/* Like (lightbox + grid overlay) — outline white by default, filled red when liked.
 * SVG presentation attributes (fill="none") have specificity 0, so CSS overrides them. */
.pswp__button--like-button.pswp__button--liked svg,
.gallery-overlay__btn--like.liked svg {
    fill: var(--color-danger, #dc2626);
    stroke: var(--color-danger, #dc2626);
}

/* Lightbox toolbar buttons that host an inner count badge need relative
   positioning + visible overflow (likes count, comments count). */
.pswp__button--like-button,
.pswp__button--detail-link {
    position: relative;
    overflow: visible;
}

/* Read-only mode (owner viewing own photo): heart visible to read the
   likes received, but click does nothing. */
.pswp__button--readonly {
    cursor: default;
    opacity: 0.85;
}

/* Counts badge for PhotoSwipe toolbar buttons (likes, comments).
   Theme-aware: on the default dark stage, badge is light (white-ish on dark
   toolbar); on the white stage (`pswp--bg-white`), badge inverts to dark on
   light. Same shape, opposite fill — mirrors how the toolbar SVG icons flip. */
.pswp__badge {
    position: absolute;
    top: 6px;
    right: 4px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 999px;
    background: rgb(255 255 255 / 92%);
    color: #1a1a2e;
    font-family: var(--font-mono);
    font-size: 9px;
    font-weight: 700;
    line-height: 16px;
    text-align: center;
    pointer-events: none;
}

.pswp--bg-white .pswp__badge {
    background: rgb(0 0 0 / 70%);
    color: #fff;
}

/* Rating badge holds the avg score (e.g. "4,3"), wider than the count badge. */
.pswp__button--rating-button .pswp__badge {
    min-width: 22px;
}

/* ── Mobile touch targets (AA) ──────────────────────────────────
 * Audience is 50+, mobile-first. Bring overlay buttons + count chips up
 * to the 44×44 WCAG minimum on phones (under 768px). Lightbox toolbar
 * buttons already get a width tweak in the dedicated section below. */
@media (width <= 768px) {
    button.gallery-overlay__btn {
        width: 44px;
        height: 44px;
    }

    .gallery-overlay__btn--rating .gallery-overlay__badge,
    .gallery-overlay__btn--like .gallery-overlay__badge,
    .gallery-overlay__btn--comment .gallery-overlay__badge {
        min-width: 18px;
        height: 18px;
        font-size: 10px;
        line-height: 18px;
    }
}

/* Compensate vertical offset between left/right toolbar zones in PhotoSwipe.
 * Like-button (order < 7, before preloader) sits in the left zone which renders 2.5px higher
 * than the right zone (where bg/maximize/caption/fullscreen live). */
.pswp__button--like-button svg {
    margin-top: 2.5px;
}

/* Rating button — default: white outline (consistent with like button).
 * When `.pswp__button--rated` is set, star fills gold. */
.pswp__button--rating-button svg {
    stroke: #fff;
    fill: none;
    width: 26px;
    height: 26px;
    flex-shrink: 0;
}

.pswp--bg-white .pswp__button--rating-button svg {
    stroke: #333;
}

/* Rated state: at least one user has rated. Star fills gold. */
.pswp__button--rating-button.pswp__button--rated svg {
    fill: #fbbf24;
    stroke: #fbbf24;
}

.pswp--bg-white .pswp__button--rating-button.pswp__button--rated svg {
    fill: #b7791f;
    stroke: #b7791f;
}

/* Rating button keeps PhotoSwipe's standard square footprint (same as like).
 * The avg score appears inside `.pswp__badge` overlap, no inline label. */

/* stylelint-disable-next-line no-descending-specificity -- different button */
.pswp__button--detail-link svg {
    width: 26px;
    height: 26px;
}

/* `.pswp-rating-label` is no longer rendered — the avg score lives inside
 * the `.pswp__badge` overlap. Kept hidden in case stale DOM survives a
 * partial reload during the rollout. */
.pswp-rating-label {
    display: none;
}

/* Mobile: scale down detail-link icon (rating button uses standard width) */
@media (width <= 480px) {
    .pswp__button--detail-link {
        width: 40px;
        margin-left: 4px;
        padding: 0;
    }

    .pswp__button--rating-button svg,
    .pswp__button--detail-link svg {
        width: 22px;
        height: 22px;
    }
}

/* Caption toggle: dim when captions are hidden */
.pswp__button--caption-toggle.pswp__button--caption-off {
    opacity: 0.5;
}

/* White background mode */
.pswp--bg-white .pswp__bg {
    background: #fff !important;
}

.pswp--bg-white {
    --pswp-icon-color: #333;
    --pswp-icon-color-secondary: #fff;
    --pswp-icon-stroke-color: #fff;
}

.pswp--bg-white .pswp__button--maximize svg,
.pswp--bg-white .pswp__button--fullscreen svg,
.pswp--bg-white .pswp__button--caption-toggle svg,
.pswp--bg-white .pswp__button--bg-toggle svg,
.pswp--bg-white .pswp__button--rating-button svg,
.pswp--bg-white .pswp__button--detail-link svg,
.pswp--bg-white .pswp__button--like-button svg {
    fill: none;
    stroke: #333;
}

/* Restore the danger fill on liked heart even on the white stage:
 * the rule above overrides the generic `.liked` selector by source-order,
 * so we re-assert it here with higher specificity. */
.pswp--bg-white .pswp__button--like-button.pswp__button--liked svg {
    fill: var(--color-danger, #dc2626);
    stroke: var(--color-danger, #dc2626);
}

.pswp--bg-white .pswp__counter {
    color: #333;
}

.pswp--bg-white .pswp__top-bar {
    background: linear-gradient(to bottom, rgb(255 255 255 / 50%) 0%, transparent 100%);
}

.pswp--bg-white .pswp__dynamic-caption {
    color: #222;
}

.pswp--bg-white .pswp__dynamic-caption a {
    color: #1a73e8;
}

/* PhotoSwipe auto-hide UI */
.pswp--ui-hidden .pswp__top-bar,
.pswp--ui-hidden .pswp__counter {
    opacity: 0;
    transition: opacity 0.3s;
    pointer-events: none;
}

/* stylelint-disable-next-line no-descending-specificity -- base state after modifier */
.pswp__top-bar,
/* stylelint-disable-next-line no-descending-specificity */
.pswp__counter {
    opacity: 1;
    transition: opacity 0.3s;
}

/* Counter font (mono): aligns with overlay badge for consistent ratings/EXIF feel */
.pswp__counter {
    font-family: var(--font-mono);
}

/* Edit button in lightbox */
.pswp__button--edit-link {
    width: 50px;
    height: 60px;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* stylelint-disable-next-line no-descending-specificity -- different button */
.pswp__button--edit-link svg {
    width: 22px;
    height: 22px;
    fill: none;
    stroke: #fff;
}

.pswp--bg-white .pswp__button--edit-link svg {
    stroke: #333;
}

/* PhotoSwipe video container */
.pswp-video-container {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    background: #000;
}

/* ── Video badge ───────────────────────────────────────────── */

.video-badge {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: var(--fs-xl);
    color: white;
    background: rgb(0 0 0 / 50%);
    border-radius: 50%;
    width: 3rem;
    height: 3rem;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

/* ── Album card hover ──────────────────────────────────────── */

.album-card {
    transition: box-shadow 0.2s ease, transform 0.2s ease;
}

.album-card:hover {
    box-shadow: 0 4px 16px var(--color-shadow);
    transform: translateY(-2px);
}

/* ── Rating mini-modal overlay ─────────────────────────────── */

.rating-modal-overlay {
    position: fixed;
    inset: 0;
    background: rgb(0 0 0 / 50%);
    z-index: var(--z-modal);
    display: none;
    align-items: center;
    justify-content: center;
}

.rating-modal-overlay--visible {
    display: flex;
}

.rating-modal {
    background: var(--color-surface);
    border-radius: var(--radius-lg);
    padding: 1.5rem;
    min-width: 280px;
    max-width: 360px;
    width: 90%;
    position: relative;
    box-shadow: 0 8px 32px rgb(0 0 0 / 30%);
}

.rating-modal__close {
    position: absolute;
    top: 0.5rem;
    right: 0.75rem;
    border: none;
    background: none;
    font-size: var(--fs-xl);
    cursor: pointer;
    color: var(--color-text-secondary);
    line-height: 1;
}

.rating-modal__close:hover {
    color: var(--color-text);
}

.rating-modal__title {
    margin: 0 0 1rem;
    font-size: var(--fs-md);
    color: var(--color-text);
}

.rating-modal__row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.5rem;
}

.rating-modal__label {
    width: 6rem;
    font-size: var(--fs-base);
    color: var(--color-text-secondary);
}

.rating-modal__delete {
    display: block;
    margin-top: 1rem;
    padding: 0;
    background: none;
    border: none;
    color: var(--color-danger);
    font-size: var(--fs-sm);
    cursor: pointer;
    text-decoration: underline;
    opacity: 0.7;
}

.rating-modal__delete:hover {
    opacity: 1;
}

/* ── Explorer sidebar collapse animation ───────────────────── */

.sidebar-collapsed {
    width: 0;
    padding: 0;
    border: none;
    overflow: hidden;
    opacity: 0;
    pointer-events: none;
}

/* ── Explorer drawer (mobile bottom-sheet) ─────────────────── */

.explorer-drawer-backdrop {
    display: none;
    position: fixed;
    inset: 0;
    background: rgb(0 0 0 / 40%);
    z-index: var(--z-drawer-backdrop);
}

.explorer-drawer-backdrop.visible {
    display: block;
}

.explorer-drawer {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    max-height: 70vh;
    background: var(--color-surface);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    z-index: calc(var(--z-drawer) + 1);
    transform: translateY(100%);
    transition: transform 0.3s ease;
    overflow-y: auto;
    padding: 0 1rem 1rem;
}

.explorer-drawer.visible {
    display: block;
}

.explorer-drawer.open {
    transform: translateY(0);
}

/* Children moved into the drawer (e.g. the collective sidebar's filter
 * sections) can carry desktop-only width hints (`w-64`, fixed paddings).
 * Force them to fit the drawer's available width to avoid horizontal
 * overflow inside the bottom sheet. */
.explorer-drawer > * {
    max-width: 100%;
    box-sizing: border-box;
}

.explorer-drawer__handle {
    display: flex;
    justify-content: center;
    padding: 0.75rem 0 0.5rem;
    cursor: grab;
    position: sticky;
    top: 0;
    background: var(--color-surface);
    z-index: 1;
}

.explorer-drawer__handle::after {
    content: '';
    width: 40px;
    height: 4px;
    border-radius: var(--radius-sm);
    background: var(--color-border);
}

/* ── Explorer spinner ──────────────────────────────────────── */

.explorer-spinner {
    display: inline-block;
    width: 24px;
    height: 24px;
    border: 3px solid var(--color-border);
    border-top-color: var(--color-primary);
    border-radius: 50%;
    animation: explorer-spin 0.8s linear infinite;
}

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

/* ── color-mix() hover utilities ───────────────────────────────
   Reusable classes for hover backgrounds using color-mix().
   Tailwind cannot generate color-mix() with CSS custom properties.
   ────────────────────────────────────────────────────────────── */

.hover-primary-subtle:hover {
    background: color-mix(in srgb, var(--color-primary) 8%, transparent);
}

.hover-primary-light:hover {
    background: color-mix(in srgb, var(--color-primary) 12%, transparent);
}

.hover-primary-medium:hover {
    background: color-mix(in srgb, var(--color-primary) 20%, transparent);
}

.hover-danger-subtle:hover {
    background: color-mix(in srgb, var(--color-danger) 8%, transparent);
}

/* Background (non-hover) utilities using color-mix() */
.bg-primary-subtle {
    background: color-mix(in srgb, var(--color-primary) 8%, transparent);
}

.bg-primary-light {
    background: color-mix(in srgb, var(--color-primary) 12%, transparent);
}

.bg-primary-medium {
    background: color-mix(in srgb, var(--color-primary) 20%, transparent);
}

.bg-danger-25 {
    background: color-mix(in srgb, var(--color-danger) 25%, transparent);
}

/* Border utilities using color-mix() */
.border-primary-subtle {
    border-color: color-mix(in srgb, var(--color-primary) 20%, transparent);
}

.border-primary-medium {
    border-color: color-mix(in srgb, var(--color-primary) 40%, var(--color-border));
}

.border-muted-50 {
    border-color: color-mix(in srgb, var(--color-border) 50%, transparent);
}

/* ── Explorer card line-clamp (webkit-only) ────────────────────
   Tailwind line-clamp works but these use -webkit- prefixed props
   that some builds strip.  Keep as fallback.
   ────────────────────────────────────────────────────────────── */

.explorer-card__title-clamp {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.explorer-card__excerpt-clamp {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* ── Transfer panel (modals) ─────────────────────────────────
   3-column transfer layout used by owner/archive picker modals.
   Classes referenced in modal.ts + collective/filters.ts.
   ────────────────────────────────────────────────────────────── */

.ge-modal--wide {
    max-width: 560px;
}

.ge-modal--advanced {
    max-width: 680px;
    max-height: 85vh;
}

.ge-modal__search {
    margin: 0.5rem 1.25rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    font-size: var(--fs-base);
    width: calc(100% - 2.5rem);
}

.ge-transfer {
    display: flex;
    gap: 0;
    margin: 0 1.25rem;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    overflow: hidden;
}

.ge-transfer__panel {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.ge-transfer__panel h4 {
    font-size: var(--fs-sm);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-text-secondary);
    padding: 0.5rem 0.6rem 0.25rem;
    margin: 0;
}

.ge-transfer__list {
    flex: 1;
    overflow-y: auto;
    max-height: 280px;
    min-height: 120px;
}

.ge-transfer__item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.4rem 0.6rem;
    font-size: var(--fs-sm);
    cursor: pointer;
    transition: background 0.1s;
    border-bottom: 1px solid color-mix(in srgb, var(--color-border) 50%, transparent);
}

.ge-transfer__item:last-child { border-bottom: none; }

.ge-transfer__item:hover {
    background: var(--color-bg-alt);
}

.ge-transfer__item.selected {
    background: color-mix(in srgb, var(--color-primary) 12%, transparent);
}

.ge-transfer__item.focused {
    background: color-mix(in srgb, var(--color-primary) 20%, transparent);
    outline: 2px solid var(--color-primary);
    outline-offset: -2px;
}

.ge-transfer__item--removable {
    cursor: default;
}

.ge-transfer__item-name {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ge-transfer__item-count {
    font-size: var(--fs-xs);
    color: var(--color-text-secondary);
}

.ge-transfer__item-remove {
    width: 18px;
    height: 18px;
    border: none;
    background: none;
    color: var(--color-text-secondary);
    cursor: pointer;
    padding: 0;
    font-size: var(--fs-base);
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    border-radius: 50%;
    transition: background 0.1s, color 0.1s;
}

.ge-transfer__item-remove:hover {
    background: rgb(255 59 48 / 15%);
    color: var(--color-danger);
}

.ge-transfer__placeholder {
    font-size: var(--fs-sm);
    color: var(--color-text-secondary);
    padding: 0.75rem 0.6rem;
    font-style: italic;
}

/* 3-column archive modal */
.ge-transfer--3col .ge-transfer__panel:first-child {
    flex: 0 0 130px;
}

.ge-transfer--3col .ge-transfer__panel:nth-child(3) {
    flex: 1;
}

.ge-transfer--3col .ge-transfer__panel:last-child {
    flex: 1;
}

.ge-transfer__controls {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.25rem;
    padding: 0.5rem;
    border-left: 1px solid var(--color-border);
    border-right: 1px solid var(--color-border);
    background: var(--color-bg-alt);
}

.ge-transfer__btn {
    width: 32px;
    height: 32px;
    border-radius: var(--radius);
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text-secondary);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s, color 0.15s;
    padding: 0;
}

.ge-transfer__btn:hover {
    background: var(--color-primary);
    color: #fff;
    border-color: var(--color-primary);
}

/* ── Advanced filters modal ──────────────────────────────────
   Pill-based filter UI used by explorer/filters.ts,
   members/filters.ts, and collective/filters.ts.
   ────────────────────────────────────────────────────────────── */

.ge-advanced__body {
    padding: 0 1.25rem;
    overflow-y: auto;
    max-height: calc(85vh - 120px);
}

.ge-advanced__section {
    margin-bottom: 1rem;
}

.ge-advanced__section:last-child {
    margin-bottom: 0.5rem;
}

.ge-advanced__section-title {
    font-size: var(--fs-sm);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-text-secondary);
    margin: 0 0 0.4rem;
}

.ge-advanced__search {
    width: 100%;
    padding: 0.4rem 0.6rem;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    font-size: var(--fs-sm);
    margin-bottom: 0.4rem;
    background: var(--color-surface);
    color: var(--color-text);
}

.ge-advanced__pills {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
}

.ge-advanced__pill {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.3rem 0.6rem;
    border: 1px solid var(--color-border);
    border-radius: 999px;
    background: var(--color-surface);
    color: var(--color-text);
    font-size: var(--fs-sm);
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.ge-advanced__pill:hover {
    background: var(--color-bg-alt);
    border-color: color-mix(in srgb, var(--color-primary) 40%, var(--color-border));
}

.ge-advanced__pill.active {
    background: var(--color-primary);
    color: #fff;
    border-color: var(--color-primary);
}

.ge-advanced__pill--year {
    font-weight: 600;
}

.ge-advanced__pill--month {
    font-size: var(--fs-xs);
    padding: 0.2rem 0.5rem;
}

.ge-advanced__pill-count {
    font-size: var(--fs-xs);
    opacity: 0.7;
}

.ge-advanced__dates {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.ge-advanced__year-group {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.ge-advanced__months {
    display: flex;
    flex-wrap: wrap;
    gap: 0.2rem;
    padding-left: 0.5rem;
}

/* ── Transfer panel mobile responsive ────────────────────────── */

@media (width <= 768px) {
    .ge-transfer {
        flex-direction: column;
    }

    .ge-transfer__controls {
        flex-direction: row;
        gap: 0.75rem;
        border-left: none;
        border-right: none;
        border-top: 1px solid var(--color-border);
        border-bottom: 1px solid var(--color-border);
    }

    .ge-transfer__btn svg {
        transform: rotate(90deg);
    }

    .ge-transfer__list {
        max-height: 180px;
    }

    .ge-modal--advanced {
        max-width: 95%;
    }
}
