/**
 * Noka Elements - Frontend styles.
 * Version: 3.16.0
 *
 * Covers all seven widgets:
 *   - Categories Grid (.noka-cat-*)
 *   - Slideshow (.noka-ss-*)
 *   - Carousel (.noka-carousel-*)
 *   - Basic Gallery (.noka-gallery-*)
 *   - Showcase (.noka-showcase-*)
 *   - Image Effects (.noka-fx-*)
 *   - Lightbox (.noka-lightbox)
 *
 * Conventions:
 *   - RTL handled via [dir="rtl"] selectors at the bottom of each section.
 *   - Focus rings: rely on the global `.noka-focus-ring` rule near the bottom
 *     of this file. Do not redefine focus styles per-widget.
 *   - prefers-reduced-motion: handled globally at the very bottom of this file.
 *   - !important: every occurrence is annotated above the line with the reason.
 *
 * IMPORTANT: bump the Version comment above on every release to match the
 * plugin header in noka-elements.php. The build process does not do this
 * automatically.
 */

/* ============================================================
 *  Helpers
 * ============================================================ */
.noka-img-cover {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* ============================================================
 *  Categories Grid
 * ============================================================ */
.noka-cat-wrapper {
    width: 100%;
}

.noka-cat-grid {
    display: grid;
    grid-template-columns: repeat(var(--cols-d, 3), 1fr);
}

@media (max-width: 1024px) {
    .noka-cat-grid {
        grid-template-columns: repeat(var(--cols-t, 2), 1fr);
    }
}

@media (max-width: 767px) {
    .noka-cat-grid {
        grid-template-columns: repeat(var(--cols-m, 1), 1fr);
    }
}

.noka-cat-item {
    position: relative;
    display: block;
    overflow: hidden;
    text-decoration: none;
    background: #1a1a1a;
}

.noka-cat-image {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.6s ease;
    display: block;
}

.noka-cat-zoom:hover .noka-cat-image {
    transform: scale(1.05);
}

.noka-cat-placeholder {
    background: #2a2a2a;
}

.noka-cat-overlay {
    position: absolute;
    inset: 0;
    transition: background-color 0.4s ease;
    z-index: 1;
}

.noka-cat-content {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 24px;
    z-index: 2;
    text-align: center;
    pointer-events: none;
}

.noka-cat-title {
    margin: 0 0 4px 0;
}

.noka-cat-count {
    display: block;
}

/* ============================================================
 *  Archive page (subcategories)
 * ============================================================ */
.noka-archive-subcategories {
    background: #0a0a0a;
    color: #fff;
    padding: 60px 20px;
    min-height: 60vh;
}

.noka-archive-container {
    max-width: 1280px;
    margin: 0 auto;
}

.noka-archive-breadcrumb {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
    margin-bottom: 32px;
    font-size: 13px;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.5);
}

.noka-archive-breadcrumb a {
    color: rgba(255, 255, 255, 0.7);
    text-decoration: none;
    transition: color 0.2s ease;
}

.noka-archive-breadcrumb a:hover {
    color: #fff;
}

.noka-archive-breadcrumb-sep {
    color: rgba(255, 255, 255, 0.3);
}

.noka-archive-breadcrumb-current {
    color: #fff;
}

.noka-archive-header {
    text-align: center;
    margin-bottom: 48px;
}

.noka-archive-title {
    font-size: clamp(2rem, 4vw, 3.5rem);
    font-weight: 400;
    letter-spacing: 2px;
    text-transform: uppercase;
    margin: 0 0 16px;
    color: #fff;
}

.noka-archive-description {
    max-width: 640px;
    margin: 0 auto;
    color: rgba(255,255,255,0.7);
    line-height: 1.6;
}

@media (max-width: 767px) {
    .noka-archive-subcategories {
        padding: 32px 16px;
    }
    .noka-archive-header {
        margin-bottom: 24px;
    }
}

/* ============================================================
 *  Slideshow
 *
 *  Layout strategy:
 *  - Three "fit modes" controlled by data-fit-mode on the wrapper:
 *    natural (default), cover, contain.
 *  - Embedded view: each fit mode has its own self-contained CSS rules
 *    that don't fight each other. The wrapper acts as a scope so a
 *    rule for one mode never affects another.
 *  - Fullscreen view: JS removes data-fit-mode entirely and adds
 *    .is-fullscreen class. The fit-mode rules don't apply, the
 *    fullscreen rules apply cleanly.
 * ============================================================ */

.noka-ss-wrapper {
    position: relative;
    outline: none;
}

.noka-ss-wrapper:focus-visible {
    outline: 2px solid rgba(255, 255, 255, 0.4);
    outline-offset: 4px;
}

/* ------------------------------------------------------------
 * COVER mode
 *   - main has fixed height (set by Elementor selector)
 *   - slides absolutely positioned inside main
 *   - active slide opacity 1, others 0
 *   - image fills container with object-fit: cover (may crop)
 * ------------------------------------------------------------ */
.noka-ss-wrapper[data-fit-mode="cover"] .noka-ss-stage {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
}

.noka-ss-wrapper[data-fit-mode="cover"] .noka-ss-main {
    position: relative;
    flex: 1 1 auto;
    width: 100%;
    /* Default height; overridden per-widget by Elementor's injected CSS. */
    height: 600px;
    overflow: hidden;
    background: #0a0a0a;
}

.noka-ss-wrapper[data-fit-mode="cover"] .noka-ss-slide {
    position: absolute;
    inset: 0;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.5s ease;
    display: flex;
    align-items: center;
    justify-content: center;
}

.noka-ss-wrapper[data-fit-mode="cover"] .noka-ss-slide.is-active {
    opacity: 1;
    visibility: visible;
}

.noka-ss-wrapper[data-fit-mode="cover"] .noka-ss-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* ------------------------------------------------------------
 * CONTAIN mode
 *   - same structure as cover, but image uses object-fit: contain
 *   - may show letterbox bars on the sides
 * ------------------------------------------------------------ */
.noka-ss-wrapper[data-fit-mode="contain"] .noka-ss-stage {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
}

.noka-ss-wrapper[data-fit-mode="contain"] .noka-ss-main {
    position: relative;
    flex: 1 1 auto;
    width: 100%;
    /* Default height; overridden per-widget by Elementor's injected CSS. */
    height: 600px;
    overflow: hidden;
    background: #0a0a0a;
}

.noka-ss-wrapper[data-fit-mode="contain"] .noka-ss-slide {
    position: absolute;
    inset: 0;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.5s ease;
    display: flex;
    align-items: center;
    justify-content: center;
}

.noka-ss-wrapper[data-fit-mode="contain"] .noka-ss-slide.is-active {
    opacity: 1;
    visibility: visible;
}

.noka-ss-wrapper[data-fit-mode="contain"] .noka-ss-img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

/* ------------------------------------------------------------
 * NATURAL mode
 *   - main has NO fixed height; takes height from active slide image
 *   - slides flow as block, only active is shown via display
 *   - image keeps proportions, capped by max-height (per widget)
 *   - never crops
 *
 * Stage is block-level so main can size to its content; arrows
 * remain absolutely positioned over the stage and stay centered.
 * ------------------------------------------------------------ */
