/* =========================================================
   Anti-flash for Swiper sliders, BEFORE Swiper's JS finishes running — its own
   stylesheet has already laid every .swiper-slide out in a plain flex row at
   100% width each, so for a split second on page load every slide shows
   stacked/overflowing at full size. Two different fixes are needed depending
   on what the slider looks like once it's actually running:

   1) Hero slider — it only ever shows ONE full-width slide at a time anyway,
      so simply hiding every slide but the first until Swiper stamps its own
      "swiper-initialized" class onto the root reproduces the exact final look
      immediately, with nothing left to jump once JS catches up.
   ========================================================= */
.ds-hero-swiper:not(.swiper-initialized) .swiper-slide:not(:first-child) {
    display: none;
}

/* 2) Product & category sliders show SEVERAL cards per row, not one — hiding
   the rest would leave a single card stretched across the full row (much
   BIGGER than its real size) until Swiper sets each slide's actual width, so
   here the fix instead lays out that same number of columns in plain CSS,
   matching assets/js/slider.js's own slidesPerView/spaceBetween breakpoints
   exactly. The row already looks right the instant the page paints, and
   nothing resizes once Swiper takes over — extra cards beyond one row are
   simply clipped by the overflow:hidden these roots already have, same as
   the real carousel only ever showing one page of cards at a time. */
.ds-product-swiper:not(.swiper-initialized) .swiper-wrapper,
.ds-category-swiper:not(.swiper-initialized) .swiper-wrapper {
    flex-wrap: nowrap;
}

.ds-product-swiper:not(.swiper-initialized) .swiper-slide {
    width: auto;
    flex: 0 0 calc((100% - 16px) / 2);
    margin-inline-end: 16px;
}

.ds-category-swiper:not(.swiper-initialized) .swiper-slide {
    width: auto;
    flex: 0 0 calc((100% - 10px) / 2);
    margin-inline-end: 10px;
}

@media (min-width: 768px) {
    .ds-product-swiper:not(.swiper-initialized) .swiper-slide {
        flex-basis: calc((100% - 40px) / 3);
        margin-inline-end: 20px;
    }

    .ds-category-swiper:not(.swiper-initialized) .swiper-slide {
        flex-basis: calc((100% - 48px) / 4);
        margin-inline-end: 16px;
    }
}

@media (min-width: 992px) {
    .ds-product-swiper:not(.swiper-initialized) .swiper-slide {
        flex-basis: calc((100% - 72px) / 4);
        margin-inline-end: 24px;
    }

    /* 992-1399px: 5 columns (was 6) — must match slidesPerView:5 at the 992
       breakpoint in initCategorySlider() (assets/js/slider.js). The 6-column
       version moves to the 1400px block below. */
    .ds-category-swiper:not(.swiper-initialized) .swiper-slide {
        flex-basis: calc((100% - 72px) / 5);
        margin-inline-end: 18px;
    }
}

@media (min-width: 1400px) {
    /* 1400px+: back to 6 columns — must match slidesPerView:6 at the 1400
       breakpoint in initCategorySlider() (assets/js/slider.js). */
    .ds-category-swiper:not(.swiper-initialized) .swiper-slide {
        flex-basis: calc((100% - 90px) / 6);
        margin-inline-end: 18px;
    }
}

/* =========================================================
   Homepage: Hero Slider (spec §8)
   ========================================================= */
.ds-hero-slider {
    position: relative;
    padding-block: clamp(1rem, 3vw, 1.5rem);
    padding-inline: clamp(30px, 5vw, 60px);
}

.ds-hero-swiper {
    height: clamp(320px, 55vw, 560px);
    border-radius: var(--ds-radius-lg);
    overflow: visible !important;
}

.ds-hero-slider-wrap {
    position: relative;
}

.ds-hero-slide {
    position: relative;
    width: 100%;
    height: 100%;
}

.ds-hero-slide-image {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: var(--ds-radius-lg);
}

/* Hidden by default on ALL screen sizes (including desktop) — this is the actual
   fix for a real bug: the mobile-only image was previously only ever set to
   `display:none` INSIDE the "@media (max-width: 768px)" block below, which means
   on desktop nothing hid it at all. Since <img> defaults to display:inline and
   this element is painted after the desktop image inside the same
   position:absolute stack, it was rendering on top of and completely covering
   the real desktop image — exactly the "desktop shows the cropped mobile image"
   bug that was reported. The mobile media query below still overrides this back
   to display:block when appropriate. */
.ds-hero-slide-image-mobile {
    display: none;
}

.ds-hero-overlay {
    position: absolute;
    inset: 0;
    border-radius: var(--ds-radius-lg);
}

/* =========================================================
   محتوای اسلایدر - به صورت پیش‌فرض روی تصویر
   ========================================================= */
.ds-hero-content-wrap {
    position: relative;
    height: 100%;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    padding-inline: clamp(1.25rem, 4vw, 3rem);
    padding-bottom: clamp(2rem, 5vw, 4rem);
}

.ds-hero-content {
    max-width: 560px;
    color: var(--ds-white);
    text-align: center;
}

.ds-hero-title {
    font-size: clamp(0.7rem, 2vw, 0.85rem);
    color: var(--ds-white);
    margin-bottom: 0.75rem;
    background: #0000003b;
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(10px);
    text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
    border-radius: 10px;
    user-select: none;
    padding: 8px;
}

.ds-hero-description {
    font-size: clamp(0.95rem, 1.5vw, 1.2rem);
    color: rgba(255, 255, 255, 0.9);
    margin-bottom: 1.25rem;
}

.ds-hero-description p {
    margin-bottom: 0.5rem;
}

/* =========================================================
   دکمه‌های ناوبری - دسکتاپ
   ========================================================= */
