/* ============================================================
   Elevate Photos — unified photo system styles
   Covers: photo hub, crop modal, swipe viewer,
           profile carousel, discovery card carousel
   ============================================================ */

/* ── Reset helpers ──────────────────────────────────────────── */
.ep-hub *,
.ep-crop-modal *,
.ep-viewer *,
.ep-profile-gallery * {
    box-sizing: border-box;
}

/* ── Legacy jCrop avatar floatbox: mobile responsive layout ─────
 *
 * The base avatar_change UI ships with a horizontal flex of
 *   [crop area at boxWidth] [16px gap] [preview at minCropSize]
 * which on a 375px iPhone needs ~600px+ of horizontal space —
 * impossible. avatar_crop_mobile.js sets body.elv-mobile-crop on
 * narrow viewports and reduces minCropSize so jCrop's math works.
 * These rules complete the picture by stacking the layout
 * vertically and shrinking the preview to a sane mobile size.
 *
 * Belt + suspenders: the @media query also catches narrow
 * desktop windows (e.g., a tablet in portrait) where the JS
 * shim hasn't tagged the body class for any reason.
 */
@media (max-width: 720px) {
    /* Floatbox itself: fit viewport, no horizontal scroll. The
       Oxwall floatbox attaches as .ow_floatbox — we constrain
       width, then re-center it. */
    .ow_floatbox {
        width:       calc(100vw - 16px) !important;
        max-width:   calc(100vw - 16px) !important;
        left:        8px !important;
        right:       8px !important;
        margin-left: 0 !important;
    }

    /* Crop step layout: stack vertically. */
    body .ow_avatar_crop_wrap .ow_avatar_crop_area,
    body .ow_avatar_crop_wrap .ow_avatar_crop_result {
        float:        none !important;
        width:        100% !important;
        margin-right: 0 !important;
        margin-left:  0 !important;
    }
    body .ow_avatar_crop_wrap .ow_avatar_crop_area {
        margin-bottom: 16px !important;
    }

    /* Crop target image and jCrop holder must not overflow. The
       holder gets an explicit pixel width from jCrop init — we
       allow it to scale down via max-width but jCrop's overlay
       won't follow, so this is mostly a safety net to prevent
       horizontal scroll. The real fix is the JS shim that
       reduces minCropSize so jCrop computes a fitting boxWidth
       in the first place. */
    body .avatar_crop_target_wrap,
    body #avatar-crop-target,
    body .jcrop-holder {
        max-width: 100% !important;
    }

    /* Preview: shrink to a mobile-friendly square. */
    body .ow_avatar_crop_result {
        max-width: 160px !important;
        margin: 0 auto 16px !important;
    }
    body .avatar_crop_preview_wrap {
        width:      120px !important;
        height:     120px !important;
        margin:     0 auto !important;
    }

    /* The drag handles on jCrop are 9px on desktop — too small
       for finger touch. Bump to 22px on mobile. */
    body .jcrop-handle {
        width:  22px !important;
        height: 22px !important;
        margin-left: -11px !important;
        margin-top:  -11px !important;
    }
    body .jcrop-vline,
    body .jcrop-hline {
        opacity: 0.6;
    }
}

/* Higher-priority overrides scoped via the JS-set body class.
   These take effect at any viewport once the JS detects a narrow
   width (catches resize-after-load), and won't interfere with
   desktop view at the same time the @media block is firing. */
body.elv-mobile-crop .ow_floatbox {
    width:     calc(100vw - 16px) !important;
    max-width: calc(100vw - 16px) !important;
    left:      8px !important;
    margin-left: 0 !important;
}
body.elv-mobile-crop .ow_avatar_crop_area,
body.elv-mobile-crop .ow_avatar_crop_result {
    float: none !important;
    width: 100% !important;
}