.noka-ss-wrapper[data-fit-mode="natural"] .noka-ss-stage {
    position: relative;
    display: block;
}

.noka-ss-wrapper[data-fit-mode="natural"] .noka-ss-main {
    position: relative;
    display: block;
    width: 100%;
    background: #0a0a0a;
    overflow: hidden;
}

.noka-ss-wrapper[data-fit-mode="natural"] .noka-ss-slide {
    position: relative;
    inset: auto;
    opacity: 1;
    visibility: visible;
    display: none;
    align-items: center;
    justify-content: center;
}

.noka-ss-wrapper[data-fit-mode="natural"] .noka-ss-slide.is-active {
    display: flex;
}

.noka-ss-wrapper[data-fit-mode="natural"] .noka-ss-img {
    width: auto;
    max-width: 100%;
    height: auto;
    /* Default max-height; overridden per-widget by Elementor's injected CSS. */
    max-height: 600px;
    object-fit: unset;
    display: block;
    margin: 0 auto;
}

/* ------------------------------------------------------------
 * Arrows
 * ------------------------------------------------------------ */
.noka-ss-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    background: transparent;
    border: 0;
    color: #fff;
    cursor: pointer;
    padding: 12px;
    z-index: 10;
    transition: color 0.2s ease, transform 0.2s ease;
    display: flex;
    align-items: center;
    justify-content: center;
}

.noka-ss-arrow:hover {
    transform: translateY(-50%) scale(1.1);
}

.noka-ss-prev { left: 16px; }
.noka-ss-next { right: 16px; }

[dir="rtl"] .noka-ss-prev { right: 16px; left: auto; }
[dir="rtl"] .noka-ss-next { left: 16px; right: auto; }
[dir="rtl"] .noka-ss-prev svg,
[dir="rtl"] .noka-ss-next svg { transform: scaleX(-1); }

.noka-ss-arrow svg {
    width: 32px;
    height: 32px;
}

/* ------------------------------------------------------------
 * Counter (e.g. "5 / 30")
 * ------------------------------------------------------------ */
.noka-ss-counter {
    position: absolute;
    bottom: 16px;
    left: 16px;
    color: rgba(255, 255, 255, 0.7);
    font-size: 13px;
    letter-spacing: 1px;
    z-index: 5;
    background: rgba(0, 0, 0, 0.5);
    padding: 4px 10px;
    border-radius: 2px;
    display: flex;
    gap: 4px;
    align-items: center;
}

[dir="rtl"] .noka-ss-counter {
    right: 16px;
    left: auto;
}

/* ------------------------------------------------------------
 * Fullscreen toggle button
 * ------------------------------------------------------------ */
.noka-ss-fullscreen {
    position: absolute;
    bottom: 16px;
    right: 16px;
    background: rgba(0, 0, 0, 0.5);
    border: 0;
    color: rgba(255, 255, 255, 0.7);
    cursor: pointer;
    padding: 6px;
    border-radius: 2px;
    z-index: 5;
    transition: color 0.2s ease, background 0.2s ease;
    display: flex;
}

.noka-ss-fullscreen:hover {
    color: #fff;
    background: rgba(0, 0, 0, 0.7);
}

[dir="rtl"] .noka-ss-fullscreen {
    left: 16px;
    right: auto;
}

.noka-ss-fullscreen svg {
    width: 18px;
    height: 18px;
}

/* ------------------------------------------------------------
 * Caption strip
 * ------------------------------------------------------------ */
.noka-ss-caption-wrap {
    position: relative;
    min-height: 1.5em;
    text-align: center;
    margin-top: 16px;
}

.noka-ss-caption {
    position: absolute;
    inset: 0;
    margin: 0;
    opacity: 0;
    transition: opacity 0.3s ease;
    font-size: 14px;
    line-height: 1.5;
}

.noka-ss-caption.is-active {
    opacity: 1;
    position: relative;
}

/* ------------------------------------------------------------
 * Thumbnails strip
 * ------------------------------------------------------------ */
.noka-ss-thumbs {
    display: flex;
    gap: 8px;
    margin-top: 24px;
    overflow-x: auto;
    scroll-behavior: smooth;
    padding: 4px 0;
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
}

.noka-ss-thumbs::-webkit-scrollbar {
    height: 4px;
}

.noka-ss-thumbs::-webkit-scrollbar-track {
    background: transparent;
}

.noka-ss-thumbs::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.2);
    border-radius: 2px;
}

.noka-ss-thumb {
    flex: 0 0 auto;
    background: transparent;
    border: 2px solid transparent;
    padding: 0;
    cursor: pointer;
    overflow: hidden;
    opacity: var(--thumb-inactive-opacity, 0.6);
    transition: opacity 0.2s ease, border-color 0.2s ease;
    aspect-ratio: 4 / 3;
    height: 80px;
}

.noka-ss-thumb:hover {
    opacity: 0.85;
}

.noka-ss-thumb.is-active {
    opacity: 1;
    border-color: #fff;
}