.ds-hero-swiper .swiper-button-next,
.ds-hero-swiper .swiper-button-prev {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    color: var(--ds-white);
    background: rgba(255, 255, 255, 0.2);
    width: 48px;
    height: 48px;
    border-radius: 50%;
    backdrop-filter: blur(4px);
    /* border: 2px solid rgba(255, 255, 255, 0.3); */
    border: 2px solid #d9b56d;
    transition: all 0.3s ease;
    z-index: 10;
    margin: 0;
}

.ds-hero-swiper .swiper-button-next {
    right: -24px;
}

.ds-hero-swiper .swiper-button-prev {
    left: -24px;
}

.ds-hero-swiper .swiper-button-next::after,
.ds-hero-swiper .swiper-button-prev::after {
    font-size: 1.2rem;
    font-weight: bold;
}

.ds-hero-swiper .swiper-button-next:hover,
.ds-hero-swiper .swiper-button-prev:hover {
    background: rgba(255, 255, 255, 0.35);
    transform: translateY(-50%) scale(1.1);
    border-color: var(--ds-accent);
}

/* =========================================================
   RTL Support برای دکمه‌های ناوبری
   ========================================================= */
[dir="rtl"] .ds-hero-swiper .swiper-button-next {
    right: auto;
    left: -24px;
}

[dir="rtl"] .ds-hero-swiper .swiper-button-prev {
    left: auto;
    right: -24px;
}

/* =========================================================
   Pagination - دسکتاپ
   ========================================================= */
.ds-hero-swiper .swiper-pagination.swiper-pagination-clickable.swiper-pagination-bullets.swiper-pagination-horizontal {
    bottom: 0;
    position: relative !important;
    margin-top: 16px;
    display: flex;
    justify-content: center;
    gap: 8px;
}

/* استایل دات‌ها در دسکتاپ */
.ds-hero-swiper .swiper-pagination-bullet {
    background: var(--ds-muted) !important;
    opacity: 0.6;
    margin: 0 !important;
    width: 10px;
    height: 10px;
    transition: all 0.3s ease;
}

.ds-hero-swiper .swiper-pagination-bullet-active {
    opacity: 1;
    background: var(--ds-accent) !important;
    transform: scale(1.2);
    width: 12px;
    height: 12px;
}

/* =========================================================
   واکنش‌گرایی - تبلت و دسکتاپ کوچک
   ========================================================= */
@media (max-width: 992px) {
    .ds-hero-content {
        max-width: 480px;
    }

    .ds-hero-title {
        font-size: clamp(1.5rem, 3.5vw, 2.2rem);
    }

    .ds-hero-description {
        font-size: clamp(0.9rem, 1.8vw, 1.1rem);
    }

    .ds-hero-content-wrap {
        padding-bottom: clamp(1.5rem, 4vw, 3rem);
    }

    .ds-hero-swiper .swiper-pagination-bullet {
        width: 9px;
        height: 9px;
    }

    .ds-hero-swiper .swiper-pagination-bullet-active {
        width: 11px;
        height: 11px;
    }
}

/* =========================================================
   موبایل - اسلایدر مربع و انتقال محتوا به زیر
   ========================================================= */
@media (max-width: 768px) {
    .ds-hero-slider {
        padding-inline: 10px;
        padding-bottom: 0;
    }

    .ds-hero-swiper {
        height: auto !important;
        aspect-ratio: 1 / 1 !important;
        max-height: 100vw;
        overflow: hidden !important;
        border-radius: var(--ds-radius-lg) var(--ds-radius-lg) 0 0;
    }

    .ds-hero-slide-image {
        border-radius: var(--ds-radius-lg) var(--ds-radius-lg) 0 0;
        object-fit: cover;
        object-position: center;
    }

    /* Default (also the fallback for browsers without :has() support, see below):
       crop the desktop image into the square container, same as before. */
    .ds-hero-slide-image-desktop {
        transform: scale(1.15);
    }

    /* If this slide has a dedicated 1:1 mobile image (site-owner uploaded one),
       show that INSTEAD — uncropped, at its native square aspect ratio — and hide
       the desktop image entirely, rather than force-cropping the desktop photo.
       (.ds-hero-slide-image-mobile itself defaults to display:none for ALL screen
       sizes — see the base rule near the top of this file — so it only needs to be
       switched back On here, inside this mobile-only media query.) */
    .ds-hero-slide:has(.ds-hero-slide-image-mobile) .ds-hero-slide-image-desktop {
        display: none;
    }

    .ds-hero-slide:has(.ds-hero-slide-image-mobile) .ds-hero-slide-image-mobile {
        display: block;
        object-fit: contain;
    }

    .ds-hero-overlay {
        border-radius: var(--ds-radius-lg) var(--ds-radius-lg) 0 0;
    }

    /* مخفی کردن محتوای داخل اسلایدر در موبایل */
    .ds-hero-content-wrap {
        display: none !important;
    }

    /* کانتینر محتوای زیر اسلایدر */
    .ds-hero-mobile-content {
        display: block !important;
        background: var(--ds-white);
        padding: 1.5rem 1rem;
        border-radius: 0 0 var(--ds-radius-lg) var(--ds-radius-lg);
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
        margin-bottom: 1rem;
        text-align: center;
    }

    .ds-hero-mobile-content .ds-hero-title {
        color: var(--ds-text);
        font-size: 1.1rem;
        margin-bottom: 0.5rem;
        font-weight: 600;
        background: none;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        text-shadow: none;
        padding: 0;
        text-align: center;
    }

    .ds-hero-mobile-content .ds-hero-description {
        color: var(--ds-muted);
        font-size: 0.875rem;
        margin-bottom: 1rem;
        line-height: 1.6;
        text-align: center;
    }

    .ds-hero-mobile-content .ds-hero-description p {
        margin-bottom: 0.5rem;
    }

    .ds-hero-mobile-content .ds-hero-cta {
        display: block;
        width: fit-content;
        max-width: 100%;
        background: var(--ds-accent);
        color: var(--ds-white);
        padding: 0.6rem 1.5rem;
        border-radius: var(--ds-radius-sm);
        text-decoration: none;
        font-weight: 500;
        transition: background 0.3s ease;
        margin-inline: auto;
        text-align: center;
    }

    .ds-hero-mobile-content .ds-hero-cta:hover {
        background: #c2a15f;
        /* manually darkened shade of --ds-accent (#D9B56D) — no
                                 --ds-accent-dark token exists in style.css's palette. */
    }

    /* دکمه‌های ناوبری در موبایل */
    .ds-hero-swiper .swiper-button-next,
    .ds-hero-swiper .swiper-button-prev {
        width: 36px;
        height: 36px;
        background: rgba(0, 0, 0, 0.5);
        border: none;
        right: 10px !important;
        left: auto !important;
        top: 50%;
        transform: translateY(-50%);
    }

    [dir="rtl"] .ds-hero-swiper .swiper-button-next {
        right: auto !important;
        left: 10px !important;
    }

    [dir="rtl"] .ds-hero-swiper .swiper-button-prev {
        left: auto !important;
        right: 10px !important;
    }

    .ds-hero-swiper .swiper-button-prev {
        left: 10px !important;
        right: auto !important;
    }

    .ds-hero-swiper .swiper-button-next::after,
    .ds-hero-swiper .swiper-button-prev::after {
        font-size: 0.9rem;
    }

    /* Pagination در موبایل */
    .ds-hero-swiper .swiper-pagination.swiper-pagination-clickable.swiper-pagination-bullets.swiper-pagination-horizontal {
        bottom: 4px;
        position: absolute !important;
        margin-top: 0;
        display: flex;
        justify-content: center;
        gap: 6px;
        z-index: 5;
    }

    .ds-hero-swiper .swiper-pagination-bullet {
        background: rgba(255, 255, 255, 0.7) !important;
        opacity: 0.7;
        margin: 0 !important;
        width: 8px;
        height: 8px;
    }

    .ds-hero-swiper .swiper-pagination-bullet-active {
        opacity: 1;
        background: var(--ds-accent) !important;
        transform: scale(1.2);
        width: 10px;
        height: 10px;
    }
}