/* ── Legacy jCrop avatar preview (used in join flow) ────────────
 *
 * The base/components/avatar_change.html ships an inline
 * `.avatar_crop_preview_wrap img { width: 100%; }` which forces the
 * preview image to the wrap width while the JS in
 * /ow_static/plugins/base/js/avatar_change.js sets explicit dimensions
 * during init (line 69 forces 190x190 BEFORE the first onSelect fires
 * with proper aspect-preserving values). For ~tens of ms after the
 * floatbox opens — and sometimes longer if jCrop's setSelect doesn't
 * fire onSelect — the preview img is shown at minCropSize x
 * minCropSize, stretching the source image's aspect ratio.
 *
 * Fix: hide the preview until cropPreview() has run at least once. We
 * detect the "ready" state by checking for the inline `margin-left`
 * style that cropPreview() sets but the init code does not. After the
 * first onSelect, margin-left is set and the rule below stops applying.
 *
 * Scoped to the legacy avatar_change UI (used during join + admin),
 * does not affect the modern Cropper.js modal in the photo hub. */
#avatar-crop-preview {
    visibility: hidden;
}
#avatar-crop-preview[style*="margin-left"],
#avatar-crop-preview[style*="margin-top"] {
    visibility: visible;
}

/* ── Suppress legacy photo upload / avatar change UI ──────────── */

/* pcgallery "not enough photos" upload tip */
#pcgallery-add-photo-btn,
.pcgallery_add_photo_tip { display: none !important; }

/* Old jCrop "Change Avatar" floatbox trigger */
[data-outlet="avatar-change"] { display: none !important; }

/* Legacy upload links on profile pages */
.upload_photo_widget_btn,
.elv_change_avatar_btn,
a.ow_lbutton[href*="ajax-upload"],
a[href*="photo/ajax-upload"] { display: none !important; }

/* ── Profile page: left column hidden, table left-anchored to name ──
   Mirrors the rules in gallery.html {style} (inline, always fresh).
   body classes: base_profile_page (others) + ow_my_profile_page (own) */
body.base_profile_page .place_section.left_section,
body.ow_my_profile_page .place_section.left_section {
    display: none !important;
    width: 0 !important;
    float: none !important;
    overflow: hidden !important;
}
body.base_profile_page .place_section.right_section,
body.ow_my_profile_page .place_section.right_section {
    float: none !important;
    width: auto !important;
    margin-left: 294px !important;
    margin-right: 294px !important;
    clear: both !important;
}
@media (max-width: 768px) {
    body.base_profile_page .place_section.right_section,
    body.ow_my_profile_page .place_section.right_section {
        margin-left: 0 !important;
        margin-right: 0 !important;
        width: 100% !important;
        margin-top: 20px !important;
    }
}

/* ============================================================
   1. PHOTO HUB
   ============================================================ */

.ep-hub {
    max-width: 720px;
    margin: 0 auto;
    padding: 16px;
}

.ep-hub-header {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin-bottom: 8px;
}

.ep-hub-title {
    font-size: 1.5rem;
    font-weight: 700;
    margin: 0;
}

.ep-hub-count {
    color: #888;
    font-size: 0.9rem;
}

.ep-hub-hint {
    color: #666;
    font-size: 0.85rem;
    margin: 0 0 16px;
    padding: 8px 12px;
    background: #f5f5f5;
    border-radius: 8px;
}

/* ── 6-slot grid ───────────────────────────────────────────── */

.ep-slot-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
}

@media (max-width: 480px) {
    .ep-slot-grid {
        grid-template-columns: repeat(2, 1fr);
        gap: 6px;
    }
}

.ep-slot {
    position: relative;
    border-radius: 12px;
    overflow: hidden;
    background: #f0f0f0;
    /* 4:5 portrait aspect ratio */
    aspect-ratio: 4 / 5;
    cursor: pointer;
    transition: box-shadow 0.2s, opacity 0.2s;
}

.ep-slot:hover {
    box-shadow: 0 4px 16px rgba(0,0,0,0.18);
}

.ep-slot--main {
    grid-row: span 1;
    border: 2px solid #e83e6c;
}

.ep-slot--empty {
    border: 2px dashed #ccc;
    background: #fafafa;
    cursor: pointer;
}