.noka-ss-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* ============================================================
 *  Slideshow - Fullscreen mode
 *
 *  When the wrapper is in fullscreen, JS:
 *    1. Removes data-fit-mode attribute (so fit-mode rules don't apply)
 *    2. Adds .is-fullscreen class
 *    3. Saves original fit-mode in data-prev-fit-mode for restoration
 *
 *  The CSS below applies in BOTH:
 *    - .noka-ss-wrapper.is-fullscreen (JS-managed, reliable)
 *    - .noka-ss-wrapper:fullscreen (CSS pseudo-class, when supported)
 * ============================================================ */
.noka-ss-wrapper.is-fullscreen,
.noka-ss-wrapper:fullscreen {
    background: #000;
    padding: 0;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    width: 100vw;
    height: 100vh;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 99999;
}

.noka-ss-wrapper.is-fullscreen .noka-ss-stage,
.noka-ss-wrapper:fullscreen .noka-ss-stage {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
    align-items: center;
    justify-content: center;
    width: 100%;
    overflow: hidden;
    position: relative;
}

.noka-ss-wrapper.is-fullscreen .noka-ss-main,
.noka-ss-wrapper:fullscreen .noka-ss-main {
    display: block;
    flex: 1 1 auto;
    width: 100%;
    height: 100%;
    max-height: 100%;
    min-height: 0;
    position: relative;
    overflow: hidden;
    background: #000;
}

.noka-ss-wrapper.is-fullscreen .noka-ss-slide,
.noka-ss-wrapper:fullscreen .noka-ss-slide {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.5s ease;
}

.noka-ss-wrapper.is-fullscreen .noka-ss-slide.is-active,
.noka-ss-wrapper:fullscreen .noka-ss-slide.is-active {
    opacity: 1;
    visibility: visible;
}

.noka-ss-wrapper.is-fullscreen .noka-ss-img,
.noka-ss-wrapper:fullscreen .noka-ss-img {
    width: auto;
    height: auto;
    max-width: 100%;
    max-height: 100%;
    min-height: 0;
    object-fit: contain;
    display: block;
    margin: auto;
}

.noka-ss-wrapper.is-fullscreen .noka-ss-thumbs,
.noka-ss-wrapper:fullscreen .noka-ss-thumbs {
    flex: 0 0 auto;
    margin: 12px 0;
    padding: 0 16px;
}

.noka-ss-wrapper.is-fullscreen .noka-ss-caption-wrap,
.noka-ss-wrapper:fullscreen .noka-ss-caption-wrap {
    flex: 0 0 auto;
    padding: 0 16px;
}

@media (max-width: 767px) {
    .noka-ss-arrow { padding: 8px; }
    .noka-ss-arrow svg { width: 24px; height: 24px; }
    .noka-ss-thumb { height: 60px; }
    .noka-ss-thumbs { gap: 4px; }
}

/* ============================================================
 *  Carousel
 * ============================================================ */
.noka-carousel-wrapper {
    position: relative;
    overflow: hidden;
}

.noka-carousel-viewport {
    overflow: hidden;
    width: 100%;
}

.noka-carousel-track {
    display: flex;
    will-change: transform;
}

.noka-carousel-slide {
    position: relative;
    overflow: hidden;
    background: #1a1a1a;
}

.noka-carousel-slide.noka-carousel-clickable {
    cursor: zoom-in;
}

.noka-carousel-slide img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* ---- Fade effect ---- */
/*
 * In fade mode, the track is the stage and slides stack on top of each other.
 * Only the active slide is visible (opacity 1), others are hidden (opacity 0).
 * No translateX is applied; transitions are opacity only.
 */
.noka-carousel-wrapper[data-effect="fade"] .noka-carousel-track {
    display: block;
    position: relative;
    width: 100%;
}

.noka-carousel-wrapper[data-effect="fade"] .noka-carousel-slide {
    position: absolute;
    inset: 0;
    width: 100%;
    /* !important: Slick adds its own inline `flex: 0 0 auto` on every slide;
     * fade mode needs all slides to overlap, so we override hard. */
    flex: none !important;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.6s ease, visibility 0s linear 0.6s;
    z-index: 1;
}

.noka-carousel-wrapper[data-effect="fade"] .noka-carousel-slide.is-active {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.6s ease;
    z-index: 2;
}

/* First slide must be visible immediately on render (before JS init). */
.noka-carousel-wrapper[data-effect="fade"] .noka-carousel-slide:first-child {
    position: relative;
}

.noka-carousel-caption {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 12px 16px;
    color: #fff;
    background: rgba(0,0,0,0.5);
    font-size: 14px;
}

.noka-carousel-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    border: 0;
    border-radius: 50%;
    background: rgba(0,0,0,0.5);
    color: #fff;
    cursor: pointer;
    z-index: 5;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.2s ease, transform 0.2s ease;
}

.noka-carousel-arrow:hover {
    background: rgba(0,0,0,0.75);
}

.noka-carousel-arrow svg {
    width: 22px;
    height: 22px;
}

.noka-carousel-prev { left: 12px; }
.noka-carousel-next { right: 12px; }

[dir="rtl"] .noka-carousel-prev { right: 12px; left: auto; }
[dir="rtl"] .noka-carousel-next { left: 12px; right: auto; }
[dir="rtl"] .noka-carousel-prev svg,
[dir="rtl"] .noka-carousel-next svg { transform: scaleX(-1); }

.noka-carousel-dots {
    display: flex;
    justify-content: center;
    gap: 8px;
    margin-top: 16px;
}

.noka-carousel-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 0;
    background: rgba(0,0,0,0.3);
    cursor: pointer;
    padding: 0;
    transition: background 0.2s ease, transform 0.2s ease;
}

.noka-carousel-dot:hover {
    transform: scale(1.2);
}

.noka-carousel-dot.is-active {
    background: #000;
}

/* ============================================================
 *  Basic Gallery
 * ============================================================ */
.noka-gallery-wrapper {
    width: 100%;
}

.noka-gallery-grid {
    display: grid;
    grid-template-columns: repeat(var(--cols-d, 4), 1fr);
}

@media (max-width: 1024px) {
    .noka-gallery-grid {
        grid-template-columns: repeat(var(--cols-t, 3), 1fr);
    }
}

@media (max-width: 767px) {
    .noka-gallery-grid {
        grid-template-columns: repeat(var(--cols-m, 2), 1fr);
    }
}

.noka-gallery-item {
    position: relative;
    overflow: hidden;
    background: #f0f0f0;
    text-decoration: none;
    display: block;
    aspect-ratio: 1 / 1;
}

.noka-gallery-item.noka-gallery-clickable {
    cursor: zoom-in;
}

.noka-gallery-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform 0.4s ease, opacity 0.3s ease;
}

/* Hover effects */
.noka-hover-zoom:hover img {
    transform: scale(1.06);
}

.noka-hover-fade:hover img {
    opacity: 0.7;
}

.noka-hover-overlay::after {
    content: '';
    position: absolute;
    inset: 0;
    background: rgba(0,0,0,0);
    transition: background 0.3s ease;
    pointer-events: none;
}

.noka-hover-overlay:hover::after {
    background: rgba(0,0,0,0.4);
}

.noka-gallery-caption {
    position: absolute;
    inset: auto 0 0 0;
    padding: 12px;
    color: #fff;
    background: rgba(0,0,0,0.6);
    font-size: 13px;
    transform: translateY(100%);
    transition: transform 0.3s ease;
    pointer-events: none;
}

.noka-gallery-item:hover .noka-gallery-caption {
    transform: translateY(0);
}

/* ============================================================
 *  Lightbox (shared by Carousel and Gallery)
 * ============================================================ */
body.noka-lightbox-open {
    overflow: hidden;
}

.noka-lightbox {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.92);
    z-index: 99999;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 40px;
    box-sizing: border-box;
}

.noka-lightbox-img {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    display: block;
    object-fit: contain;
}

.noka-lightbox-close,
.noka-lightbox-prev,
.noka-lightbox-next {
    position: absolute;
    background: rgba(255,255,255,0.1);
    border: 0;
    color: #fff;
    cursor: pointer;
    transition: background 0.2s ease;
    display: flex;
    align-items: center;
    justify-content: center;
}

.noka-lightbox-close {
    top: 20px;
    right: 20px;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    font-size: 28px;
    line-height: 1;
}

.noka-lightbox-close:hover,
.noka-lightbox-prev:hover,
.noka-lightbox-next:hover {
    background: rgba(255,255,255,0.2);
}

.noka-lightbox-prev,
.noka-lightbox-next {
    top: 50%;
    transform: translateY(-50%);
    width: 56px;
    height: 56px;
    border-radius: 50%;
}

.noka-lightbox-prev { left: 20px; }
.noka-lightbox-next { right: 20px; }

[dir="rtl"] .noka-lightbox-prev { right: 20px; left: auto; }
[dir="rtl"] .noka-lightbox-next { left: 20px; right: auto; }
[dir="rtl"] .noka-lightbox-prev svg,
[dir="rtl"] .noka-lightbox-next svg { transform: scaleX(-1); }