/* =========================================================
   مخفی کردن محتوای موبایل در دسکتاپ
   ========================================================= */
.ds-hero-mobile-content {
    display: none;
}

/* =========================================================
   واکنش‌گرایی - موبایل کوچک
   ========================================================= */
@media (max-width: 480px) {
    .ds-hero-swiper {
        aspect-ratio: 1 / 1 !important;
    }

    .ds-hero-swiper .swiper-button-next,
    .ds-hero-swiper .swiper-button-prev {
        width: 30px;
        height: 30px;
    }

    .ds-hero-swiper .swiper-button-next::after,
    .ds-hero-swiper .swiper-button-prev::after {
        font-size: 0.7rem;
    }

    .ds-hero-mobile-content {
        padding: 1rem 0.75rem;
    }

    .ds-hero-mobile-content .ds-hero-title {
        font-size: .85rem;
        height: 36px;
    }

    .ds-hero-mobile-content .ds-hero-description {
        font-size: 0.8rem;
    }

    .ds-hero-mobile-content .ds-hero-cta {
        padding: 0.5rem 1.2rem;
        font-size: 0.9rem;
    }

    .ds-hero-swiper .swiper-pagination-bullet {
        width: 6px;
        height: 6px;
    }

    .ds-hero-swiper .swiper-pagination-bullet-active {
        width: 8px;
        height: 8px;
    }
}

/* =========================================================
   Homepage: Promo banners strip (below hero)
   Layouts: 1 full-width / 2 side-by-side / 4 side-by-side.
   Managed from Appearance → بنرهای زیر اسلایدر.
   ========================================================= */
.ds-promo-banners {
    padding-block: clamp(0.75rem, 2vw, 3rem) clamp(0.25rem, 1vw, 0.5rem);
}

.ds-promo-banners-grid {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: clamp(0.65rem, 1.5vw, 1rem);
}

.ds-promo-banner-item {
    margin: 0;
    min-width: 0;
}

.ds-promo-banner-link {
    display: block;
    position: relative;
    overflow: hidden;
    border-radius: 999px;
    text-decoration: none;
    color: inherit;
    transition: transform 0.25s ease, box-shadow 0.25s ease;
}