.ep-slot--empty:hover {
    border-color: #e83e6c;
    background: #fff5f8;
}

.ep-slot--pending .ep-slot-img {
    opacity: 0.6;
    filter: grayscale(30%);
}

/* Drag state */
.ep-slot.ep-dragging {
    opacity: 0.4;
}

.ep-slot.ep-drag-over {
    outline: 2px dashed #e83e6c;
    outline-offset: 2px;
}

/* Touch-drag floating clone — follows the finger on mobile while
   reordering. The original slot fades via .ep-dragging above; the
   clone is visually "lifted" so the user feels the photo move. */
.ep-touch-drag-clone {
    border-radius: 12px;
    overflow: hidden;
    /* Disable text selection during drag so iOS doesn't pop the
       selection magnifier. */
    -webkit-user-select: none;
    user-select: none;
}

.ep-slot-inner {
    position: relative;
    width: 100%;
    height: 100%;
}

.ep-slot-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: filter 0.2s, opacity 0.25s;
}

/* Image is mid-load (waiting for first successful 200) — keep it invisible
   so we don't show the broken-img icon. The skeleton sibling shows in its
   place until `load` fires and removes both the loading class and the
   skeleton element. */
.ep-slot-img--loading {
    opacity: 0;
}

/* Final-failure state after retries exhausted — give a clear visual cue
   that the photo will appear after a refresh, rather than a broken icon. */
.ep-slot-img--failed {
    opacity: 0;
}

/* Shimmer skeleton while a slot's image is loading. Shown while
   .ep-slot-img--loading, removed by JS once the image's load event fires.
   Animation is reduced-motion aware. */
.ep-slot-img-skeleton {
    position: absolute;
    inset: 0;
    background: linear-gradient(
        90deg,
        #e8e8e8 0%,
        #f5f5f5 40%,
        #e8e8e8 80%
    );
    background-size: 200% 100%;
    animation: ep-slot-shimmer 1.4s ease-in-out infinite;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #999;
    font-size: 0.75rem;
    font-weight: 500;
    text-align: center;
    padding: 8px;
}

@media (prefers-reduced-motion: reduce) {
    .ep-slot-img-skeleton {
        animation: none;
        background: #ececec;
    }
}

.ep-slot-img-skeleton--failed {
    background: #fff5f8;
    color: #c0392b;
    animation: none;
}