.noka-lightbox-prev svg,
.noka-lightbox-next svg {
    width: 24px;
    height: 24px;
}

.noka-lightbox-counter {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    color: rgba(255,255,255,0.8);
    font-size: 14px;
    background: rgba(0,0,0,0.4);
    padding: 6px 14px;
    border-radius: 20px;
}

/* ============================================================
 *  Showcase widget
 *
 *  Manual cards grid, generic. Each card can have:
 *    - Image, title, description, icon, CTA button
 *    - Wide flag (spans 2 columns in grid mode)
 *    - Content position: overlay (top/center/bottom) or outside (top/bottom)
 *
 *  Layout modes:
 *    - Grid (uniform card heights set by control)
 *    - Masonry (variable heights, JS-driven)
 *
 *  Hover effects, set via .noka-hover-{name} on the grid:
 *    none, zoom, lift, reveal, slide-up, tilt, grayscale
 * ============================================================ */

.noka-showcase-wrapper {
    width: 100%;
}

.noka-showcase-grid {
    display: grid;
    grid-template-columns: repeat(var(--cols-d, 3), 1fr);
    gap: 24px;
}

@media (max-width: 1024px) {
    .noka-showcase-grid {
        grid-template-columns: repeat(var(--cols-t, 2), 1fr);
    }
}

@media (max-width: 767px) {
    .noka-showcase-grid {
        grid-template-columns: repeat(var(--cols-m, 1), 1fr);
    }
}

/* ---- Card base ---- */
.noka-showcase-card {
    position: relative;
    overflow: hidden;
    background: #1a1a1a;
    border-radius: 4px;
    text-decoration: none;
    color: inherit;
    display: block;
    transition: transform 0.4s ease, box-shadow 0.4s ease;
    will-change: transform;
}

a.noka-showcase-card {
    cursor: pointer;
}

/* Wide cards span 2 columns when grid has 2+ columns. */
@media (min-width: 768px) {
    .noka-showcase-card.is-wide {
        grid-column: span 2;
    }
}

/* ---- Image ---- */
.noka-showcase-image-wrap {
    position: relative;
    width: 100%;
    height: 100%;
    overflow: hidden;
    flex: 1 1 auto;
    min-height: 0;
}

/*
 * In "content-overlay" mode (overlay on image), the wrap must fill
 * the card. The card has fixed height (or masonry) and the wrap +
 * image inside it must stretch to fill.
 */
.noka-showcase-card.has-content-overlay .noka-showcase-image-wrap {
    height: 100%;
}

.noka-showcase-card.has-content-outside .noka-showcase-image-wrap {
    aspect-ratio: 4 / 3;
    height: auto;
}

.noka-showcase-image {
    /* !important: Some themes (e.g. Astra, Hello Elementor child themes)
     * inject `.entry-content img { height: auto }` rules that win on
     * specificity. The card relies on the image filling its wrapper.
     * Documented fix from v3.9.0. */
    width: 100% !important;
    height: 100% !important;
    object-fit: cover;
    display: block;
    transition: transform 0.6s ease, filter 0.4s ease, opacity 0.4s ease;
    /* Filter values are injected per-widget via Elementor selectors. */
    filter:
        brightness(var(--noka-brightness, 100%))
        saturate(var(--noka-saturation, 100%))
        blur(var(--noka-blur, 0px));
}

.noka-showcase-card.has-content-outside .noka-showcase-image {
    /* !important: same theme-override reason as above. */
    height: 100% !important;
}

.noka-showcase-overlay {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.35);
    transition: background-color 0.4s ease;
    pointer-events: none;
    z-index: 1;
}

/* ---- Content positioning ---- */
.noka-showcase-content {
    position: relative;
    padding: 24px;
    z-index: 2;
}

.noka-showcase-card.has-content-overlay .noka-showcase-content {
    position: absolute;
    left: 0;
    right: 0;
    z-index: 2;
    color: #fff;
}

.noka-showcase-card.noka-pos-overlay-bottom .noka-showcase-content {
    bottom: 0;
}

.noka-showcase-card.noka-pos-overlay-top .noka-showcase-content {
    top: 0;
}

.noka-showcase-card.noka-pos-overlay-center .noka-showcase-content {
    top: 50%;
    transform: translateY(-50%);
}

.noka-showcase-card.noka-pos-top {
    display: flex;
    flex-direction: column-reverse;
}

.noka-showcase-card.noka-pos-bottom {
    display: flex;
    flex-direction: column;
}

.noka-showcase-card.has-content-outside .noka-showcase-image-wrap {
    flex: 0 0 auto;
}

.noka-showcase-card.has-content-outside .noka-showcase-content {
    flex: 1 1 auto;
    color: inherit;
}

.noka-showcase-card.has-content-outside .noka-showcase-title,
.noka-showcase-card.has-content-outside .noka-showcase-desc {
    color: inherit;
}

/* ---- Title / description / icon ---- */
.noka-showcase-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 12px;
    color: #fff;
    line-height: 1;
}

.noka-showcase-icon i { font-size: 32px; }
.noka-showcase-icon svg { width: 32px; height: 32px; fill: currentColor; }

.noka-showcase-title {
    margin: 0 0 8px;
    color: #fff;
    line-height: 1.2;
}

.noka-showcase-desc {
    margin: 0;
    color: rgba(255, 255, 255, 0.8);
    line-height: 1.5;
}

.noka-showcase-desc p:last-child { margin-bottom: 0; }
.noka-showcase-desc p:first-child { margin-top: 0; }

/* ---- Button ---- */
.noka-showcase-btn-wrap {
    margin-top: 16px;
}

.noka-showcase-btn {
    display: inline-block;
    padding: 10px 24px;
    background: rgba(255, 255, 255, 0.15);
    color: #fff;
    text-decoration: none;
    border-radius: 2px;
    transition: background-color 0.25s ease, color 0.25s ease, transform 0.25s ease;
    border: 0;
    cursor: pointer;
    line-height: 1.2;
}

.noka-showcase-btn:hover {
    background: #fff;
    color: #000;
}

/* ============================================================
 *  Hover effects (applied per-grid via .noka-hover-XYZ)
 * ============================================================ */

/* ---- None ---- */
/* (no styles, just reset) */

/* ---- Zoom: image scales on hover ---- */
.noka-hover-zoom .noka-showcase-card:hover .noka-showcase-image {
    transform: scale(1.06);
}

/* ---- Lift: card raises with shadow ---- */
.noka-hover-lift .noka-showcase-card:hover {
    transform: translateY(-6px);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
}

/* ---- Reveal: content fades in only on hover (overlay positions only) ---- */
.noka-hover-reveal .noka-showcase-card.has-content-overlay .noka-showcase-content {
    opacity: 0;
    transition: opacity 0.4s ease;
}

.noka-hover-reveal .noka-showcase-card.has-content-overlay:hover .noka-showcase-content {
    opacity: 1;
}

.noka-hover-reveal .noka-showcase-card.has-content-overlay .noka-showcase-overlay {
    opacity: 0;
}

.noka-hover-reveal .noka-showcase-card.has-content-overlay:hover .noka-showcase-overlay {
    opacity: 1;
}