a.ds-promo-banner-link:hover,
a.ds-promo-banner-link:focus-visible {
    transform: translateY(-2px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
    outline: none;
    border-radius: 999px;
}

a.ds-promo-banner-link:focus-visible {
    box-shadow: 0 0 0 3px rgba(82, 125, 94, 0.45);
}

/* Natural image size — no forced aspect-ratio / crop; width fills the column. */
.ds-promo-banner-image {
    display: block;
    width: 100%;
    height: auto;
    vertical-align: middle;
}

/* Full-width layout stays one column. 2- and 4-up layouts stay two-up on phones. */
.ds-promo-banners-grid[data-layout="1"] {
    grid-template-columns: 1fr;
}

.ds-promo-banners-grid[data-layout="2"],
.ds-promo-banners-grid[data-layout="4"] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* Desktop: four banners in one row. */
@media (min-width: 992px) {
    .ds-promo-banners-grid[data-layout="4"] {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

/* Prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
    .ds-promo-banner-link {
        transition: none;
    }

    a.ds-promo-banner-link:hover,
    a.ds-promo-banner-link:focus-visible {
        transform: none;
    }
}

/* =========================================================
   Homepage: Search section
   ========================================================= */
.ds-home-search {
    padding-block: 2.5rem;
    position: relative;
}

.ds-home-search>* {
    position: relative;
    z-index: 1;
}

/* Icon sits BESIDE the title now (previously stacked above it, centered on its
   own line) — this wrapper puts both on one row. */
.ds-home-search-heading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    margin-bottom: 0.5rem;
}

.ds-home-search-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: var(--ds-primary-light);
    color: var(--ds-primary);
}

.ds-home-search-badge .ds-icon {
    width: 22px;
    height: 22px;
}

.ds-home-search-title {
    font-size: var(--ds-fs-xl);
    margin-bottom: 0;
}

.ds-home-search-subtitle {
    color: var(--ds-muted);
    font-size: var(--ds-fs-sm);
    margin-bottom: 1.75rem;
}

.ds-home-search-rotator {
    display: inline-block;
    font-weight: 700;
    color: var(--ds-primary-dark);
    opacity: 1;
    transition: opacity 0.25s ease, transform 0.25s ease;
}

.ds-home-search-rotator-fade-out {
    opacity: 0;
    transform: translateY(4px);
}

@media (max-width: 575.98px) {
    /* Forced to its own line on mobile regardless of length — even a short name
       (which would technically fit at the end of the subtitle line) still drops
       down, so the rotator's line position stays fixed as it cycles through
       names of very different lengths instead of the whole subtitle reflowing
       line count each time. */
    .ds-home-search-rotator {
        display: block;
        margin-top: 0.35rem;
    }
}

.ds-home-search-card {
    max-width: 680px;
    background: var(--ds-white);
    border-radius: var(--ds-radius-lg);
    padding: 0.6rem;
    box-shadow: var(--ds-shadow-lg);
    border: 1px solid var(--ds-border);
}

.ds-home-search-wrap {
    width: 100%;
}

/* Homepage-only sizing for the shared .ds-ajax-search component (header/mobile
   keep their compact default styling — only this instance is enlarged). */
.ds-home-search-wrap .ds-ajax-search-input {
    padding: 1.05rem 3.25rem 1.05rem 1.25rem;
    font-size: var(--ds-fs-lg);
    border: none;
    border-radius: calc(var(--ds-radius-lg) - 0.3rem);
    background: transparent;
}

.ds-home-search-wrap .ds-ajax-search-input:focus {
    box-shadow: none;
}

.ds-home-search-wrap .ds-ajax-search-icon {
    color: var(--ds-primary);
    right: 20px;
}

.ds-home-search-wrap .ds-ajax-search-icon svg {
    width: 22px;
    height: 22px;
}

.ds-home-search-wrap .ds-ajax-search-results {
    right: 0.6rem;
    left: 0.6rem;
    top: calc(100% + 4px);
}

/* =========================================================
   Homepage: Intro / About section
   ========================================================= */
.ds-intro-section {
    display: grid;
    grid-template-columns: 1fr;
    gap: 2.5rem;
    align-items: center;
}

@media (min-width: 992px) {
    .ds-intro-section {
        grid-template-columns: 0.85fr 1fr;
        gap: 4rem;
    }
}

.ds-intro-media-col {
    position: relative;
}

/* Soft decorative blob behind the photo frame — pure atmosphere, no new colors:
   just the existing brand green at very low opacity, offset behind the image so
   it peeks out along one edge instead of sitting flat and static. */
.ds-intro-blob {
    position: absolute;
    top: -8%;
    left: -10%;
    width: 62%;
    height: 62%;
    color: var(--ds-primary);
    opacity: 0.10;
    z-index: 0;
    pointer-events: none;
}

[dir="rtl"] .ds-intro-blob {
    left: auto;
    right: -10%;
}

.ds-intro-media {
    position: relative;
    z-index: 1;
    border-radius: var(--ds-radius-lg);
    overflow: hidden;
    aspect-ratio: 4 / 3;
    background: var(--ds-primary-light);
    box-shadow: var(--ds-shadow-lg);
}

.ds-intro-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.ds-intro-media-placeholder {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, var(--ds-primary-light) 0%, var(--ds-border) 100%);
    color: var(--ds-primary);
}

.ds-intro-media-placeholder .ds-icon {
    width: 72px;
    height: 72px;
    opacity: 0.6;
}

.ds-intro-media-badge {
    position: absolute;
    bottom: 16px;
    right: 16px;
    width: 52px;
    height: 52px;
    border-radius: 50%;
    background: var(--ds-white);
    box-shadow: var(--ds-shadow-md);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--ds-primary);
}

.ds-intro-media-badge .ds-icon {
    width: 24px;
    height: 24px;
}

.ds-intro-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: var(--ds-fs-xs);
    font-weight: 700;
    letter-spacing: 0.02em;
    color: var(--ds-primary-dark);
    margin-bottom: 1rem;
}

.ds-intro-eyebrow::before {
    content: "";
    display: inline-block;
    width: 28px;
    height: 2px;
    background: var(--ds-accent);
    border-radius: 999px;
}

.ds-intro-title {
    font-size: var(--ds-fs-3xl);
    margin-bottom: 1.1rem;
    line-height: 1.4;
}

@media (max-width: 768px) {
.ds-intro-title{
        font-size: var(--ds-fs-2xl);
}
}


.ds-intro-text {
    color: var(--ds-muted);
    font-size: var(--ds-fs-base);
    line-height: 1.9;
    margin-bottom: 1.75rem;
}

.ds-intro-text p:last-child {
    margin-bottom: 0;
}

.ds-intro-actions {
    display: flex;
    align-items: center;
    gap: 1rem;
}

.ds-intro-btn {
    display: inline-block;
}

/* =========================================================
   Homepage: 6-feature showcase (near the bottom of the page)
   ========================================================= */
.ds-features-section {
    position: relative;
    z-index: 1;
}

.ds-features-section .section-title {
    margin-bottom: 2rem;
}

.ds-features-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.5rem;
}