@keyframes ep-slot-shimmer {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/* Pending-review cover for slots whose photo is awaiting moderation.
   Replaces the image entirely — moderation queue may not produce a
   public preview URL, so showing a friendly text overlay is more honest
   than showing a possibly-broken thumbnail. */
.ep-slot-pending-cover {
    position: absolute;
    inset: 0;
    background: linear-gradient(135deg, #fff7e6 0%, #ffe5b4 100%);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 12px;
    gap: 4px;
}

.ep-slot-pending-icon {
    font-size: 1.6rem;
    color: #b8860b;
    line-height: 1;
}

.ep-slot-pending-text {
    font-size: 0.8rem;
    font-weight: 700;
    color: #6b4d00;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.ep-slot-pending-sub {
    font-size: 0.7rem;
    color: #806036;
    line-height: 1.3;
}

/* ── Slot badges ───────────────────────────────────────────── */

.ep-slot-badge {
    position: absolute;
    top: 8px;
    left: 8px;
    font-size: 0.7rem;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: 100px;
    line-height: 1.6;
    pointer-events: none;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.ep-slot-badge--main {
    background: #e83e6c;
    color: #fff;
}

.ep-slot-badge--pending {
    background: rgba(255, 180, 0, 0.9);
    color: #333;
}

/*
 * When BOTH the "Profile photo" and "Pending review" badges sit on
 * the same slot (the user just changed their avatar and it's awaiting
 * moderation), the default top:8px / left:8px positioning makes them
 * stack on top of each other and the pending badge is invisible.
 * Push the pending badge below the main badge in that specific case.
 * 8px (top) + ~22px (badge height) + 6px gap = 36px.
 */
.ep-slot--main.ep-slot--pending .ep-slot-badge--pending {
    top: 36px;
}


/* ── Empty slot placeholder ────────────────────────────────── */

.ep-slot-placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
    gap: 6px;
    padding: 12px;
}

.ep-slot-plus {
    font-size: 2rem;
    line-height: 1;
    color: #ccc;
    transition: color 0.2s;
}

.ep-slot--empty:hover .ep-slot-plus {
    color: #e83e6c;
}

.ep-slot-add-label {
    font-size: 0.75rem;
    color: #aaa;
    text-align: center;
    font-weight: 500;
}

/* ── Slot action overlay ───────────────────────────────────── */

.ep-slot-actions {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    opacity: 0;
    transition: opacity 0.2s;
    padding: 8px;
}

.ep-slot:hover .ep-slot-actions,
.ep-slot:focus-within .ep-slot-actions,
.ep-slot.ep-actions-visible .ep-slot-actions {
    opacity: 1;
}

/* On touch devices, always show actions for filled slots */
@media (hover: none) {
    .ep-slot:not(.ep-slot--empty) .ep-slot-actions {
        opacity: 1;
        background: rgba(0, 0, 0, 0.35);
        justify-content: flex-end;
        padding-bottom: 10px;
    }
}

.ep-slot-action {
    background: rgba(255,255,255,0.9);
    border: none;
    border-radius: 100px;
    padding: 5px 12px;
    font-size: 0.75rem;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.15s, transform 0.1s;
    width: 90%;
    text-align: center;
}

.ep-slot-action:hover {
    background: #fff;
    transform: scale(1.03);
}

.ep-slot-action--setmain {
    color: var(--brand, #7C3AED);
}

.ep-slot-action--setmain:hover {
    color: #6B21A8;
}

/* Star glyph (★) is decorative; on narrow viewports there's not
   enough horizontal room for it AND the "Set as Profile Photo"
   label. Hide the .ep-star span on small screens so the full label
   fits cleanly. */
@media (max-width: 480px) {
    .ep-star {
        display: none;
    }
}

.ep-slot-action--delete {
    background: rgba(230, 50, 50, 0.32);
    color: #c0392b;
}

.ep-slot-action--delete:hover {
    background: rgba(230, 50, 50, 0.45);
}

/* Hidden file inputs */
.ep-file-input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    overflow: hidden;
    pointer-events: none;
}

/* ── Toast notifications ───────────────────────────────────── */
/*
 * Slides in from the top so it's hard to miss above the fold. Auto-dismisses
 * after 5s. Used for success confirmations only — failures get a persistent
 * modal that requires explicit dismissal (see .ep-error-modal).
 */
.ep-toast {
    position: fixed;
    top: 24px;
    left: 50%;
    transform: translateX(-50%) translateY(-100px);
    padding: 14px 22px;
    border-radius: 12px;
    font-size: 0.95rem;
    font-weight: 600;
    z-index: 9999;
    opacity: 0;
    transition: opacity 0.3s ease-out, transform 0.3s cubic-bezier(0.2, 0.9, 0.3, 1.2);
    max-width: min(90vw, 460px);
    text-align: center;
    pointer-events: none;
    box-shadow: 0 8px 24px rgba(0,0,0,0.18);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
}

.ep-toast.ep-toast--visible {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}

.ep-toast-icon {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.25);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 800;
    font-size: 0.85rem;
    line-height: 1;
    flex-shrink: 0;
}

.ep-toast--error   { background: #c0392b; color: #fff; }
.ep-toast--success { background: #27ae60; color: #fff; }

/* ── Persistent error modal ────────────────────────────────── */
/*
 * Shown for any user-visible failure (upload, crop, delete, set-main, etc).
 * Replaces the auto-dismissing error toast which hid before the user could
 * read the reason. Requires explicit dismissal so the user can read and
 * act on the error message.
 */
.ep-error-modal {
    position: fixed;
    inset: 0;
    z-index: 10000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
}

.ep-error-modal[hidden] {
    display: none !important;
}

.ep-error-modal-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(2px);
}

.ep-error-modal-panel {
    position: relative;
    background: #fff;
    border-radius: 16px;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
    width: min(420px, calc(100vw - 32px));
    max-height: calc(100vh - 32px);
    overflow-y: auto;
    padding: 28px 24px 20px;
    text-align: center;
    animation: ep-error-pop 0.18s ease-out;
}

@keyframes ep-error-pop {
    from { opacity: 0; transform: scale(0.92); }
    to   { opacity: 1; transform: scale(1); }
}

.ep-error-modal-icon {
    width: 56px;
    height: 56px;
    margin: 0 auto 14px;
    border-radius: 50%;
    background: #fee2e2;
    color: #c0392b;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.9rem;
    font-weight: 800;
    line-height: 1;
}

.ep-error-modal-title {
    margin: 0 0 10px;
    font-size: 1.15rem;
    font-weight: 700;
    color: #1f1f1f;
}

.ep-error-modal-message {
    margin: 0 0 22px;
    font-size: 0.95rem;
    line-height: 1.5;
    color: #444;
    white-space: pre-line;
    word-wrap: break-word;
}

.ep-error-modal-btn {
    display: inline-block;
    min-width: 140px;
    padding: 11px 24px;
    background: #e83e6c;
    color: #fff;
    border: none;
    border-radius: 100px;
    font-size: 0.95rem;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s, transform 0.1s;
}

.ep-error-modal-btn:hover  { background: #d23560; }
.ep-error-modal-btn:active { transform: scale(0.97); }
.ep-error-modal-btn:focus  { outline: 3px solid rgba(232, 62, 108, 0.35); outline-offset: 2px; }

@media (prefers-color-scheme: dark) {
    .ep-error-modal-panel { background: #1e1e1e; }
    .ep-error-modal-title { color: #fff; }
    .ep-error-modal-message { color: #ccc; }
    .ep-error-modal-icon { background: rgba(192, 57, 43, 0.25); color: #ff8a80; }
}

/* ============================================================
   2. CROP MODAL
   ============================================================ */

.ep-crop-modal {
    position: fixed;
    inset: 0;
    z-index: 9000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
}

.ep-crop-modal[hidden] {
    display: none !important;
}

.ep-crop-modal-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.75);
    backdrop-filter: blur(4px);
}

.ep-crop-modal-panel {
    position: relative;
    background: #fff;
    border-radius: 16px;
    overflow: hidden;
    width: min(480px, calc(100vw - 32px));
    max-height: min(90vh, 700px);
    display: flex;
    flex-direction: column;
    box-shadow: 0 20px 60px rgba(0,0,0,0.4);
}

.ep-crop-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 20px;
    border-bottom: 1px solid #eee;
}

.ep-crop-modal-header h2 {
    margin: 0;
    font-size: 1.1rem;
    font-weight: 700;
}

.ep-crop-modal-close {
    background: none;
    border: none;
    font-size: 1.5rem;
    cursor: pointer;
    color: #666;
    padding: 0;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    transition: background 0.15s;
}

.ep-crop-modal-close:hover {
    background: #f0f0f0;
}

.ep-crop-modal-body {
    flex: 1;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

/* Cropper.js container — fixed aspect 1:1 for avatar */
.ep-crop-container {
    position: relative;
    flex: 1;
    min-height: 320px;
    overflow: hidden;
    background: #111;
    max-height: 400px;
}

.ep-crop-container img {
    /* Cropper.js will manage display */
    display: block;
    max-width: 100%;
}

/* Inline failure state shown after preview-load retries exhaust. Replaces
   the cropper container body and stays put until the user clicks
   Discard/Cancel — explicit and readable, vs a tiny broken-img icon. */
.ep-crop-failure {
    position: absolute;
    inset: 0;
    background: #1a1a1a;
    color: #fff;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 24px;
    gap: 10px;
}

.ep-crop-failure-icon {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: rgba(192, 57, 43, 0.25);
    color: #ff8a80;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.5rem;
    font-weight: 800;
    line-height: 1;
}

.ep-crop-failure-title {
    font-size: 1rem;
    font-weight: 700;
}

.ep-crop-failure-detail {
    font-size: 0.85rem;
    color: #ccc;
    max-width: 360px;
    line-height: 1.4;
}

.ep-crop-hint {
    padding: 8px 16px;
    margin: 0;
    font-size: 0.8rem;
    color: #888;
    text-align: center;
    border-top: 1px solid #eee;
}

.ep-crop-modal-footer {
    display: flex;
    gap: 8px;
    padding: 12px 16px;
    border-top: 1px solid #eee;
}

.ep-crop-btn-cancel,
.ep-crop-btn-apply {
    flex: 1;
    padding: 12px;
    border: none;
    border-radius: 10px;
    font-size: 0.95rem;
    font-weight: 600;
    cursor: pointer;
    transition: opacity 0.15s, transform 0.1s;
}

.ep-crop-btn-cancel {
    background: #f0f0f0;
    color: #333;
}

.ep-crop-btn-apply {
    background: #e83e6c;
    color: #fff;
}

.ep-crop-btn-apply:hover,
.ep-crop-btn-cancel:hover {
    opacity: 0.9;
}

.ep-crop-btn-apply:active,
.ep-crop-btn-cancel:active {
    transform: scale(0.98);
}

/* Loading overlay inside crop modal. Two presentations:
   - default (.ep-crop-loading)        → covers the full panel during a
     server-side save; disables footer buttons by virtue of overlaying them.
   - .ep-crop-loading--image           → covers only the cropper image area
     during image-fetch retries; leaves the Discard/Cancel button clickable
     so the user is never trapped waiting on a stuck preview load. */
.ep-crop-loading {
    position: absolute;
    inset: 0;
    background: rgba(20, 20, 20, 0.78);
    backdrop-filter: blur(2px);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    font-weight: 600;
    color: #fff;
    z-index: 5;
    font-size: 0.9rem;
    letter-spacing: 0.02em;
}

.ep-crop-loading--image {
    /* Pull the overlay off the footer so the Discard button stays
       interactive. Cropper container has min-height: 320px and the image
       area sits between header (~50px) and footer (~64px).
       Do NOT add `inset: auto` here — `inset` is shorthand and would
       wipe out top/bottom/left/right back to auto, collapsing the
       overlay to a content-sized box in the top-left. */
    top:    52px;
    bottom: 60px;
    left:   0;
    right:  0;
}

.ep-crop-loading .ep-spinner {
    border-color: rgba(255, 255, 255, 0.25);
    border-top-color: #ff75a3;
}

.ep-crop-loading[hidden] { display: none !important; }

/* ── Spinner ───────────────────────────────────────────────── */
.ep-spinner {
    width: 40px;
    height: 40px;
    border: 3px solid #e0e0e0;
    border-top-color: #e83e6c;
    border-radius: 50%;
    animation: ep-spin 0.7s linear infinite;
}

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

/* ============================================================
   3. PHOTO VIEWER (swipe gallery, shown on profile pages)
   ============================================================ */

.ep-viewer {
    position: fixed;
    inset: 0;
    z-index: 8500;
    background: rgba(0,0,0,0.92);
    display: flex;
    flex-direction: column;
}

.ep-viewer[hidden] { display: none !important; }

.ep-viewer-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 16px;
    color: #fff;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1;
    background: linear-gradient(rgba(0,0,0,0.6), transparent);
}

.ep-viewer-close {
    background: none;
    border: none;
    color: #fff;
    font-size: 1.6rem;
    cursor: pointer;
    line-height: 1;
    padding: 4px;
    opacity: 0.85;
    transition: opacity 0.15s;
}

.ep-viewer-close:hover { opacity: 1; }

.ep-viewer-counter {
    font-size: 0.85rem;
    font-weight: 600;
    opacity: 0.85;
}

/* Scroll-snap slide track */
.ep-viewer-track {
    flex: 1;
    display: flex;
    overflow-x: scroll;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none; /* Firefox */
}

.ep-viewer-track::-webkit-scrollbar { display: none; }

.ep-viewer-slide {
    flex: 0 0 100%;
    scroll-snap-align: start;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 60px 16px 60px;
    min-height: 0;
}

.ep-viewer-slide img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    border-radius: 8px;
    user-select: none;
    -webkit-user-drag: none;
}

/* Desktop prev/next arrows */
.ep-viewer-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    background: rgba(255,255,255,0.15);
    border: none;
    color: #fff;
    font-size: 1.5rem;
    width: 48px;
    height: 48px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    z-index: 2;
    transition: background 0.15s;
}

.ep-viewer-arrow:hover { background: rgba(255,255,255,0.3); }
.ep-viewer-arrow--prev { left: 12px; }
.ep-viewer-arrow--next { right: 12px; }

@media (max-width: 480px) {
    .ep-viewer-arrow { display: none; }
}

/* Dot indicators */
.ep-viewer-dots {
    display: flex;
    justify-content: center;
    gap: 6px;
    padding: 12px;
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    background: linear-gradient(transparent, rgba(0,0,0,0.5));
}

.ep-viewer-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgba(255,255,255,0.45);
    transition: background 0.2s, transform 0.2s;
}

.ep-viewer-dot.ep-dot--active {
    background: #fff;
    transform: scale(1.3);
}

/* ============================================================
   4. PROFILE PHOTO GALLERY (inline, on public profiles)
   ============================================================ */

.ep-profile-gallery {
    position: relative;
    border-radius: 12px;
    overflow: hidden;
    background: #111;
    /* 4:5 portrait */
    aspect-ratio: 4 / 5;
    user-select: none;
}

.ep-profile-gallery-track {
    display: flex;
    width: 100%;
    height: 100%;
    overflow-x: scroll;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}

.ep-profile-gallery-track::-webkit-scrollbar { display: none; }

.ep-profile-gallery-slide {
    flex: 0 0 100%;
    scroll-snap-align: start;
    position: relative;
    min-height: 0;
}

.ep-profile-gallery-slide img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    cursor: pointer;
}