/* ---- Slide-up: content slides up from below on hover ---- */
.noka-hover-slide-up .noka-showcase-card.has-content-overlay {
    overflow: hidden;
}

.noka-hover-slide-up .noka-showcase-card.noka-pos-overlay-bottom .noka-showcase-content {
    transform: translateY(40%);
    transition: transform 0.4s ease;
}

.noka-hover-slide-up .noka-showcase-card.noka-pos-overlay-bottom:hover .noka-showcase-content {
    transform: translateY(0);
}

/* ---- Tilt: subtle 3D tilt on hover ---- */
.noka-hover-tilt .noka-showcase-card {
    transition: transform 0.5s cubic-bezier(0.2, 0.7, 0.3, 1), box-shadow 0.5s ease;
    transform-style: preserve-3d;
}

.noka-hover-tilt .noka-showcase-card:hover {
    transform: perspective(1000px) rotateX(2deg) rotateY(-3deg) translateY(-4px);
    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
}

/* ---- Grayscale to color ---- */
/*
 * The image's base filter() includes brightness/saturation/blur.
 * For grayscale-to-color hover, we override saturation: 0% in default state,
 * and let it return to normal on hover. This composes cleanly with the
 * other filter variables.
 */
.noka-hover-grayscale .noka-showcase-card .noka-showcase-image {
    --noka-saturation: 0%;
}

.noka-hover-grayscale .noka-showcase-card:hover .noka-showcase-image {
    --noka-saturation: 100%;
}

/* ============================================================
 *  Masonry mode
 * ============================================================ */
.noka-showcase-grid.is-masonry {
    display: block;
    column-gap: 24px;
}

.noka-showcase-grid.is-masonry .noka-showcase-card {
    break-inside: avoid;
    margin-bottom: 24px;
    /* !important: Grid mode sets a fixed pixel height on `.noka-showcase-card`
     * via Elementor's per-widget CSS injection. In masonry mode each card
     * must size to its content; this explicitly resets the inherited rule. */
    height: auto !important;
    width: 100%;
    display: block;
}

@media (min-width: 1025px) {
    .noka-showcase-grid.is-masonry { column-count: var(--cols-d, 3); }
}
@media (max-width: 1024px) and (min-width: 768px) {
    .noka-showcase-grid.is-masonry { column-count: var(--cols-t, 2); }
}
@media (max-width: 767px) {
    .noka-showcase-grid.is-masonry { column-count: var(--cols-m, 1); }
}

.noka-showcase-grid.is-masonry .noka-showcase-card.has-content-outside {
    display: block;
}

.noka-showcase-grid.is-masonry .noka-showcase-card.has-content-outside .noka-showcase-image-wrap {
    aspect-ratio: auto;
    height: auto;
}

.noka-showcase-grid.is-masonry .noka-showcase-card.has-content-outside .noka-showcase-image {
    height: auto;
}

/* ============================================================
 *  Accessibility — global rules
 *  ============================================================
 *  These rules apply across all widgets and intentionally have low
 *  specificity so themes can still override per project. They exist
 *  to satisfy the WIDGET_STANDARDS Charter axes 2 (CSS-first, reduced
 *  motion) and 3 (Accessibility WCAG AA, focus-visible).
 * ============================================================ */

/* ---- Focus rings -------------------------------------------------
 * Single canonical focus style for every interactive element across
 * all Noka widgets. Avoids the 'outline:none' anti-pattern by always
 * pairing it with a visible :focus-visible replacement.
 *
 * Add the class .noka-focus-ring to any widget element that needs the
 * standard ring; for native links/buttons inside .noka-* wrappers we
 * also apply it via descendant selectors below so themes don't have
 * to opt in manually.
 * ----------------------------------------------------------------- */
.noka-focus-ring,
.noka-cat-item,
.noka-gallery-item,
.noka-gallery-clickable,
.noka-showcase-card,
.noka-showcase-btn,
.noka-carousel-prev,
.noka-carousel-next,
.noka-carousel-dot,
.noka-ss-prev,
.noka-ss-next,
.noka-ss-thumb,
.noka-ss-fullscreen-btn,
.noka-lightbox-prev,
.noka-lightbox-next,
.noka-lightbox-close {
    outline: none;
}

.noka-focus-ring:focus-visible,
.noka-cat-item:focus-visible,
.noka-gallery-item:focus-visible,
.noka-gallery-clickable:focus-visible,
.noka-showcase-card:focus-visible,
.noka-showcase-btn:focus-visible,
.noka-carousel-prev:focus-visible,
.noka-carousel-next:focus-visible,
.noka-carousel-dot:focus-visible,
.noka-ss-prev:focus-visible,
.noka-ss-next:focus-visible,
.noka-ss-thumb:focus-visible,
.noka-ss-fullscreen-btn:focus-visible,
.noka-lightbox-prev:focus-visible,
.noka-lightbox-next:focus-visible,
.noka-lightbox-close:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 2px;
    /* Box-shadow gives a subtle halo so the ring is visible against
     * any background (including dark images). currentColor on the
     * outline + a translucent black halo works in light & dark themes. */
    box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.18);
    border-radius: 2px;
    z-index: 5;
}

/* ---- Reduced motion ---------------------------------------------
 * Honour the user's OS-level "reduce motion" preference. The Charter
 * requires this on every widget. We disable purely decorative motion
 * (transitions, transform/scale animations, autoplay carousels) but
 * keep functional state changes (visibility, opacity flips) instant.
 *
 * Carousel/Slideshow JS also reads the same media query and disables
 * autoplay; this CSS block is the safety net.
 * ----------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    /* Strip transitions and animations from every Noka element. */
    [class^="noka-"],
    [class*=" noka-"],
    [class^="noka-"] *,
    [class*=" noka-"] * {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        scroll-behavior: auto !important;
    }

    /* Cancel decorative transforms (zoom, lift, tilt, slide-up).
     * Keep the resting state visible so the user always sees the
     * full image / card. */
    .noka-cat-zoom .noka-cat-image,
    .noka-hover-zoom .noka-showcase-image,
    .noka-hover-zoom .noka-gallery-item img,
    .noka-hover-lift .noka-showcase-card,
    .noka-hover-tilt .noka-showcase-card,
    .noka-hover-slide-up .noka-showcase-content,
    .noka-showcase-card,
    .noka-showcase-image {
        transform: none !important;
    }

    /* Grayscale-to-color hover: skip the transition; show colour. */
    .noka-hover-grayscale .noka-showcase-image {
        filter:
            brightness(var(--noka-brightness, 100%))
            saturate(var(--noka-saturation, 100%))
            blur(var(--noka-blur, 0px)) !important;
    }
}