@media (min-width: 576px) {
    .ds-features-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (min-width: 992px) {
    .ds-features-grid {
        grid-template-columns: repeat(3, 1fr);
    }
}

.ds-feature-card {
    background: var(--ds-white);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-lg);
    padding: 1.75rem 1.5rem;
    text-align: center;
    transition: transform var(--ds-transition), box-shadow var(--ds-transition);
}

.ds-feature-card:hover {
    transform: translateY(-4px);
    box-shadow: var(--ds-shadow-md);
}

.ds-feature-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: var(--ds-primary-light);
    color: var(--ds-primary-dark);
    margin-bottom: 1rem;
}

.ds-feature-icon .ds-icon {
    width: 26px;
    height: 26px;
}

.ds-feature-title {
    font-size: var(--ds-fs-base);
    margin-bottom: 0.5rem;
}

.ds-feature-text {
    font-size: var(--ds-fs-sm);
    color: var(--ds-muted);
    line-height: 1.8;
    margin-bottom: 0;
}

/* =========================================================
   Homepage: Category icons (spec §10)
   ========================================================= */
.ds-bg-alt {
    background: var(--ds-primary-light);
    position: relative;
    overflow: hidden;
}

.ds-bg-alt>.container {
    position: relative;
    z-index: 1;
}

/* =========================================================
   Decorative SVG atmosphere (spec follow-up: soften flat/rigid section edges
   with subtle wave lines and halos, no new colors, all aria-hidden)
   ========================================================= */
.ds-section-wave {
    position: absolute;
    inset-inline: 0;
    top: 0;
    width: 100%;
    height: 46px;
    color: var(--ds-primary);
    opacity: 0.12;
    z-index: 0;
    pointer-events: none;
}

.ds-section-wave-bottom {
    top: auto;
    bottom: 0;
    transform: scaleY(-1);
}

.ds-soft-halo {
    position: absolute;
    top: 50%;
    width: 46vw;
    max-width: 520px;
    aspect-ratio: 1 / 1;
    color: var(--ds-primary);
    transform: translateY(-50%);
    z-index: 0;
    pointer-events: none;
}

.ds-soft-halo-start {
    inset-inline-start: -14%;
}

.ds-soft-halo-end {
    inset-inline-end: -14%;
}

@media (max-width: 767.98px) {
    .ds-soft-halo {
        width: 70vw;
    }
}

/* =========================================================
   Homepage: Category slider (two-row Swiper grid)
   A real Swiper carousel using the built-in Grid module (2 fixed rows, fill:
   'row') — replaces the previous 3D "rotateY cylinder" carousel entirely (that
   implementation, and its dedicated assets/js/category-cylinder.js, have been
   removed). The Grid module advances a full page of cards at a time (Swiper sets
   slidesPerGroup to match slidesPerView automatically in grid mode), so the
   pagination dot count reflects real PAGES of cards, not one dot per card, and
   items that aren't currently visible slide in together as a group rather than
   one-by-one. Nav arrows + pagination reuse the same .ds-slider-nav /
   .ds-slider-pagination classes already used by the product sliders below, so
   there's no duplicate button styling to maintain.
   ========================================================= */
.ds-category-slider-wrap {
    position: relative;
    padding-inline: 0;
}

@media (min-width: 992px) {
    .ds-category-slider-wrap {
        padding-inline: 46px;
    }
}

.ds-category-swiper {
    padding-block: 0.5rem;
    padding-inline: 4px;
    z-index: 0;
}

/* Each swiper-slide holds one pair (up to 2) of category items, stacked
   vertically — this is what recreates the old 2-row look now that Swiper's own
   Grid module (incompatible with loop: true, see category-icons.php) is no
   longer used. flex:1 on the items below keeps both halves of a pair the same
   height even when one title wraps to 2 lines and the other doesn't. */
.ds-category-slide-col {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    height: 100%;
}

.ds-category-icon-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.55rem;
    padding: 0.5rem;
    flex: 1 1 0;
    background: var(--ds-white);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-md);
    text-align: center;
    text-decoration: none;
    color: var(--ds-text);
    box-sizing: border-box;
    transition: transform var(--ds-transition), box-shadow var(--ds-transition), border-color var(--ds-transition);
    /* NOT aspect-ratio: 1/1 on purpose — see .ds-category-icon-image-wrap
       comment below. Height stays content-driven (auto) so the card can
       never be squeezed shorter than what the 120px image + 2-line title
       actually need at a given column width. */
}

.ds-category-icon-item:hover,
.ds-category-icon-item:focus-visible {
    transform: translateY(-4px);
    box-shadow: var(--ds-shadow-md);
    border-color: transparent;
    color: var(--ds-primary-dark);
}

.ds-category-icon-image-wrap {
    /* 2026-09-02: raised from 96px per site-owner request. Deliberately NOT
       paired with `aspect-ratio: 1/1` on .ds-category-icon-item above — with
       5-6 columns across a normal container width, an item is only ever
       ~150-190px wide (measured: ~159px at the 992px breakpoint, ~190px at
       1400px), while a 120px circle + spacing + a wrapped 2-line title needs
       roughly 190px of height. Forcing the item to be as tall as it is wide
       would make it shorter than that at most of those widths and visibly
       clip the image/title. Leaving the item's height auto (content-driven,
       as it always has been) means this bigger image is always fully
       visible, at every breakpoint including mobile, with no overflow risk. */
    width: 120px;
    height: 120px;
    max-width: 128px;
    max-height: 128px;
    border-radius: 50%;
    overflow: hidden;
    background: var(--ds-primary-light);
    flex-shrink: 0;
}

.ds-category-icon-image-wrap img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform var(--ds-transition);
}

.ds-category-icon-item:hover .ds-category-icon-image-wrap img {
    transform: scale(1.08);
}

.ds-category-icon-title {
    font-size: var(--ds-fs-xs);
    font-weight: 600;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
}