/* Desktop arrows on profile gallery */
.ep-profile-gallery-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    background: rgba(0,0,0,0.35);
    border: none;
    color: #fff;
    font-size: 1.2rem;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    z-index: 2;
    opacity: 0;
    transition: opacity 0.2s, background 0.15s;
}

.ep-profile-gallery:hover .ep-profile-gallery-arrow { opacity: 1; }
.ep-profile-gallery-arrow:hover { background: rgba(0,0,0,0.6); }
.ep-profile-gallery-arrow--prev { left: 8px; }
.ep-profile-gallery-arrow--next { right: 8px; }

@media (max-width: 480px) {
    .ep-profile-gallery-arrow { display: none; }
}

/* Profile gallery dots */
.ep-profile-gallery-dots {
    position: absolute;
    bottom: 10px;
    left: 0;
    right: 0;
    display: flex;
    justify-content: center;
    gap: 5px;
    pointer-events: none;
}

.ep-profile-gallery-dot {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: rgba(255,255,255,0.5);
    transition: background 0.2s, transform 0.2s;
}

.ep-profile-gallery-dot.ep-dot--active {
    background: #fff;
    transform: scale(1.4);
}

/* Open viewer on tap/click */
.ep-profile-gallery-slide img {
    cursor: zoom-in;
}