/* ============================================================
 *  Image Effects widget — 3.15.0 rewrite (.noka-fx-*)
 *  ============================================================
 *  Single-image element with hover/load/view-once triggers and
 *  five visual effect families: zoom, grayscale, ink reveal,
 *  slide reveal (4 directions), tilt.
 *
 *  Architecture: plain CSS `transition` on a single property per
 *  effect, toggled by the `.is-active` class which JS adds when a
 *  trigger fires. Editor preview uses one-shot `@keyframes`
 *  per-effect so authors get visible feedback on every settings
 *  change. No `@property`, no `@starting-style`, no
 *  `animation-timeline: view()` — earlier 3.14.x revisions leaned
 *  on those features and proved unreliable across the realistic
 *  placement matrix (above/below the fold, narrow column, editor
 *  canvas, dynamic insertion). The XTRA `cz_brfx_*` family
 *  (codevz-plus) provided the simplification template: keep the
 *  tools to widely-supported CSS and let JS handle the trigger
 *  event. The visual choice (clip-path curtain reveal on the
 *  image instead of an overlay-block sweep) is what the original
 *  Image Effects spec promised.
 *
 *  CSS variables (set inline by templates/widgets/image-effects.php):
 *    --noka-fx-duration       e.g. 800ms
 *    --noka-fx-easing         e.g. cubic-bezier(.7, 0, .3, 1)
 *  Set via Style controls (Elementor selectors_dictionary):
 *    --noka-fx-brightness     e.g. 100%
 *    --noka-fx-saturation     e.g. 100%
 *    --noka-fx-blur           e.g. 0px
 *    --noka-fx-tilt-strength  e.g. 8 (degrees, JS-consumed)
 *    --noka-fx-tilt-scale     e.g. 1.04 (JS-consumed)
 */

/* ---- Wrapper layout ---------------------------------------------- */
.noka-fx-outer {
    display: block;
    /* The Elementor `align` control adds `display: flex; justify-content: ...`
     * inline as needed. Outer is otherwise a plain block. */
}

.noka-fx {
    --noka-fx-duration: 800ms;
    --noka-fx-easing: cubic-bezier(.7, 0, .3, 1);
    --noka-fx-brightness: 100%;
    --noka-fx-saturation: 100%;
    --noka-fx-blur: 0px;
    --noka-fx-tilt-strength: 8;
    --noka-fx-tilt-scale: 1.04;

    display: block;
    /* Explicit width: 100% is critical because the wrapper carries
     * `container-type: inline-size` for caption container queries.
     * Without an explicit width, inline-axis size containment
     * resolves a flex item's intrinsic width to zero, collapsing
     * the image. (Documented bug fix from 3.14.2.) */
    width: 100%;
    position: relative;
    text-decoration: none;
    color: inherit;
    container-type: inline-size;
    container-name: noka-fx;
    overflow: visible;
}

.noka-fx-frame {
    position: relative;
    overflow: hidden;
    width: 100%;
    line-height: 0; /* removes phantom whitespace below the image */
}

/* Aspect ratio is set by the Elementor `aspect_ratio` control via
 * selectors_dictionary; the variants here just position the image
 * correctly inside the frame. */
.noka-fx--aspect-original .noka-fx-frame { /* aspect-ratio defaults to auto */ }
.noka-fx--aspect-fixed .noka-fx-img {
    position: absolute;
    inset: 0;
    height: 100%;
    object-fit: cover;
}

/* ---- The image itself -------------------------------------------- */
.noka-fx-img {
    width: 100%;
    height: auto;
    display: block;
    /* User filters live here as the rest state. Each effect's active
     * state below either replaces this filter (grayscale → colour) or
     * leaves it alone (zoom, slide, ink). */
    filter: brightness(var(--noka-fx-brightness))
            saturate(var(--noka-fx-saturation))
            blur(var(--noka-fx-blur));
    /* One transition rule covers every effect's animatable property.
     * .is-active or :hover toggles the relevant property to its end
     * state; the browser handles the interpolation. */
    transition: transform var(--noka-fx-duration) var(--noka-fx-easing),
                clip-path  var(--noka-fx-duration) var(--noka-fx-easing),
                filter     var(--noka-fx-duration) var(--noka-fx-easing);
    will-change: transform, clip-path, filter;
}

/* ==================================================================
 *  Effects — each toggles a single property between rest and active.
 *  Active is reached when:
 *    a) hover trigger and cursor is over the wrapper
 *    b) any other trigger and JS has added `.is-active`
 *    c) editor preview: per-effect @keyframes runs once on render
 *       (see editor block at the bottom)
 * ================================================================== */

/* ---- Effect: None ------------------------------------------------ */
/* No state change — the image is just rendered with the user filter. */

/* ---- Effect: Zoom ------------------------------------------------ */
.noka-fx--zoom .noka-fx-img {
    transform: scale(1);
}
.noka-fx--zoom.noka-fx--trigger-hover:hover .noka-fx-img,
.noka-fx--zoom.is-active .noka-fx-img {
    transform: scale(1.12);
}

/* ---- Effect: Grayscale → Colour ---------------------------------- */
.noka-fx--grayscale .noka-fx-img {
    filter: grayscale(1)
            brightness(var(--noka-fx-brightness))
            saturate(var(--noka-fx-saturation))
            blur(var(--noka-fx-blur));
}
.noka-fx--grayscale.noka-fx--trigger-hover:hover .noka-fx-img,
.noka-fx--grayscale.is-active .noka-fx-img {
    filter: grayscale(0)
            brightness(var(--noka-fx-brightness))
            saturate(var(--noka-fx-saturation))
            blur(var(--noka-fx-blur));
}

/* ---- Effect: Ink Reveal ------------------------------------------ */
/* Circular reveal from centre. circle(R) at 50% 50% — at ~71% the
 * circle reaches the corners of a square, so 80% gives a clean
 * hold past the edges. */
.noka-fx--ink .noka-fx-img {
    clip-path: circle(0% at 50% 50%);
}
.noka-fx--ink.noka-fx--trigger-hover:hover .noka-fx-img,
.noka-fx--ink.is-active .noka-fx-img {
    clip-path: circle(80% at 50% 50%);
}

/* ---- Effect: Slide reveals (4 directions) ------------------------ */
/* `clip-path: inset(top right bottom left)` — a percentage on a side
 * clips that portion away. Each direction starts with 100% on the
 * side OPPOSITE to where the reveal originates: a top-to-bottom
 * reveal starts with bottom: 100% (top of the image is visible
 * first, the rest gets revealed downward) and ends with all sides
 * at 0%. */
.noka-fx--slide-tb .noka-fx-img { clip-path: inset(0 0 100% 0); } /* top edge appears first */
.noka-fx--slide-bt .noka-fx-img { clip-path: inset(100% 0 0 0); } /* bottom edge appears first */
.noka-fx--slide-lr .noka-fx-img { clip-path: inset(0 100% 0 0); } /* left edge appears first */
.noka-fx--slide-rl .noka-fx-img { clip-path: inset(0 0 0 100%); } /* right edge appears first */

.noka-fx--slide-tb.noka-fx--trigger-hover:hover .noka-fx-img,
.noka-fx--slide-bt.noka-fx--trigger-hover:hover .noka-fx-img,
.noka-fx--slide-lr.noka-fx--trigger-hover:hover .noka-fx-img,
.noka-fx--slide-rl.noka-fx--trigger-hover:hover .noka-fx-img,
.noka-fx--slide-tb.is-active .noka-fx-img,
.noka-fx--slide-bt.is-active .noka-fx-img,
.noka-fx--slide-lr.is-active .noka-fx-img,
.noka-fx--slide-rl.is-active .noka-fx-img {
    clip-path: inset(0 0 0 0);
}