.ds-category-icon-svg {
    display: inline-flex;
    width: 16px;
    height: 16px;
    color: var(--ds-primary);
    flex-shrink: 0;
}

.ds-category-icon-svg svg {
    width: 100%;
    height: 100%;
}

/* =========================================================
   Homepage: Scrolling product-tag ticker — a green strip of clickable tag
   pills, shown just above the "جدیدترین محصولات" band (site-owner request).
   One CSS keyframe on the track element does the actual sliding —
   GPU-composited, negligible cost even while other sliders/JS run on the
   page. The tag list is repeated back-to-back at least 5 times by PHP
   (dairy_shop_tag_ticker() in inc/helpers.php), but the real gap-free
   guarantee comes from assets/js/theme.js's initTagTicker() (2026-09-02):
   it clones EXTRA duplicate copies at runtime, in the browser, until the
   buffer of always-still-ahead content safely exceeds the actual window
   width (with margin) — so this self-adjusts correctly on any screen size,
   including a very wide desktop monitor where PHP's fixed 5 copies alone
   might not have been enough. That same script then measures one copy's
   exact rendered width and writes it into the --ds-tag-ticker-shift custom
   property, which the @keyframes below use for the loop-point transform —
   the seam is always exactly one copy-width regardless of how many total
   copies end up in the DOM. The "20%" fallback below (100/5%, matching
   PHP's baseline 5 copies) is used ONLY if that script doesn't run. The
   shift is POSITIVE, not negative — see the @keyframes comment below for
   why (the track is right-anchored inside this RTL page's flex layout, so
   a negative shift immediately exposed empty space on the right instead of
   scrolling in more content).
   ========================================================= */
.ds-tag-ticker {
    background: var(--ds-primary);
    overflow: hidden;
    position: relative;
    padding-block: 0.6rem;
}

.ds-tag-ticker::before,
.ds-tag-ticker::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    width: 56px;
    z-index: 1;
    pointer-events: none;
}

.ds-tag-ticker::before {
    inset-inline-start: 0;
    /* Opaque (matches the bar's own background) right at the true edge, fading
       to transparent moving inward — so a tag fades away before the hard
       overflow:hidden clip, instead of getting cut off mid-pill. */
    background: linear-gradient(to left, var(--ds-primary), transparent);
}

.ds-tag-ticker::after {
    inset-inline-end: 0;
    background: linear-gradient(to right, var(--ds-primary), transparent);
}

.ds-tag-ticker-track {
    display: flex;
    align-items: center;
    /* Spacing is a trailing margin on each item (below), not a flex `gap` here.
       `gap` would add one EXTRA gap at each seam between the 5 repeated
       copies that isn't part of any one copy's own width, so -20% (meant to
       be exactly "one copy's width") would land short — a small but visible
       hitch every loop. A per-item margin bakes the same spacing into every
       item including each copy's last one, so all 5 copies are exactly
       equal width and every seam is genuinely seamless. */
    width: max-content;
    animation: ds-tag-ticker-scroll 32s linear infinite;
    will-change: transform;
}

/* Pausing on hover/focus (rather than just letting it keep sliding under the
   cursor) is what makes the pills actually clickable in practice. */
.ds-tag-ticker:hover .ds-tag-ticker-track,
.ds-tag-ticker:focus-within .ds-tag-ticker-track {
    animation-play-state: paused;
}

.ds-tag-ticker-item {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    padding: 0.3rem 0.9rem;
    margin-inline-end: 1.5rem;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.16);
    color: var(--ds-white);
    font-size: var(--ds-fs-xs);
    font-weight: 600;
    white-space: nowrap;
    text-decoration: none;
    transition: background-color var(--ds-transition);
}

.ds-tag-ticker-item:hover,
.ds-tag-ticker-item:focus-visible {
    background: rgba(255, 255, 255, 0.3);
    color: var(--ds-white);
    text-decoration: none;
}

@keyframes ds-tag-ticker-scroll {
    from {
        transform: translateX(0);
    }

    to {
        /* POSITIVE, not negative (2026-09-02, confirmed live by the site
           owner via DevTools). translateX is physical, but the TRACK's own
           content is right-anchored: it's a flex container inside an
           RTL-direction page, so copy 1 (the real/focusable one) lays out as
           the RIGHTMOST content, flush against the ticker's right edge, with
           nothing rendered further right. Moving the track in the NEGATIVE
           direction drags that right edge further left, exposing empty
           background there almost immediately — the "gap right after the
           first tag" that kept getting reported. Moving it POSITIVE instead
           slides the already-rendered extra copies (which extend far to the
           left — see initTagTicker()'s ensureEnoughBuffer()) into view from
           that side, where the buffer math actually guarantees content.
           20% (100% / 5, matching PHP's baseline 5 copies) is kept here ONLY
           as a no-JS fallback. assets/js/theme.js's initTagTicker() clones as
           many EXTRA duplicate copies as the current window needs (see the
           section header comment above) and overwrites --ds-tag-ticker-shift
           with the exact rendered pixel width of one copy, which wins over
           this percentage whenever the script runs — that way the loop point
           is correct no matter how many total copies ended up in the DOM. */
        transform: translateX(var(--ds-tag-ticker-shift, 20%));
    }
}

/* Respect reduced-motion: no auto-scrolling, fall back to a manually
   swipeable/scrollable strip instead of a static wall of text. */
@media (prefers-reduced-motion: reduce) {
    .ds-tag-ticker-track {
        animation: none;
        overflow-x: auto;
    }

    /* Only the first (real, focusable) copy of the tag list is meant to be
       seen/used — the other 4 exist purely to keep the auto-scroll loop
       gap-free (see section header comment above) and are already marked
       aria-hidden. Without this they'd otherwise become visible too while
       manually scrolling here, showing the same tags repeated 5 times. */
    .ds-tag-ticker-item[aria-hidden="true"] {
        display: none;
    }
}