/* ============================================================
   5. DISCOVERY CARD PHOTO CAROUSEL
   ============================================================ */

.discovery-card-thumb {
    position: relative;
    display: block;
    overflow: hidden;
}

/* Replace the static img with a scroll-snap track */
.ep-card-carousel {
    position: relative;
    width: 100%;
    overflow: hidden;
}

.ep-card-track {
    display: flex;
    overflow-x: scroll;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}

.ep-card-track::-webkit-scrollbar { display: none; }

.ep-card-slide {
    flex: 0 0 100%;
    scroll-snap-align: start;
    position: relative;
}

.ep-card-slide img {
    width: 100%;
    display: block;
    object-fit: cover;
    pointer-events: none;
    aspect-ratio: 4 / 5;
}

/* Dot bar at bottom of card */
.ep-card-dots {
    position: absolute;
    bottom: 8px;
    left: 0;
    right: 0;
    display: flex;
    justify-content: center;
    gap: 4px;
    pointer-events: none;
    z-index: 2;
}

.ep-card-dot {
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: rgba(255,255,255,0.5);
    transition: background 0.2s, transform 0.2s;
}

.ep-card-dot.ep-dot--active {
    background: #fff;
    transform: scale(1.4);
}

/* Desktop arrow hint strips on card edges */
.ep-card-arrow {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 32px;
    background: rgba(0,0,0,0);
    border: none;
    cursor: pointer;
    z-index: 3;
    display: flex;
    align-items: center;
    justify-content: center;
    color: rgba(255,255,255,0.8);
    font-size: 1rem;
    opacity: 0;
    transition: opacity 0.2s, background 0.2s;
}