/* ---- Effect: Tilt ------------------------------------------------ */
/* No CSS animation — image-effects.js sets `transform` inline on
 * mousemove. Set the perspective context and a snappier rest
 * transition (so mouseleave feels responsive, not the full duration
 * users picked for reveals). */
.noka-fx--tilt {
    perspective: 900px;
}
.noka-fx--tilt .noka-fx-img {
    transform-style: preserve-3d;
    transition: transform 240ms ease-out;
}

/* ==================================================================
 *  XTRA-derived effects (3.16.0)
 *  ==================================================================
 *  Patterns adapted from codevz-plus (XTRA). Kept the simplicity:
 *  transition on `transform`, or a single keyframe for shine. No
 *  modern CSS features that previously caused trouble.
 *
 *  Some of these only make visual sense as hover effects — Drift
 *  and Skew leave the image in a tilted/shifted state, which looks
 *  broken if the trigger is `view-once` or `load`. The PHP template
 *  forces `trigger = hover` for those effects.
 */

/* ---- Effect: Shine ----------------------------------------------- */
/* A skewed white gradient strip sweeps across the image left-to-right.
 * Adapted from XTRA's `fx_shine`. The strip is a `::before` on the
 * frame, sitting above the image (z-index 2) but below the caption
 * overlay (z-index 4). Works with every trigger — after the sweep,
 * the image is unchanged, so view-once and load are fine too. */
.noka-fx--shine .noka-fx-frame::before {
    content: '';
    position: absolute;
    top: 0;
    left: -120%;
    width: 50%;
    height: 100%;
    background: linear-gradient(to right,
                                rgba(255, 255, 255, 0) 0%,
                                rgba(255, 255, 255, 0.5) 100%);
    transform: skewX(-25deg);
    z-index: 2;
    pointer-events: none;
}
.noka-fx--shine.noka-fx--trigger-hover:hover .noka-fx-frame::before,
.noka-fx--shine.is-active .noka-fx-frame::before {
    animation: noka-fx-shine var(--noka-fx-duration) forwards;
}
@keyframes noka-fx-shine {
    0%   { left: -120%; }
    100% { left: 130%; }
}

/* ---- Effect: Skew (left / right) — hover-only ------------------- */
/* Adapted from XTRA's `fx_skew_left` / `fx_skew_right`. Skew angle
 * and direction kept identical (10deg / -10deg). */
.noka-fx--skew-left .noka-fx-img,
.noka-fx--skew-right .noka-fx-img {
    transform: skew(0deg);
}
.noka-fx--skew-left:hover .noka-fx-img {
    transform: skew(10deg);
}
.noka-fx--skew-right:hover .noka-fx-img {
    transform: skew(-10deg);
}

/* ---- Effect: Grow & Rotate -------------------------------------- */
/* Combines a slight zoom with a tiny rotation — adapted from XTRA's
 * `fx_grow_rotate_right`. Works with every trigger; after the active
 * state, the image is at scale(1.06) rotate(3deg) and stays there. */
.noka-fx--tilt-zoom .noka-fx-img {
    transform: scale(1) rotate(0deg);
}
.noka-fx--tilt-zoom.noka-fx--trigger-hover:hover .noka-fx-img,
.noka-fx--tilt-zoom.is-active .noka-fx-img {
    transform: scale(1.06) rotate(3deg);
}

/* ---- Effect: Drift (4 directions) — hover-only ------------------ */
/* Subtle 30px translate in a chosen direction on hover. Adapted from
 * XTRA's `fx_up` / `fx_down` / `fx_left` / `fx_right`. Hover-only
 * because non-hover triggers would leave the image permanently
 * shifted out of position. */
.noka-fx--drift-up .noka-fx-img,
.noka-fx--drift-down .noka-fx-img,
.noka-fx--drift-left .noka-fx-img,
.noka-fx--drift-right .noka-fx-img {
    transform: translate(0, 0);
}
.noka-fx--drift-up:hover    .noka-fx-img { transform: translateY(-30px); }
.noka-fx--drift-down:hover  .noka-fx-img { transform: translateY( 30px); }
.noka-fx--drift-left:hover  .noka-fx-img { transform: translateX(-30px); }
.noka-fx--drift-right:hover .noka-fx-img { transform: translateX( 30px); }

/* ==================================================================
 *  Editor preview (body.elementor-editor-active)
 *  ==================================================================
 *  In the Elementor canvas, the JS-driven trigger paths can race
 *  Elementor's render lifecycle on settings changes. To guarantee
 *  visible feedback on every tweak, run a one-shot CSS animation
 *  per effect on render. Each preview animation mirrors what
 *  `.is-active` does on the live site, so canvas matches output.
 *
 *  Hover and Tilt are NOT overridden — they preview correctly via
 *  the cursor in the editor as-is.
 *
 *  On the live site and in Elementor's preview mode the
 *  body.elementor-editor-active class is absent, so this entire
 *  block is dormant — the JS+IntersectionObserver path runs there.
 */
body.elementor-editor-active .noka-fx--zoom.noka-fx--trigger-load .noka-fx-img,
body.elementor-editor-active .noka-fx--zoom.noka-fx--trigger-view-once .noka-fx-img,
body.elementor-editor-active .noka-fx--zoom.noka-fx--trigger-scroll-progress .noka-fx-img {
    animation: noka-fx-zoom-preview var(--noka-fx-duration) var(--noka-fx-easing) forwards;
}
@keyframes noka-fx-zoom-preview {
    from { transform: scale(1); }
    to   { transform: scale(1.12); }
}

body.elementor-editor-active .noka-fx--grayscale.noka-fx--trigger-load .noka-fx-img,
body.elementor-editor-active .noka-fx--grayscale.noka-fx--trigger-view-once .noka-fx-img,
body.elementor-editor-active .noka-fx--grayscale.noka-fx--trigger-scroll-progress .noka-fx-img {
    animation: noka-fx-grayscale-preview var(--noka-fx-duration) var(--noka-fx-easing) forwards;
}
@keyframes noka-fx-grayscale-preview {
    from {
        filter: grayscale(1)
                brightness(var(--noka-fx-brightness))
                saturate(var(--noka-fx-saturation))
                blur(var(--noka-fx-blur));
    }
    to {
        filter: grayscale(0)
                brightness(var(--noka-fx-brightness))
                saturate(var(--noka-fx-saturation))
                blur(var(--noka-fx-blur));
    }
}

body.elementor-editor-active .noka-fx--ink.noka-fx--trigger-load .noka-fx-img,
body.elementor-editor-active .noka-fx--ink.noka-fx--trigger-view-once .noka-fx-img,
body.elementor-editor-active .noka-fx--ink.noka-fx--trigger-scroll-progress .noka-fx-img {
    animation: noka-fx-ink-preview var(--noka-fx-duration) var(--noka-fx-easing) forwards;
}
@keyframes noka-fx-ink-preview {
    from { clip-path: circle(0% at 50% 50%); }
    to   { clip-path: circle(80% at 50% 50%); }
}