/* Responsive tuning — same breakpoints used elsewhere in the theme
   (components.css). The wide desktop gap/fade would eat a big chunk of a
   narrow phone screen and make each loop feel mostly empty, so both shrink
   step by step; the animation itself needs no adjustment since it's a
   transform on a fixed-content track, not something that reflows. */
@media (max-width: 768px) {
    .ds-tag-ticker-item {
        margin-inline-end: 1.1rem;
    }

    .ds-tag-ticker::before,
    .ds-tag-ticker::after {
        width: 40px;
    }
}

@media (max-width: 576px) {
    .ds-tag-ticker {
        padding-block: 0.5rem;
    }

    .ds-tag-ticker::before,
    .ds-tag-ticker::after {
        width: 28px;
    }

    .ds-tag-ticker-item {
        padding: 0.28rem 0.75rem;
        margin-inline-end: 0.85rem;
        font-size: var(--ds-fs-xs);
    }
}

/* =========================================================
   Homepage: Product slider sections (§15-17)
   ========================================================= */
.ds-product-slider-section .section-title {
    margin-bottom: 0;
}

.ds-see-all-link {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: var(--ds-fs-sm);
    font-weight: 600;
    color: var(--ds-primary-dark);
    background: transparent;
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-sm);
    padding: 0.55rem 1rem;
    white-space: nowrap;
    transition: border-color var(--ds-transition), color var(--ds-transition), gap var(--ds-transition);
}

.ds-see-all-link::after {
    content: "";
    width: 7px;
    height: 7px;
    border-style: solid;
    border-color: currentColor;
    border-width: 0 1.4px 1.4px 0;
    transform: rotate(135deg);
    flex-shrink: 0;
    transition: transform var(--ds-transition);
}

.ds-see-all-link:hover,
.ds-see-all-link:focus-visible {
    color: var(--ds-primary);
    border-color: var(--ds-primary);
    gap: 0.75rem;
}

.ds-product-swiper {
    padding-block: 0.5rem;
    padding-inline: 4px;
}

/* Wrapper that owns the nav arrows + pagination as SIBLINGS of the swiper root (not
   children of it) — the swiper root itself has overflow:hidden by default (needed to
   properly clip/mask sliding cards), so anything meant to sit visually outside the
   product images must live outside that box instead of inside it. */
.ds-product-slider-wrap {
    position: relative;
    padding-inline: 0;
}

@media (min-width: 992px) {
    .ds-product-slider-wrap {
        padding-inline: 46px;
    }
}

/*
 * IMPORTANT: these buttons/pagination keep the "swiper-button-next/prev" and
 * "swiper-pagination" classes so Swiper's JS (navigation/pagination modules) can find
 * and operate them — but ALL visual styling is driven by our own ".ds-slider-nav" /
 * ".ds-slider-pagination" classes below, with every position-related property reset
 * explicitly. This sidesteps Swiper's bundled CSS entirely instead of trying to win a
 * cascade fight against it: Swiper's own RTL flip rules only ever target elements that
 * are still DESCENDANTS of its ".swiper-rtl"-classed root, and since these buttons now
 * live outside that root (see note above), they never receive that automatic flip —
 * left unresolved, that produced the exact "both buttons collapse onto the same spot"
 * bug. Every offset below is therefore set to an explicit value (never left implicit),
 * so nothing from Swiper's stylesheet can leak through.
 */
.ds-slider-nav {
    box-sizing: border-box;
    display: none;
    position: absolute !important;
    inset-block-start: 38% !important;
    inset-block-end: auto !important;
    inset-inline-start: auto !important;
    inset-inline-end: auto !important;
    transform: translateY(-50%) !important;
    margin: 0 !important;
    padding: 0;
    border: none;
    cursor: pointer;
    align-items: center;
    justify-content: center;
    color: var(--ds-primary-dark);
    background: var(--ds-white);
    width: 38px !important;
    height: 38px !important;
    border-radius: 50%;
    box-shadow: var(--ds-shadow-sm);
    z-index: 5 !important;
    transition: background-color var(--ds-transition), color var(--ds-transition);
}

@media (min-width: 992px) {
    .ds-slider-nav {
        display: inline-flex;
    }
}

.ds-slider-nav-prev {
    /* inset-inline-start resolves to the RIGHT edge under RTL — "previous" sits on
       the right, matching standard RTL carousel convention. */
    inset-inline-start: 0 !important;
}

.ds-slider-nav-next {
    /* inset-inline-end resolves to the LEFT edge under RTL — "next" sits on the left. */
    inset-inline-end: 0 !important;
}

.ds-slider-nav::after {
    content: "";
    width: 8px;
    height: 8px;
    border-style: solid;
    border-color: currentColor;
    border-width: 0 1.6px 1.6px 0;
}

.ds-slider-nav-prev::after {
    /* Sits on the right edge — points further right, toward previous items. */
    transform: rotate(-45deg) translate(-1px, -1px);
}

.ds-slider-nav-next::after {
    /* Sits on the left edge — points further left, toward next items. */
    transform: rotate(135deg) translate(-1px, -1px);
}

.ds-slider-nav:hover {
    background: var(--ds-primary);
    color: var(--ds-white);
}

.ds-slider-nav:focus-visible {
    outline: 2px solid var(--ds-primary);
    outline-offset: 2px;
}

.ds-slider-nav.swiper-button-disabled {
    opacity: 0.35;
    pointer-events: none;
}

/* Pagination dots: forced into normal document flow BELOW the slides (not the
   library's default absolute-over-content position), with clear breathing room so
   they never sit on top of product cards. */
.ds-slider-pagination {
    display: flex !important;
    position: static !important;
    inset: auto !important;
    width: auto !important;
    margin-top: 1.5rem !important;
    margin-inline: 0 !important;
    padding: 0;
    justify-content: center;
    gap: 0.4rem;
}