.ep-card-carousel:hover .ep-card-arrow { opacity: 1; }

.ep-card-arrow:hover {
    background: rgba(0,0,0,0.25);
}

.ep-card-arrow--prev { left: 0; }
.ep-card-arrow--next { right: 0; }

/* Prevent card carousel scroll from triggering like/pass swipe */
.ep-card-track {
    touch-action: pan-x;
}

/* ============================================================
   6. INLINE AVATAR REPLACEMENT on profile / settings pages
   ============================================================ */

/* "Manage Photos" button — subdued, not the primary CTA */
.ep-avatar-change-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: transparent;
    color: var(--text-muted, #6B7280);
    border: 1px solid var(--border, #E5E7EB);
    border-radius: 100px;
    padding: 6px 14px;
    font-size: 0.8rem;
    font-weight: 500;
    cursor: pointer;
    margin-top: 8px;
    transition: color 0.15s, border-color 0.15s;
    text-decoration: none;
}

.ep-avatar-change-btn:hover {
    color: var(--brand, #7C3AED);
    border-color: var(--brand, #7C3AED);
}

/* Overlay badge on avatar showing pending/rejected state */
.ep-avatar-status-overlay {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    background: rgba(0,0,0,0.65);
    color: #fff;
    text-align: center;
    font-size: 0.7rem;
    font-weight: 600;
    padding: 4px 6px;
    border-bottom-left-radius: inherit;
    border-bottom-right-radius: inherit;
}

/* ============================================================
   7. UPLOAD PROGRESS
   ============================================================ */

.ep-upload-progress {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    height: 3px;
    background: #eee;
    z-index: 9500;
    overflow: hidden;
    transform: scaleY(0);
    transform-origin: bottom;
    transition: transform 0.2s;
}

.ep-upload-progress.ep-progress--active {
    transform: scaleY(1);
}

.ep-upload-progress-bar {
    height: 100%;
    background: #e83e6c;
    transition: width 0.3s ease;
    width: 0%;
}

/* ============================================================
   8. DARK MODE SUPPORT
   ============================================================ */
@media (prefers-color-scheme: dark) {
    .ep-hub-hint { background: #2a2a2a; color: #aaa; }
    .ep-slot--empty { background: #1e1e1e; border-color: #444; }
    .ep-slot { background: #1e1e1e; }
    .ep-crop-modal-panel { background: #1e1e1e; }
    .ep-crop-modal-header { border-bottom-color: #333; }
    .ep-crop-modal-header h2 { color: #fff; }
    .ep-crop-hint { color: #888; border-top-color: #333; }
    .ep-crop-modal-footer { border-top-color: #333; }
    .ep-crop-btn-cancel { background: #333; color: #ddd; }
    .ep-slot-action { background: rgba(255,255,255,0.15); color: #fff; }
    .ep-slot-action--delete { background: rgba(230,50,50,0.25); color: #ff6b6b; }
}