body.elementor-editor-active .noka-fx--slide-tb.noka-fx--trigger-load .noka-fx-img,
body.elementor-editor-active .noka-fx--slide-tb.noka-fx--trigger-view-once .noka-fx-img,
body.elementor-editor-active .noka-fx--slide-tb.noka-fx--trigger-scroll-progress .noka-fx-img {
    animation: noka-fx-slide-tb-preview var(--noka-fx-duration) var(--noka-fx-easing) forwards;
}
@keyframes noka-fx-slide-tb-preview {
    from { clip-path: inset(0 0 100% 0); }
    to   { clip-path: inset(0 0 0 0); }
}

body.elementor-editor-active .noka-fx--slide-bt.noka-fx--trigger-load .noka-fx-img,
body.elementor-editor-active .noka-fx--slide-bt.noka-fx--trigger-view-once .noka-fx-img,
body.elementor-editor-active .noka-fx--slide-bt.noka-fx--trigger-scroll-progress .noka-fx-img {
    animation: noka-fx-slide-bt-preview var(--noka-fx-duration) var(--noka-fx-easing) forwards;
}
@keyframes noka-fx-slide-bt-preview {
    from { clip-path: inset(100% 0 0 0); }
    to   { clip-path: inset(0 0 0 0); }
}

body.elementor-editor-active .noka-fx--slide-lr.noka-fx--trigger-load .noka-fx-img,
body.elementor-editor-active .noka-fx--slide-lr.noka-fx--trigger-view-once .noka-fx-img,
body.elementor-editor-active .noka-fx--slide-lr.noka-fx--trigger-scroll-progress .noka-fx-img {
    animation: noka-fx-slide-lr-preview var(--noka-fx-duration) var(--noka-fx-easing) forwards;
}
@keyframes noka-fx-slide-lr-preview {
    from { clip-path: inset(0 100% 0 0); }
    to   { clip-path: inset(0 0 0 0); }
}

body.elementor-editor-active .noka-fx--slide-rl.noka-fx--trigger-load .noka-fx-img,
body.elementor-editor-active .noka-fx--slide-rl.noka-fx--trigger-view-once .noka-fx-img,
body.elementor-editor-active .noka-fx--slide-rl.noka-fx--trigger-scroll-progress .noka-fx-img {
    animation: noka-fx-slide-rl-preview var(--noka-fx-duration) var(--noka-fx-easing) forwards;
}
@keyframes noka-fx-slide-rl-preview {
    from { clip-path: inset(0 0 0 100%); }
    to   { clip-path: inset(0 0 0 0); }
}

/* Editor previews for the XTRA-derived effects that support non-hover
 * triggers (shine and grow-rotate). Skew and Drift are hover-only —
 * the PHP template forces `trigger = hover` for them, so the editor
 * canvas previews them via the same :hover rules used live; no
 * additional editor-mode CSS needed. */
body.elementor-editor-active .noka-fx--shine.noka-fx--trigger-load .noka-fx-frame::before,
body.elementor-editor-active .noka-fx--shine.noka-fx--trigger-view-once .noka-fx-frame::before,
body.elementor-editor-active .noka-fx--shine.noka-fx--trigger-scroll-progress .noka-fx-frame::before {
    animation: noka-fx-shine var(--noka-fx-duration) forwards;
}

body.elementor-editor-active .noka-fx--tilt-zoom.noka-fx--trigger-load .noka-fx-img,
body.elementor-editor-active .noka-fx--tilt-zoom.noka-fx--trigger-view-once .noka-fx-img,
body.elementor-editor-active .noka-fx--tilt-zoom.noka-fx--trigger-scroll-progress .noka-fx-img {
    animation: noka-fx-tilt-zoom-preview var(--noka-fx-duration) var(--noka-fx-easing) forwards;
}
@keyframes noka-fx-tilt-zoom-preview {
    from { transform: scale(1) rotate(0deg); }
    to   { transform: scale(1.06) rotate(3deg); }
}

/* ==================================================================
 *  Reduced motion
 *  ==================================================================
 *  Honour `prefers-reduced-motion: reduce`. Land on the active /
 *  revealed state for every effect, no transition or animation. JS
 *  also short-circuits in this mode (sets .is-active immediately,
 *  doesn't attach observers or move-listeners).
 */
@media (prefers-reduced-motion: reduce) {
    .noka-fx-img {
        transition: none !important;
        animation: none !important;
    }
    .noka-fx[class*="--slide-"] .noka-fx-img,
    .noka-fx--ink .noka-fx-img {
        clip-path: inset(0 0 0 0);
    }
    .noka-fx--zoom .noka-fx-img {
        transform: none;
    }
    .noka-fx--grayscale .noka-fx-img {
        filter: brightness(var(--noka-fx-brightness))
                saturate(var(--noka-fx-saturation))
                blur(var(--noka-fx-blur));
    }
}

/* ==================================================================
 *  Caption (semantic <figcaption> below the image, or absolutely
 *  positioned overlay with three vertical anchors).
 * ================================================================== */
.noka-fx-figure {
    margin: 0;
    display: block;
}

.noka-fx-caption {
    display: block;
    font-size: 0.95em;
    line-height: 1.4;
    /* text-wrap: balance is widely supported as of 2024 — keeps short
     * captions from breaking awkwardly into orphaned final lines. */
    text-wrap: balance;
}

.noka-fx-caption.is-below,
.noka-fx-caption--below {
    margin-top: 0.75em;
}

.noka-fx-caption.is-overlay {
    position: absolute;
    left: 0;
    right: 0;
    z-index: 4;
    padding: 12px 16px;
    color: #fff;
    background: var(--noka-fx-overlay-bg, rgba(0, 0, 0, 0.45));
    pointer-events: none;
}

/* Per-position rules. The template emits both an `.is-overlay-*`
 * convenience class and a BEM-style `.noka-fx-caption--overlay-*`
 * modifier, so we accept either. */
.noka-fx-caption.is-overlay-top,
.noka-fx-caption--overlay-top    { top: 0; }
.noka-fx-caption.is-overlay-center,
.noka-fx-caption--overlay-center { top: 50%; transform: translateY(-50%); }
.noka-fx-caption.is-overlay-bottom,
.noka-fx-caption--overlay-bottom { bottom: 0; }

.noka-fx-figure .noka-fx-frame { isolation: isolate; }

/* Container query: very narrow widget → smaller caption */
@container noka-fx (max-width: 360px) {
    .noka-fx-caption { font-size: 0.85em; }
    .noka-fx-caption.is-overlay { padding: 8px 12px; }
}

/* ==================================================================
 *  Anchor wrapping (.noka-fx is sometimes the <a>)
 * ================================================================== */
a.noka-fx {
    text-decoration: none;
    color: inherit;
    cursor: pointer;
}
a.noka-fx:focus-visible {
    outline: 2px solid var(--noka-focus, #0073aa);
    outline-offset: 3px;
}

/* ---- Editor empty state inherits the global empty-state styles
 *      from Base — nothing extra needed here. */