/* Higher specificity than the rule above (two classes vs one) so that when Swiper's
   watchOverflow locks the pagination (not enough slides to need it — e.g. a related-
   products list with only 1-3 items), it actually stays hidden instead of our own
   "!important; display:flex" winning the tie and showing empty/useless dots. */
.ds-slider-pagination.swiper-pagination-lock {
    display: none !important;
}

.ds-slider-pagination .swiper-pagination-bullet {
    margin: 0 !important;
}

.ds-slider-pagination .swiper-pagination-bullet-active {
    background: var(--ds-primary);
    opacity: 1;
}

/* =========================================================
   Homepage: Latest posts / News section — distinct, slightly richer background
   than the plain .ds-bg-alt tint used elsewhere, so it reads as its own clearly
   separated section rather than blending into the generic alt-background blocks
   above/below it. Still zero new colors: a soft diagonal blend between the two
   tones already used for .ds-bg-alt and the page background.
   ========================================================= */
.ds-bg-news {
    background: linear-gradient(160deg, var(--ds-primary-light) 0%, var(--ds-background) 55%, var(--ds-primary-light) 100%);
    padding-block: 0.5rem;
}

/* =========================================================
   Homepage: "بیشترین تخفیف‌ها" section. First attempt here used a repeating
   diagonal-stripe texture on top of the gradient (a "sale banner" pattern) —
   too busy and out of step with the calm, flat backgrounds every other
   section on the site uses (.ds-bg-alt, .ds-bg-news above). Replaced with the
   exact same simple soft-gradient treatment as .ds-bg-news, just tinted
   toward the warm/red tokens instead of green — consistent with the rest of
   the site, with the color shift (plus the small tag-icon badge next to the
   title) doing the "this section is different" signaling on its own.
   ========================================================= */
.ds-bg-discounts {
    background: linear-gradient(160deg, color-mix(in srgb, var(--ds-danger) 8%, var(--ds-background)) 0%, var(--ds-background) 55%, color-mix(in srgb, var(--ds-danger) 8%, var(--ds-background)) 100%);
    padding-block: 0.5rem;
}

.ds-bg-discounts .section-title {
    color: var(--ds-danger);
}

.ds-discounts-title {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.ds-discounts-title-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    background: var(--ds-danger);
    color: var(--ds-white);
    flex-shrink: 0;
}

.ds-discounts-title-badge .ds-icon {
    width: 18px;
    height: 18px;
}

.ds-latest-posts .section-title {
    margin-bottom: 0;
}

.ds-latest-posts .ds-blog-grid {
    margin-top: 1.5rem;
    position: relative;
    z-index: 1;
}

.ds-latest-posts .d-flex {
    position: relative;
    z-index: 1;
}

/* ---- Featured post card (newest post, image + text side by side; stacks on
   mobile) — same rounded-corner/border/shadow language as .ds-blog-card and
   .ds-product-card so it reads as one visual family, not a one-off design. ---- */
.ds-blog-featured-card {
    position: relative;
    z-index: 1;
    margin-top: 1.75rem;
    display: flex;
    flex-direction: column;
    background: var(--ds-white);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-radius-lg);
    overflow: hidden;
    box-shadow: var(--ds-shadow-sm);
    transition: box-shadow var(--ds-transition), transform var(--ds-transition), border-color var(--ds-transition);
}

.ds-blog-featured-card:hover {
    box-shadow: var(--ds-shadow-lg);
    border-color: transparent;
    transform: translateY(-4px);
}

.ds-blog-featured-image-link {
    display: block;
    aspect-ratio: 16 / 10;
    overflow: hidden;
    background: var(--ds-primary-light);
}

.ds-blog-featured-image-link img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.5s ease;
}

.ds-blog-featured-card:hover .ds-blog-featured-image-link img {
    transform: scale(1.05);
}

.ds-blog-featured-body {
    padding: 1.5rem;
    display: flex;
    flex-direction: column;
}

.ds-blog-featured-title {
    font-size: var(--ds-fs-xl);
    line-height: 1.4;
    margin-bottom: 0.75rem;
    transition: color var(--ds-transition);
}

.ds-blog-featured-card:hover .ds-blog-featured-title {
    color: var(--ds-primary);
}

.ds-blog-featured-excerpt {
    color: var(--ds-muted);
    line-height: 1.85;
    margin-bottom: 1.25rem;
}

.ds-blog-featured-readmore {
    align-self: flex-start;
}

@media (min-width: 768px) {
    .ds-blog-featured-card {
        flex-direction: row;
    }

    .ds-blog-featured-image-link {
        flex: 1 1 55%;
        aspect-ratio: auto;
        min-width: 0;
    }

    .ds-blog-featured-body {
        flex: 1 1 45%;
        justify-content: center;
        padding: 2rem;
    }

    .ds-blog-featured-title {
        font-size: var(--ds-fs-2xl);
    }
}

/* ---- Shared date/category/reading-time row, used above the title on both the
   featured card and the regular cards below it. Scoped to .ds-latest-posts so the
   plain-text treatment here doesn't affect .ds-blog-card-category's pill-badge
   styling on the actual blog archive page (template-parts/blog/content.php). ---- */
.ds-latest-posts .ds-blog-card-meta-row {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: var(--ds-fs-xs);
    color: var(--ds-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-bottom: 0.6rem;
}

.ds-latest-posts .ds-blog-card-meta-sep {
    color: var(--ds-border);
}

.ds-latest-posts .ds-blog-card-category {
    display: inline;
    background: none;
    padding: 0;
    border-radius: 0;
    color: var(--ds-muted);
    font-weight: 600;
}

.ds-latest-posts .ds-blog-card-reading-time {
    white-space: nowrap;
}

@media (max-width: 575.98px) {
    .ds-hero-content {
        max-width: 100%;
    }
}