/* ==========================================================================
   BeautyVerse — custom CSS for the Prestige theme
   Paste this WHOLE file into: Dashboard → Store Design → Prestige → CSS editor
   ========================================================================== */

/* --- Video banner ------------------------------------------------------- */
.prestige-video-banner {
    padding-bottom: 2.5rem;
    height: 65vh;
}

/* --- Section titles ----------------------------------------------------- */
.s-block__title {
  justify-content: start;
}

/* FIX #4: was a hard 50px, which overflowed on phones.
   clamp() scales between 26px (mobile) and 50px (desktop). */
.s-block__title h2 {
  color: #0a2a52;
  font-size: clamp(1.625rem, 1.1rem + 2.6vw, 3.125rem);
  line-height: 1.2;
}

.s-block__title .right-side:where([dir=rtl],[dir=rtl] *) {
    padding-left: 0;
}

/* --- Banners ------------------------------------------------------------ */
/* FIX: removed `.s-block--banners .grid { grid-template-columns: repeat(auto-fit,
   minmax(0px, 1fr)) }` — minmax(0px, 1fr) gives auto-fit no real floor, so it
   was generating dozens of near-zero-width tracks instead of letting the
   theme's own Tailwind classes (grid-cols-1 md:grid-cols-3 lg:grid-cols-3)
   lay the grid out correctly. No replacement rule needed. */
.s-block--banners .lazy__bg {
    background-size: contain;
    background-repeat: no-repeat;
}
.s-block--banners .banner-entry {
    height: 500px;
}
@media (max-width: 992px) {
    .s-block--banners .banner-entry {
        height: 200px;
    }
}
@media (max-width: 480px) {
    .s-block--banners .banner-entry {
        height: 200px;
    }
    .prestige-video-banner {
        padding-bottom: 2.5rem;
        height: 40vh;
    }
}
@media (min-width: 640px) {
    .two-row .banner-entry:first-child {
        grid-row: span 1 / span 1;
    }
}

/* Transparent banner backgrounds */
.banner-entry,
.banner--fixed img {
    background-color: transparent !important;
}

.prestige-banner-2 .text.absolute {
    --tw-translate-y: 50%;
    --tw-translate-x: -50%;
    left: 50%;
    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

/* --- Layering ----------------------------------------------------------- */
/* رفع طبقة القائمة المنسدلة لتظهر فوق البانر في الصفحة الرئيسية */
.sub-menu {
    z-index: 99 !important;
}
/* لضمان بقاء الهيدر بالكامل فوق عناصر الصفحة */
.store-header {
    position: relative;
    z-index: 100 !important;
}

/* --- Section 2 banner images (mobile) ----------------------------------- */
@media (max-width: 767px) {
  /* Position-independent: was `.prestige-banner-2-1`/`.prestige-banner-2-2`
     (the numeric suffix is the component's dashboard sort position, not a
     fixed instance id — reordering homepage blocks changes it). Targeting
     the shared `.prestige-banner-2` base class instead, matching the
     existing `.prestige-banner-2 .text.absolute` rule above. */
  .prestige-banner-2 .grid {
    max-width: 390px;
    margin: 0 auto;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    row-gap: 0.75rem !important;
  }

  .prestige-banner-2 .grid > div {
    margin: 0 !important;
  }

  /* 2026-08-27 (corrected): the six concern tiles are merged into ONE grid by
     mergeConcernTileGrids() in custom.js — see that function for why CSS
     alone cannot merge two sibling grid containers.

     This rule is the FALLBACK for the case where that JS has not run yet, or
     fails: an unmerged grid holds 3 tiles in 2 columns, which leaves an empty
     4th cell. Letting the 3rd tile span both columns fills it. Scoped with
     :not([data-bv-merged]) so it switches itself off the moment the merge
     lands — without that guard it would fire on the merged 6-tile grid too
     and blow the 3rd tile up to double width. */
  .prestige-banner-2 .grid:not([data-bv-merged]) > div:nth-child(3) {
    grid-column: span 2 / span 2;
    aspect-ratio: 16 / 9 !important;
  }

  /* F1 fix: concern-name h3 must visually outweigh its "هل تعاني من" eyebrow
     span below (.prestige-banner-2 .text > span). Was 14px, equal to/less than
     the eyebrow's 16px floor rule — raised to 18px to restore hierarchy. */
  .prestige-banner-2 .grid h3 {
    font-size: 18px !important;
    line-height: 1.3 !important;
  }

  .prestige-banner-2 .grid span {
    font-size: 11px !important;
  }

  .prestige-banner-2 .grid .text {
    padding: 0 8px !important;
    white-space: normal !important;
    word-break: break-word !important;
  }

}

/* Arabic 14px floor: concern tiles (exactly 6 elements, confirmed live).
   The deployed rule above (`.prestige-banner-2 .grid span { font-size: 11px
   !important }`) has identical specificity (0,2,0) to this override, so it
   must both (a) come later in the cascade and (b) also carry !important to
   win — specificity alone won't decide it.

   F1 fix: this eyebrow ("هل تعاني من") was raised to 16px, which made it equal
   to or larger than the concern-name h3 it labels — inverting the intended
   hierarchy. Lowered to 14px (still above the un-readable 11px floor it
   overrides) so the h3 above (now 18px) reads as clearly dominant. */
@media (max-width: 767px) {
  .prestige-banner-2 .text > span {
    font-size: 14px !important;
  }
}

/* ==========================================================================
   CONCERN TILES — merge two components into one uniform 6-tile grid
   (2026-08-27, Abdulhadi)

   Two separate 3-tile "featured squares" components render one after another
   (6 concern tiles total). Both already share one grid style via the rule
   above, but each component's own wrapper div also carries the theme's
   `pt-6` (24px top padding) — on top of the 12px row-gap already inside each
   grid — so the seam between tile 3 and tile 4 was double the gap between
   tile 1 and tile 2, reading as "two sections" instead of one.

   General-sibling selector (~), not adjacent-sibling (+): the theme inserts a
   <style> tag between the two wrapper divs, so + would never match. Dropping
   every LATER wrapper's own top padding down to the grid's own row-gap makes
   all 6 tiles read as one continuous grid — no DOM changes, no risk to the
   first component's spacing below the header above it. */
.prestige-banner-2 ~ .prestige-banner-2 {
    padding-top: 0.75rem !important;
}

/* --- Misc --------------------------------------------------------------- */
li:has(img[alt="mahally_customer_wallet"]) {
    display: none !important;
}

/* Header nav "Brands" item: empty <span>, no text, no href — renders as a
   blank clickable gap. Hidden pre-launch until brands content exists.
   Reversible: delete this rule to bring it back.
   Broadened to [id*="brands"]: the desktop nav li has id="brands", but the
   mobile hamburger-menu duplicate has its id double-escaped in the parsed
   DOM (literally id="&quot;brands&quot;"), so #brands alone doesn't match
   it. Attribute-contains catches both without matching unrelated ids. */
li[id*="brands"] {
    display: none !important;
}

@media (max-width: 480px) {
  .blog-single .object-cover {
    object-fit: contain;
    margin-bottom: -50px;
  }
}
@media (min-width: 1024px) {
  .blog-single .object-cover {
    height: 450px;
  }
}

/* --- Footer ------------------------------------------------------------- */
/* FIX #2: the footer grid is now defined ONLY here. custom.js used to set the
   same properties inline as well, which meant two sources of truth for one
   layout. The JS now only MOVES nodes; all sizing lives in this block. */
footer .grid.lg\:grid-cols-4.md\:grid-cols-2.grid-cols-1 > div:nth-child(4) {
  display: none !important;
}

footer .grid {
  display: grid !important;
  grid-template-columns: repeat(3, 1fr) !important;
  gap: 24px !important;
}

@media (max-width: 1024px) {
  footer .grid { grid-template-columns: repeat(2, 1fr) !important; }
}
@media (max-width: 640px) {
  footer .grid { grid-template-columns: 1fr !important; }
}

/* زيادة المسافة بين العمود الأول والتاني فقط */
footer .grid > div:nth-child(2) {
  margin-inline-start: 100px;
}
@media (max-width: 1024px) {
  /* the 100px indent breaks the 2-col and 1-col layouts */
  footer .grid > div:nth-child(2) { margin-inline-start: 0; }
}

/* FIX #1: this used to be a bare `.mb-2 { margin-top: 1rem }`, which overrode
   Tailwind's utility class site-wide — every .mb-2 element on every page got an
   unintended top margin. Its only real target was the footer's social list, so
   it is now scoped to exactly that. (custom.js no longer sets this inline.) */
footer .mb-2 {
  margin-top: 1rem;
}

/* --- Footer horizontal overflow fix (mobile) --------------------------- */
/* storefront-diagnose measured (2026-08-25) document.scrollWidth at 386px
   against a 375px mobile viewport (11px overflow), traced to four footer
   elements — div.info, div.footer-list, p.max-w-sm, and the social icon
   row ul.flex.items-stretch.space-x-2 — all sitting at left:-11px,
   width:328px. These are nested inside one footer contacts column, so a
   single upstream offset (margin, `left`, or a percentage-width calc —
   not independently isolated here, only the measured symptom was handed
   off) is most likely being inherited down through width:100% children.
   Rather than guess which of the three mechanisms it is, reset all of
   them together on the four measured elements, and back that with
   overflow-x:hidden on the footer itself so it cannot contribute to
   page-level scrollWidth regardless of which mechanism turns out to be
   the real cause. */
footer {
  max-width: 100vw;
  overflow-x: hidden;
}
@media (max-width: 480px) {
  footer .info,
  footer .footer-list,
  footer .info p.max-w-sm,
  footer ul.space-x-2 {
    left: 0 !important;
    margin-inline-start: 0 !important;
    width: 100% !important;
    max-width: 100% !important;
    box-sizing: border-box !important;
  }
}

/* ==========================================================================
   FIX #5 — Routine section
   The "HTML Content" app block (bundle 1078728572) refuses to render because
   this store lacks the app's required "CSS Custom" feature; it printed an error
   to visitors instead. custom.js now injects this markup itself, so the content
   is restored without depending on that app.

   NOTE: every selector is scoped under .bv-routine-section. The original app
   markup used bare .routine-card / .img-box / .content / .btn, which are far too
   generic to leave global on a live theme.
   ========================================================================== */
.bv-routine-section {
    /* 1280px, NOT 1300px — measured 2026-08-28 on the live homepage at a
       1897px viewport: every theme container (.container.mx-auto,
       .s-block--banners, the blog list) computes to exactly 1280px and starts
       at left:309. At 1300px this section started at left:299, so it hung 10px
       past every neighbouring section on BOTH sides — a small, constant
       misalignment down the whole page. Match the theme's container; do not
       reintroduce a bespoke width here. */
    max-width: 1280px;
    margin: 50px auto;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    border: 1px solid #ddd;
    background: #fff;
}

.bv-routine-section .bv-routine-card {
    position: relative;
    padding: 35px 25px;
    text-align: center;
    border-inline-start: 1px solid #e6e6e6;
    transition: box-shadow .35s;
    background: linear-gradient(180deg, #E7E6E6 0%, rgba(255, 255, 255, 0) 100%);
}

/* RTL grid: CSS Grid does not reverse DOM order in RTL, only the inline
   axis direction — so the DOM-first card lands in the visually-rightmost
   column, and border-inline-start (= right edge in RTL) on that card is
   redundant against the grid container's own border. :first-child (not
   :last-child) is the one that needs its inline-start border removed. */
.bv-routine-section .bv-routine-card:first-child {
    border-inline-start: none;
}

.bv-routine-section .bv-routine-card:hover {
    box-shadow: 0 15px 35px rgba(0, 0, 0, .08);
    z-index: 2;
}

.bv-routine-section .bv-img-box {
    position: relative;
    width: 260px;
    height: 260px;
    margin: auto;
    display: flex;
    align-items: center;
    justify-content: center;
}

.bv-routine-section .bv-img-box::before {
    content: "";
    position: absolute;
    width: 220px;
    height: 220px;
    border: 1px solid #d9d9d9;
    border-radius: 50%;
}

.bv-routine-section .bv-img-box img {
    width: 100%;
    max-width: 220px;
    position: relative;
    z-index: 2;
}

.bv-routine-section .bv-routine-content h2 {
    margin-top: 20px;
    font-size: 34px;
    line-height: 1.25;
    font-weight: 800;
    color: #111;
}

.bv-routine-section .bv-routine-content p {
    margin: 18px auto;
    max-width: 320px;
    font-size: 16px;
    color: #666666;
    line-height: 1.9;
}

.bv-routine-section .bv-routine-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    background: #111;
    color: #fff;
    text-decoration: none;
    padding: 11px 28px;
    font-size: 14px;
    border-radius: 4px;
    transition: background .3s;
}

.bv-routine-section .bv-routine-btn:hover {
    background: #444;
    color: #fff;
}

@media (max-width: 992px) {
    .bv-routine-section {
        grid-template-columns: 1fr;
    }
    .bv-routine-section .bv-routine-card {
        border-inline-start: none;
        border-block-end: 1px solid #eee;
    }
    .bv-routine-section .bv-routine-card:last-child {
        border-block-end: none;
    }
    .bv-routine-section .bv-routine-content h2 {
        font-size: 28px;
    }
}

@media (max-width: 576px) {
    .bv-routine-section .bv-routine-card {
        padding: 25px 18px;
    }
    .bv-routine-section .bv-img-box {
        width: 200px;
        height: 200px;
    }
    .bv-routine-section .bv-img-box::before {
        width: 170px;
        height: 170px;
    }
    .bv-routine-section .bv-img-box img {
        max-width: 170px;
    }
    .bv-routine-section .bv-routine-content h2 {
        font-size: 24px;
    }
    .bv-routine-section .bv-routine-content p {
        font-size: 16px;
    }
    .bv-routine-section .bv-routine-btn {
        width: 100%;
    }
}

/* ==========================================================================
   FIX #3 — doubled section margin above the routine block
   Markup nests .s-block inside .s-block:
     section.s-block--bundle-html-content (has .s-block, margin-top 64/32px)
       └ salla-html-content-<random>.s-block.s-block-html-content (also .s-block)
   which doubles the section-to-section gap to 128/64px. Zero the inner one
   only — do not touch `.s-block` margin globally.
   ========================================================================== */
section.s-block--bundle-html-content > .s-block-html-content {
    margin-top: 0;
}

/* ==========================================================================
   FIX #4 — empty personalized-shoppers section reserves a gap
   Pre-launch: this block has no purchase history to show yet, so it renders at
   0 height — but its .s-block margin still reserves the gap. Revisit once the
   store has orders and the block renders real content.
   ========================================================================== */
section.s-block--bundle-personalized-shoppers-who-bought {
    margin-top: 0;
}

/* ==========================================================================
   FIX #2 — routine block, NATIVE markup (section.routine-section > div.routine-card)
   The app block now renders correctly on its own using bare global classes
   (.routine-card, .img-box, .content, .btn) shipped by its own inline <style>.
   Those class names are too generic to leave unscoped on a live theme, so
   every rule below is chained through the real ancestor
   `section.routine-section` to prevent collisions elsewhere on the site.
   These are separate from, and do not touch, the .bv-routine-* fallback above.
   ========================================================================== */

/* 2a: CTA buttons misaligned when one card's <h2> wraps to 2 lines and others
   wrap to 1 — .routine-card is display:block, so the .btn sits at whatever
   height content happens to push it to. Make the card (or its .content child,
   whichever wraps img-box + text) a column flex box so all three cards share
   equal height, and pin the button to the bottom regardless of heading length. */
section.routine-section .routine-card {
    display: flex;
    flex-direction: column;
}
section.routine-section .routine-card .content {
    display: flex;
    flex-direction: column;
    flex: 1;
}
section.routine-section .routine-card .btn {
    margin-top: auto;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* 2b: .img-box is a hard 260×260 (200×200 ≤576px) circle frame, but
   .img-box img only capped max-width, not height, so an oversized source
   image could render taller than the box and break the circle. Cap height to
   match the decorative ::before circle (220px desktop / 170px mobile) and use
   object-fit so all three images crop/scale consistently instead of
   distorting. */
section.routine-section .img-box img {
    max-height: 220px;
    object-fit: contain;
}
@media (max-width: 576px) {
    section.routine-section .img-box img {
        max-height: 170px;
    }
}

/* 2c: at ≤576px .routine-section sits flush left (0px) while every sibling
   homepage section is inset 10px by the theme's own container padding.
   Constrain width/margin to match that inset without touching the desktop
   `max-width:1300px; margin:50px auto` the native block already sets. Still
   applies with the 2d horizontal-scroll row below: it keeps the row's edges
   lined up with sibling sections, same as it did for the vertical stack. */
@media (max-width: 576px) {
    section.routine-section {
        width: calc(100% - 20px);
        margin-inline: 10px;
    }
}

/* 2d: the app's own inline <style> collapses .routine-section to a
   single-column grid at ≤576px (grid-template-columns: 1fr), stacking all
   three cards vertically at ~458.8px tall each — a visitor has to scroll
   ~1.8 viewport-heights to see the whole set. Override to a horizontal
   scroll-snap row instead, same pattern used elsewhere on this file for
   one-card-at-a-time mobile stacks. Card width is 85% (not the usual ~42%)
   because these are content-heavy cards (circular image + h2 + paragraph +
   full-width button), not small tiles: 85% shows ~1.15 cards at 375px, just
   enough sliver of the next card to signal "swipe for more" without
   cramming the card's own content into too little width. This does not
   touch the 2a column-flex / margin-top:auto button fix on .routine-card
   itself — that still runs per-card, stretched to equal height by this
   row's default flex `align-items: stretch`. */
@media (max-width: 576px) {
    section.routine-section {
        display: flex;
        grid-template-columns: none;
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        -webkit-overflow-scrolling: touch;
        gap: 12px;
    }
    section.routine-section::-webkit-scrollbar {
        display: none;
    }
    section.routine-section .routine-card {
        flex: 0 0 auto;
        width: 85%;
        scroll-snap-align: start;
    }
    section.routine-section .routine-card:last-child {
        border-block-end: none;
    }
}

/* ==========================================================================
   ACCESSIBILITY — 2026-08-23 touch-target / contrast / typography pass
   Selectors verified live by storefront-diagnose against the deployed DOM.
   ========================================================================== */

/* --- Hero CTA touch target ------------------------------------------------ */
/* Desktop is already 62px tall — untouched. Mobile measured at 46px, below
   the 48px floor requested for this primary CTA. */
@media (max-width: 767px) {
    .prestige-video-banner-header .prestige-video-button {
        min-height: 48px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
}

/* --- Header icon buttons -------------------------------------------------- */
/* All three measured ~24×25px with 0 padding. These are inline icon
   containers (not block buttons), so padding is used to reach 44×44 rather
   than min-height alone, keeping the icon centred inside the larger box.
   `.mburger` is mobile-only (desktop hides it via the theme's own CSS), the
   search button and language div apply at all widths since neither is
   hidden at desktop. */
@media (max-width: 767px) {
    .store-header a.mburger {
        min-width: 44px;
        min-height: 44px;
        padding: 10px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
}
.store-header button:has(> i.sicon-search) {
    min-width: 44px;
    min-height: 44px;
    padding: 10px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.store-header div:has(> i.sicon-lang) {
    min-width: 44px;
    min-height: 44px;
    padding: 10px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* --- Header icon cluster overflow fix (mobile only) ------------------------
   2026-08-23: the 44px boxes above (10px padding, border-box) plus
   Tailwind's gap-4 (16px) push the 3-icon cluster's required row width to
   ~412px against a 375px viewport, shoving salla-cart-summary to a negative
   x-offset (clipped by an ancestor's overflow-x:hidden — invisible right now
   only because that element isn't hydrated in the test env; it is a live
   bug for real visitors once the cart populates).
   min-width:44px on a border-box element is a hard floor on the *total*
   box (content+padding) — reducing padding alone while min-width:44px is
   still set does NOT shrink the box, it just gets forced back to 44 by the
   min-width. Both must be overridden together.
   Fix: shrink the icons back toward their natural ~24px visual size and
   restore the true 44x44 tap target with an invisible ::before hit-slop
   pseudo-element instead of inflating the visible/layout box.
   Math (375px viewport): cart 40 + account 40 + logo 144 + 3×32(icon) +
   2×8(gap) + 2×16(header padding) = 368px — 7px under budget.
   Caveat: the ::before hit-slop (inset:-6px) expands 6px into the 8px gap
   from each side (12px vs 8px available), so adjacent icons' invisible hit
   regions overlap by ~4px in the middle of the gap. Negligible in practice
   (a sliver far smaller than a fingertip), but noting it rather than
   presenting the fix as perfectly seamless.
   NOTE: `.store-header .flex.items-center.justify-center.gap-4` (the icon
   cluster wrapper) is taken directly from the audit's measurements, not
   independently re-verified against the live DOM by this pass — confirm it
   still matches before relying on it long-term. */
@media (max-width: 767px) {
    .store-header .flex.items-center.justify-center.gap-4 {
        gap: 8px;
    }
    .store-header a.mburger,
    .store-header button:has(> i.sicon-search),
    .store-header div:has(> i.sicon-lang) {
        min-width: 32px;
        min-height: 32px;
        padding: 4px;
        position: relative;
    }
    .store-header a.mburger::before,
    .store-header button:has(> i.sicon-search)::before,
    .store-header div:has(> i.sicon-lang)::before {
        content: '';
        position: absolute;
        inset: -6px;
    }
}

/* --- Routine card body text contrast -------------------------------------- */
/* The offending #777 rule is defined in the app block's own inline <style>
   tag (`.content p`, specificity 0,1,1) — NOT the `.bv-routine-content p`
   fallback above, which is a different, already-fine block. This selector
   (0,3,1) outranks the source rule on specificity alone, no cascade-order
   dependency, no !important needed. #666666 measured at 5.31:1 against the
   card's real gradient background (#777 only reached 4.14:1). */
section.routine-section .routine-card .content p {
    color: #666666;
}

/* --- Arabic 16px floor: product-section subtitles ------------------------- */
/* .products-prestige > p is 14px only below 768px; already 16px at desktop. */
@media (max-width: 767px) {
    .products-prestige > p {
        font-size: 16px;
    }
}

/* --- Arabic 16px floor: main nav items ------------------------------------ */
/* NOT scoped under .store-header: the mmenu plugin physically relocates this
   nav to div.mm-ocd__content nav#mobile-menu at ≤375px, so a header-scoped
   selector would silently no-op on mobile. font-size is set on the <li>
   itself — the text-sm class lives there, the inner <a> only inherits it.
   Specificity (0,1,2) beats the bare .text-sm (0,1,0) utility, no !important
   needed. Applies at both breakpoints per the diagnosis report. */
ul.main-menu li {
    font-size: 16px;
}
/* The <li>'s font-size doesn't reach the visible text: the <a> inside it
   paints at its own 14px on desktop (confirmed live @1280 — 16px only held
   on mobile), so it needs the same fix directly. */
ul.main-menu li a {
    font-size: 16px;
}

/* ==========================================================================
   PRODUCT CARD
   Targets Prestige's real markup:
     custom-salla-product-card
       └ .prestige-card[.prestige-card-outofstock]
           ├ .prestige-label                    (out-of-stock badge)
           ├ a > .img > img                     (square image + hover overlay)
           └ .flex.flex-col.mt-4                (name, price, text add-button)

   Measured on the live store before writing:
     · names need max 3 lines at 149px (mobile) and 2 lines at 234px (desktop)
     · the add-to-cart button renders 0x0 and is hover-only, so it is unusable
       on touch — the theme already ships a full-width text variant
       (.prestige-product-card-content-text) that is simply set to `hidden`
     · text contrast is 16.33:1 — left alone deliberately
   ========================================================================== */

/* --- Card shell: define the box so a ragged grid reads as a set ---------- */
custom-salla-product-card .prestige-card {
    text-align: start;                 /* was centre; start-aligned scans better in Arabic */
    background: #fff;
    border: 1px solid #e3e1dd;
    border-radius: 8px;
    overflow: hidden;
    height: 100%;
    transition: border-color .2s, box-shadow .2s, transform .2s;
}

custom-salla-product-card .prestige-card:hover {
    border-color: #cfcbc4;
    box-shadow: 0 10px 24px rgba(0, 0, 0, .07);
    transform: translateY(-2px);
}

custom-salla-product-card .prestige-card .img img {
    transition: transform .4s ease;
}
custom-salla-product-card .prestige-card:hover .img img {
    transform: scale(1.05);
}

/* Out of stock: mute the image so the badge is not the only signal */
custom-salla-product-card .prestige-card-outofstock .img img {
    filter: grayscale(.85) opacity(.65);
}

/* --- Text column -------------------------------------------------------- */
custom-salla-product-card .prestige-card .flex.flex-col.mt-4 {
    margin-top: 12px;
    padding: 0 12px 14px;
    flex: 1;
}

/* Category chip — injected by custom.js from product.category.name.
   Chosen over brand because category is filled on 16/16 products while brand
   is filled on 4/16. One metadata line only: two crowd a 173px mobile card. */
.bv-cat-chip {
    display: inline-block;
    align-self: flex-start;
    max-width: 100%;
    margin-bottom: 6px;
    padding: 2px 8px;
    border-radius: 100px;
    background: #f1efec;
    color: #6b6660;
    font-size: 11px;
    line-height: 1.5;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Product name — fixed line count, so every price in the row sits level.
   Mobile 3 lines / desktop 2: both measured to fit the whole catalogue. */
/* 2026-08-27: replaced the fixed `height: calc(N * 1.45em)` with
   `min-height` of the same value — a first pass that dropped it entirely
   fixed the mid-glyph clipping this was meant to fix, but broke the row-
   alignment this rule exists for in the first place: a SHORT name (fewer
   lines than the clamp) naturally sizes its own box smaller with no floor,
   so its price/button sat higher than its siblings' (harness caught this:
   heights [61,61,61,41] / [41,41,41,20], the short outlier every time).

   min-height gives short names the same floor as before (fixing alignment)
   while never FORCING a box smaller than what N lines actually need — a
   name needing exactly N (or clamped to N) lines is always >= that floor
   already, so min-height never fights line-clamp's own sizing the way a
   hard max-height did. Both problems solved at once, not traded off. */
custom-salla-product-card .prestige-card .flex.flex-col.mt-4 > a {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    box-orient: vertical;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    min-height: calc(3 * 1.45em);
    overflow: hidden;
    margin-bottom: 8px;
    font-size: 14px;
    font-weight: 500;
    line-height: 1.45;
}

@media (min-width: 768px) {
    custom-salla-product-card .prestige-card .flex.flex-col.mt-4 > a {
        -webkit-line-clamp: 2;
        line-clamp: 2;
        min-height: calc(2 * 1.45em);
    }
}

/* Price — was 14px/400, identical weight to the name. It is the second
   strongest element on the card and should read that way. */
custom-salla-product-card .prestige-card .flex.flex-col.mt-4 > span.text-sm {
    margin-top: auto;
    font-size: 18px;
    font-weight: 700;
    letter-spacing: -.01em;
}

/* --- Add to cart -------------------------------------------------------- */
/* The hover-only icon button is redundant once a permanent button exists,
   and it never had usable dimensions anyway. */
custom-salla-product-card .prestige-product-card-content-icon {
    display: none !important;
}

/* Un-hide the theme's own full-width text button and give it a 44px touch
   target (the WCAG 2.1 AA minimum). This is the fix that makes buying from
   the grid possible on a phone at all. */
custom-salla-product-card .prestige-product-card-content-text {
    display: flex !important;
    width: 100%;
    margin-top: 10px;
}
custom-salla-product-card .prestige-product-card-content-text button {
    width: 100%;
    min-height: 44px;
    border-radius: 6px;
}

/* ==========================================================================
   "قريباً" block — shown where a product section resolves empty.
   Injected by custom.js (renderEmptyProductSections). Self-retiring: once
   products are in stock the lists fill and this never renders.

   Neutral greys only — no palette has been approved. No star/sparkle mark.
   ========================================================================== */
.bv-soon {
    max-width: 620px;
    margin: 8px auto 24px;
    padding: 32px 24px;
    text-align: center;
    background: #faf9f7;
    border: 1px solid #e8e5e0;
    border-radius: 10px;
}

.bv-soon__title {
    margin: 0 0 8px;
    font-size: 17px;
    font-weight: 700;
    color: #231f1e;
    line-height: 1.5;
}

.bv-soon__text {
    margin: 0 0 20px;
    font-size: 16px;
    line-height: 1.9;
    color: #6b6660;
}

.bv-soon__actions {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
}

/* Primary action: the one thing that works with no stock at all. */
.bv-soon__cta {
    min-height: 44px;            /* WCAG 2.1 AA touch target */
    padding: 0 28px;
    background: #1a1a1a;
    color: #fff;
    border: 0;
    border-radius: 6px;
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: background .15s;
}
.bv-soon__cta:hover { background: #000; }
.bv-soon__cta:focus-visible { outline: 2px solid #1a1a1a; outline-offset: 3px; }

/* Secondary, deliberately quieter. */
.bv-soon__link {
    display: inline-flex;
    align-items: center;
    min-height: 32px;
    font-size: 13px;
    color: #6b6660;
    text-decoration: underline;
}
.bv-soon__link:hover { color: #231f1e; }

@media (max-width: 480px) {
    .bv-soon { padding: 26px 18px; margin-inline: 12px; }
    .bv-soon__cta { width: 100%; }
}

/* ==========================================================================
   PRODUCT PAGE (PDP)
   Measured on the live site 2026-08-23 against real products, not assumed.
   Every product description is Quill output with real <h2> headings
   ("مكونات...", "طريقة استخدام...") each followed by a <ul> — custom.js pulls
   those out into the sections styled below. No new merchant data needed.
   ========================================================================== */

/* --- Ingredients & usage steps, pulled out of the free-text description -- */
.bv-pdp-section-title {
    margin: 24px 0 12px;
    font-size: 16px;
    font-weight: 700;
    color: #231f1e;
}

.bv-ing-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* Same visual language as the product-card category chip (.bv-cat-chip) —
   one system, not a new one invented for this page. */
.bv-ing-chip {
    display: inline-block;
    padding: 5px 12px;
    border-radius: 100px;
    background: #f1efec;
    color: #4a4642;
    font-size: 13px;
    line-height: 1.5;
}

.bv-usage-steps {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.bv-usage-step {
    display: flex;
    align-items: flex-start;
    gap: 12px;
}

/* ==========================================================================
   BUNDLE PAGE (Salla Bundle Products) — BP-3b
   Scoped narrowly under the classes custom.js creates (.bv-bundle-*), plus one
   real Twilight class (.s-multiple-bundle-product-content-wrapper) that only
   exists inside the bundle slider — see specs/2026-08-27-bundle-project-plan.md.
   Neutral palette only, matching the ingredient/usage blocks directly above.
   ========================================================================== */
.bv-bundle-intro {
    margin: 0 0 20px;
    padding: 16px 18px;
    border-radius: 10px;
    background: #f1efec;
    color: #4a4642;
}

.bv-bundle-intro .prestige-desc {
    color: inherit;
}

.bv-bundle-why {
    margin: 6px 0 0;
    font-size: 12px;
    line-height: 1.5;
    color: #6b6560;
}

/* Concern-priority swap-ordering badge — see custom.js applyBundleConcernPriority().
   Ship-dark: only ever renders once a section has 2+ selectable products,
   which is not true of either live bundle today. Neutral palette, matching
   .bv-bundle-why directly below it; not a clickable control, so the 44px
   touch-target rule doesn't apply to this plain text label. */
.bv-bundle-suited {
    display: inline-block;
    margin: 0 0 6px;
    padding: 2px 10px;
    border-radius: 100px;
    background: #ece9e5;
    color: #4a4642;
    font-size: 11px;
    font-weight: 700;
    line-height: 1.6;
}

/* ==========================================================================
   BP-14: accordion → always-visible numbered steps + swap UI.
   Selectors below are the REAL Twilight/Stencil classes read directly out of
   node_modules/@salla.sa/twilight-components (salla-accordion*.css/.js,
   salla-multiple-bundle-product-slider.js, salla-slider.js) on 2026-09-02 —
   NOT live-inspected, since both real bundle products still return HTTP 410
   pending BP-2 (unhide). Flagged for storefront-diagnose/harness-run to
   re-confirm the moment a real bundle page is reachable. See custom.js's
   matching BP-14 header comment for the full mechanism reasoning (why a
   plain attribute-presence override is enough to neutralize the toggle,
   with no MutationObserver or capture-phase click handling needed).

   ROLLOUT GATE (added 2026-09-03): every selector below is scoped under
   `.bv-bundle-reskin`, a class custom.js's bvBundleEnhanceStepUI() only adds
   to the ONE host in BV_BUNDLE_RESKIN_ALLOWLIST (currently bundle
   2145274116). A bundle not on that list never gets the class, so none of
   these rules match it — Salla's native accordion, toggle affordance, and
   carousel are left completely untouched on it. The `.bv-bundle-ui-ready`/
   `.bv-bundle-slide-current` rules further down don't need their own scope:
   custom.js only ever adds those classes from inside the same gated
   function, so they're already unreachable on a non-allowlisted bundle.
   ========================================================================== */

/* Force every accordion body permanently open. The shipped component CSS
   gates visibility purely off the data-collapsed ATTRIBUTE VALUE on this
   same element; the click handler only ever flips that attribute (never an
   inline style), so matching the attribute's PRESENCE — any value — at this
   selector's specificity makes the toggle a no-op visually while leaving the
   click handler and the component's own state completely untouched. */
salla-multiple-bundle-product.bv-bundle-reskin .s-accordion-body-wrapper[data-collapsed] {
    visibility: visible !important;
    max-height: none !important;
    opacity: 1 !important;
    padding-top: 0 !important;
    padding-bottom: 0 !important;
}

/* Step header: strip the collapse affordance, keep the element itself in the
   DOM (removing it risks breaking ARIA/keyboard wiring Salla attached). */
salla-multiple-bundle-product.bv-bundle-reskin .s-accordion-head-wrapper {
    cursor: default;
}
salla-multiple-bundle-product.bv-bundle-reskin .s-accordion-head-wrapper-toggle {
    display: none !important;
}
.bv-bundle-step-number {
    display: inline-block;
    margin-inline-end: 8px;
    font-weight: 700;
    color: #6b6560;
}

/* Single-card-plus-change-button. custom.js marks the currently-selected
   slide `.bv-bundle-slide-current` and adds `.bv-bundle-ui-ready` to the
   slider host once it has resolved a selection — gating the hide-rule behind
   that class so a page load with JS not yet run (or still hydrating) shows
   Salla's normal carousel instead of a blank step, never the reverse. The
   slider is always built with show-controls="false" and no block-title
   (confirmed from salla-slider's own source: that combination renders NO nav
   arrows at all), so only the pagination dots need hiding here. */
salla-multiple-bundle-product-slider.bv-bundle-ui-ready .s-multiple-bundle-product-slide-one-third {
    display: none;
}
salla-multiple-bundle-product-slider.bv-bundle-ui-ready .s-multiple-bundle-product-slide-one-third.bv-bundle-slide-current {
    display: block;
    width: 100%;
}
salla-multiple-bundle-product.bv-bundle-reskin .s-multiple-bundle-product-wrapper-slider .swiper-pagination {
    display: none !important;
}

.bv-bundle-change-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 0 16px;
    margin: 8px 0 0;
    border: 1px solid #d8d4cf;
    border-radius: 8px;
    background: #fff;
    color: #1a1a1a;
    font-size: 13px;
    font-weight: 700;
    cursor: pointer;
}

.bv-bundle-swap-panel {
    display: none;
    margin: 10px 0 4px;
    padding: 12px;
    border-radius: 10px;
    background: #f6f5f3;
}
.bv-bundle-swap-panel.bv-bundle-swap-panel--open {
    display: block;
}

.bv-bundle-swap-row {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 44px;
    padding: 8px;
    border-radius: 8px;
    background: #fff;
    margin-bottom: 8px;
    cursor: pointer;
}
.bv-bundle-swap-row:last-child {
    margin-bottom: 0;
}
.bv-bundle-swap-row.bv-bundle-candidate-current {
    display: none;
}
.bv-bundle-swap-row img {
    width: 44px;
    height: 44px;
    object-fit: contain;
    flex-shrink: 0;
}
.bv-bundle-swap-row-info {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.bv-bundle-swap-row-name {
    font-size: 13px;
    color: #1a1a1a;
}
.bv-bundle-swap-row-price {
    font-size: 12px;
    color: #6b6560;
}

.bv-bundle-swap-empty {
    margin: 0;
    padding: 8px 4px;
    color: #6b6560;
    font-size: 13px;
}

.bv-bundle-reset-link {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 8px;
    color: #4a4642;
    font-size: 13px;
    font-weight: 700;
    text-decoration: underline;
    background: none;
    border: none;
    cursor: pointer;
}
.bv-bundle-reset-link[hidden] {
    display: none;
}

.bv-bundle-swap-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 0 14px;
    border: none;
    background: none;
    color: #6b6560;
    font-size: 12px;
    text-decoration: underline;
    cursor: pointer;
}

.bv-bundle-summary {
    margin: 0 0 20px;
    padding: 14px 16px;
    border-radius: 10px;
    background: #ece9e5;
    color: #1a1a1a;
}
.bv-bundle-summary-row {
    display: flex;
    justify-content: space-between;
    font-size: 13px;
    line-height: 1.8;
}
.bv-bundle-summary-row--total {
    font-weight: 700;
    font-size: 15px;
}

.bv-usage-num {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: #1a1a1a;
    color: #fff;
    font-size: 12px;
    font-weight: 700;
    font-family: inherit;
}

.bv-usage-text {
    font-size: 14px;
    line-height: 1.7;
    color: #3a3632;
    padding-top: 3px;
}

@media (min-width: 768px) {
    .bv-usage-steps {
        flex-direction: row;
        flex-wrap: wrap;
    }
    .bv-usage-step {
        flex: 1 1 240px;
    }
}

/* --- Un-tabbing description / reviews ------------------------------------ */
/* Prestige hides #tab-comments behind a click (its own `hidden` class). We
   force both panels visible and hide the now-redundant tab nav. !important
   is required to outrank the theme's own utility class. */
#tab-description,
#tab-comments {
    display: block !important;
}

.bv-hidden {
    display: none !important;
}

.bv-pdp-panel-title {
    margin: 0 0 16px;
    font-size: 18px;
    font-weight: 700;
    color: #231f1e;
}

/* --- Sticky mobile add-to-cart bar ---------------------------------------- */
.bv-pdp-sticky {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 40;
    display: none;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 16px;
    background: #fff;
    border-top: 1px solid #e3e1dd;
    box-shadow: 0 -4px 16px rgba(0, 0, 0, .08);
}

.bv-pdp-sticky.bv-visible {
    display: flex;
}

.bv-pdp-sticky-price {
    font-size: 16px;
    font-weight: 700;
    color: #231f1e;
    white-space: nowrap;
}

.bv-pdp-sticky-btn {
    flex: 1;
    max-width: 240px;
    min-height: 44px;
    background: #1a1a1a;
    color: #fff;
    border: 0;
    border-radius: 6px;
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
}
.bv-pdp-sticky-btn:hover {
    background: #000;
}

/* Desktop never gets the sticky bar — the real CTA is always in view there. */
@media (min-width: 768px) {
    .bv-pdp-sticky {
        display: none !important;
    }
}

/* --- Share button hidden on mobile ---------------------------------------- */
/* Was `hidden sm:inline-flex` — left mobile visitors with no way to share a
   product link at all. Scoped to the product form so other salla-social-share
   instances elsewhere on the site (e.g. blog) are untouched. */
.product-form salla-social-share {
    display: inline-flex !important;
}

/* --- Share/wishlist row: out of the primary buy zone ----------------------
   Escape-ledger row 35 (2026-08-31, Abdulhadi's phone review). Confirmed live
   markup (p1184424522), a direct child of form.product-form, immediately
   AFTER the add-to-cart button:
     <div class="flex text-base mt-6">
       <div class="flex">
         <salla-social-share class="hidden sm:inline-flex mb-5"></salla-social-share>
         <salla-button class="btn--wishlist animated hidden sm:inline-flex">...</salla-button>
       </div>
     </div>
   That stacks 6px-margin utility icons into the same vertical run as
   price → add-to-cart → category line, wasting buy-zone space on a
   secondary action.

   These icons are legitimate (not clutter like the category line below), so
   they are relocated, not hidden. Real selector for the product title/image
   area was not confirmed by diagnosis, so this deliberately does NOT attempt
   a DOM move outside form.product-form — flex `order` moves the row to the
   very TOP of the form instead, i.e. immediately under the real
   (outside-the-form) price/title and above price-row/add-to-cart/category,
   as a slim, right-aligned, de-emphasized strip rather than a full-margin
   row wedged after the CTA. If a future diagnose pass confirms a title/image
   container selector, this can move further (a real JS relocation) — flagged
   here rather than guessed. */
form.product-form {
    display: flex;
    flex-direction: column;
}

form.product-form > .flex.text-base.mt-6:has(salla-social-share) {
    order: -1;
    margin-top: 0;
    margin-bottom: 8px;
    justify-content: flex-end;
}

form.product-form > .flex.text-base.mt-6:has(salla-social-share) salla-social-share {
    margin-bottom: 0;
}

/* --- Quantity stepper ------------------------------------------------------ */
/* The real name="quantity" input is MOVED (not duplicated) into this control
   by custom.js, so Salla's own add-to-cart submit reads it exactly as before.
   44px touch targets, same WCAG 2.1 AA floor used on the product card. */
.bv-qty-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 12px;
}

.bv-qty-label {
    font-size: 16px;
    font-weight: 600;
    color: #231f1e;
    line-height: 1.5;
}

.bv-qty-stepper {
    display: flex;
    align-items: center;
    border: 1px solid #e3e1dd;
    border-radius: 6px;
    overflow: hidden;
}

.bv-qty-btn {
    width: 40px;
    height: 44px;
    border: 0;
    background: #f1efec;
    color: #231f1e;
    font-size: 16px;
    font-weight: 700;
    font-family: inherit;
    line-height: 1;
    cursor: pointer;
}
.bv-qty-btn:hover {
    background: #e3e1dd;
}

.bv-qty-input {
    width: 44px;
    height: 44px;
    border: 0;
    border-inline: 1px solid #e3e1dd;
    text-align: center;
    font-size: 14px;
    font-weight: 600;
    color: #231f1e;
    font-family: inherit;
}

/** Edits **/
/* Mobile Spaces */
@media (Max-width: 768px) {
    section.bv-routine-section {
        margin-block: 0;
    }
    .s-products-list-vertical-cards,
    section.s-block.s-block--bundle-blog,
    section.s-block.s-block--bundle-html-content,
    .routine-section {
        margin-block: 0;
    }
    .navbar-mobile-menu {
        display: none;
    }
}.prestige-statistics { display: none !important; }
/* ==========================================================================
   ROUTINE CARDS — button parity + size reduction (2026-08-26, Abdulhadi)

   Measured live at 2560px before changing anything:
     card 433x601 · img-box 260x260 · buttons 382x44, 382x87, 383x44

   Two problems, both visible:
   1. The middle card's CTA rendered 87px tall against 44px for its siblings.
      `.btn` sits in a column flex with margin-top:auto, so it was absorbing
      the leftover space of a shorter card instead of keeping its own height.
      Pinning flex-basis stops it stretching without touching the layout.
   2. The cards are simply too tall — 601px means one card nearly fills a
      laptop viewport and the three of them dominate the homepage.

   Ratio kept deliberate rather than arbitrary: the image was 43% of card
   height (260/601) and stays ~42% (190/450), so the card shrinks about 25%
   while looking proportionally the same. Type steps down with it.
   ========================================================================== */

/* 1 — every routine CTA is the same height, always */
section.routine-section .routine-card .btn {
    flex: 0 0 44px;
    height: 44px;
}

/* 2 — smaller cards: image, type and padding step down together */
section.routine-section .routine-card {
    padding: 24px 20px;
}

section.routine-section .img-box {
    width: 190px;
    height: 190px;
}

section.routine-section .img-box::before {
    width: 160px;
    height: 160px;
}

section.routine-section .img-box img {
    max-width: 160px;
    max-height: 160px;
}

section.routine-section .routine-card .content h2 {
    font-size: 26px;
    line-height: 1.25;
    margin-top: 14px;
}

section.routine-section .routine-card .content p {
    font-size: 15px;
    line-height: 1.75;
    margin: 12px auto;
}

@media (max-width: 576px) {
    section.routine-section .img-box {
        width: 150px;
        height: 150px;
    }
    section.routine-section .img-box::before {
        width: 126px;
        height: 126px;
    }
    section.routine-section .img-box img {
        max-width: 126px;
        max-height: 126px;
    }
    section.routine-section .routine-card .content h2 {
        font-size: 21px;
    }
}
/* ==========================================================================
   CONCERN HEADER — size + alignment (2026-08-26, Abdulhadi)

   The wording was fixed at source in the theme editor (component 802876938):
   "اختاري همّ بشرتك" was slang, and the first replacement offered here —
   "ما يشغل بشرتك" — was wrong too: يشغل collocates only with بال, never with
   بشرة, so it read as translated English. Now "ما الذي تحتاجه بشرتكِ؟", which
   uses a collocation Arabic actually licenses (بشرة تحتاج).

   Two things left for CSS. Measured live: font-size 50px, text-align start.
   50px is hero scale on a section label, and start-alignment puts it hard
   against the right edge above a centred tile grid — the heading and the
   thing it labels visibly disagree.

   Scoped through .s-block__title so it cannot reach the routine block, which
   is the same s-block--bundle-html-content component but titles its cards
   through .routine-card .content h2 instead.
   ========================================================================== */

/* 2026-08-27: symmetric space above/below the header text. The section's
   own margin is zeroed on mobile by the "/** Edits **/" block above (added
   outside this file, directly in the dashboard, to fix unrelated double-
   margin issues) — which left this heading with NO controlled space of its
   own on either side; whatever gap existed was an accidental byproduct of
   the hero's padding-bottom above and the first tile-row's padding-top
   below, and the two were never equal. padding-block (not margin) is
   deliberate: adjacent margins collapse unpredictably, padding does not, so
   this guarantees the same measured gap on both sides of the text itself. */
section.s-block--bundle-html-content .s-block__title {
    justify-content: center;
    padding-block: 24px;
}

section.s-block--bundle-html-content .s-block__title h2 {
    font-size: clamp(1.375rem, 1.05rem + 1.35vw, 1.75rem);
    text-align: center;
}
/* ==========================================================================
   MOBILE — routine cards left too narrow by the size reduction
   (2026-08-26; found because Abdulhadi asked whether the fixes held on phone)

   The card shrink-wraps its widest child, so dropping .img-box to 150px at
   <=576px pulled the whole card to 150 + 40px padding = 190px wide against
   543px tall — a 1:2.9 sliver — and the body copy then wrapped hard enough to
   drive that height up further.

   The section is a deliberate horizontal carousel on mobile (overflow-x:auto,
   scrollWidth 594 vs clientWidth 353), so cards 2 and 3 are swipeable, not
   hidden. Pinning the flex basis restores a sane proportion and still leaves
   part of the next card showing, which is the cue to swipe.
   ========================================================================== */

@media (max-width: 576px) {
    section.routine-section .routine-card {
        flex: 0 0 260px;
        width: 260px;
    }
}


/* ==========================================================================
   ROUTINE BANNERS — numbered steps (2026-08-27, Abdulhadi)

   The three step captions ("التنظيف" / "المعالجة" / "الحماية") are baked
   into the photos themselves — confirmed empty of any DOM text in the served
   markup, so this doesn't duplicate or fight that. A small numbered badge is
   overlaid independently, so the sequence (1 → 2 → 3) reads clearly at any
   viewport width, matching whichever orientation the responsive grid picks.

   No star/sparkle: that mark is reserved for the logo and the "BeautyVerse
   Pick" badge only. Plain numerals; the badge colour reuses the header's
   existing #0a2a52 rather than introducing a new one — palette isn't locked
   yet, so nothing new is added to it. */
.s-block--banners .banner-entry {
    position: relative;
}

.s-block--banners .banner-entry::before {
    position: absolute;
    top: 12px;
    inset-inline-start: 12px;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: rgba(10, 42, 82, 0.85);
    color: #fff;
    font-size: 14px;
    font-weight: 700;
    font-family: inherit;
}

.s-block--banners .banner-entry:nth-child(1)::before { content: "1"; }
.s-block--banners .banner-entry:nth-child(2)::before { content: "2"; }
.s-block--banners .banner-entry:nth-child(3)::before { content: "3"; }


/* --- Header aligned to the content column (Abdulhadi, 2026-08-28) --------- */
/* The complaint: "the desktop version has most of the items centered, sides
   empty, like if it was a mobile view."

   Measured on the live homepage at a 1897px viewport, before any change:
     header .................. 1897px wide, left 0      (full bleed)
     .container.mx-auto ...... 1280px wide, left 309
     .s-block--banners ....... 1280px wide, left 309
     blog listing container .. 1280px wide, left 309
   So ~309px of empty margin each side — about a third of the screen — while
   the header alone ran edge to edge. That mismatch is what made the body read
   as a narrow phone column dropped into a wide page: it was not the 1280px cap
   on its own (a normal, deliberate choice that also keeps Arabic line length
   readable), it was the header disagreeing with everything beneath it.

   Chosen fix (option 2 of 3): bring the header's two rows onto the same
   1280px column so every horizontal edge on the page lines up. The coloured
   bar still spans full width, because the background lives on the <header>
   itself — only its CONTENTS are constrained, so nothing is left unpainted.

   Verified by injecting this exact rule into the live page before writing it
   here: header rows, .container.mx-auto and .s-block--banners all then
   reported left 309 / right 1589 / width 1280 — one shared edge.

   Gated at min-width 1280px so narrower screens keep the theme's own fluid
   behaviour untouched. padding-inline matches the container's own 10px so the
   logo and icons sit on the content edge rather than 48px inside it. */
@media (min-width: 1280px) {
  .store-header .prestige-header,
  .store-header .navbar-mobile-menu {
    max-width: 1280px;
    margin-inline: auto;
    padding-inline: 10px;
  }
}


/* --- Assistant launcher hint bubble --------------------------------------- */
/* Abdulhadi: the launcher "isn't recognizable". This is the one-line hint that
   names it. Built by addAssistantHint() in custom.js.

   Positioned BESIDE the button, never over it — a hint that covers the control
   it explains is worse than no hint. The launcher sits bottom-start; this sits
   directly above it, matching that edge so the two read as one unit.

   Neutral placeholder colours in the same register as the rest of this file.
   The palette is NOT locked, so nothing here invents a brand colour. */
/* Positioned relative to the LAUNCHER'S OWN CONTAINER, not the viewport.
   Abdulhadi, 2026-08-28: "the pop up message ... is in the right side of the
   page" — it was. The first version used `position: fixed` with
   `inset-inline-start: 16px`, and on an RTL document inset-inline-start
   resolves to the RIGHT edge, while the launcher actually sits bottom-LEFT
   (measured: left 24 → 78). The bubble and the button it explains ended up on
   opposite sides of the screen.

   Anchoring to .bv-fab-container (which is itself position:fixed) means the
   bubble tracks the launcher wherever the third-party widget puts it, instead
   of depending on text direction — which was never the right cue, since the
   launcher's side is set by that widget, not by the language. custom.js also
   sets the correct physical side at runtime as a belt-and-braces measure. */
.bv-fab-hint {
  position: absolute;
  bottom: calc(100% + 12px);    /* sits directly above the launcher */
  left: 0;                      /* overridden at runtime if the fab is right-aligned */
  z-index: 9998;                /* under the launcher itself, above the page */
  max-width: min(78vw, 300px);
  width: max-content;
  padding: 10px 34px 10px 14px;
  border-radius: 12px;
  background: #1f2937;
  color: #fff;
  font-size: 13px;
  line-height: 1.55;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
  animation: bv-fab-hint-in 220ms ease-out both;
}

/* A small pointer toward the launcher below it. Physical `left`, matching the
   bubble's own anchoring — see the note above on why inset-inline-* is the
   wrong tool for an element pinned to a widget rather than to text flow. */
.bv-fab-hint::after {
  content: "";
  position: absolute;
  bottom: -6px;
  left: 18px;
  width: 12px;
  height: 12px;
  background: inherit;
  transform: rotate(45deg);
}

.bv-fab-hint__close {
  position: absolute;
  top: 4px;
  inset-inline-end: 6px;
  width: 24px;
  height: 24px;
  border: 0;
  background: transparent;
  color: inherit;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.75;
}

.bv-fab-hint__close:hover,
.bv-fab-hint__close:focus-visible { opacity: 1; }

@keyframes bv-fab-hint-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* Respect a reduced-motion preference — the bubble still appears, it just
   does not slide. */
@media (prefers-reduced-motion: reduce) {
  .bv-fab-hint { animation: none; }
}


/* --- Magazine index: intro line + per-card category / reading time -------- */
/* Injected by enhanceMagazineIndex() in custom.js. The meta line answers
   "what is this, and what will it cost me to read it" before the reader
   commits — which is the whole reason it sits ABOVE the title, not below.

   Deliberately quiet: it must not compete with the title. Colour and size are
   placeholders in the same neutral register as the rest of this file — the
   palette is NOT locked yet, so nothing here invents a brand colour. */
.bv-magazine-intro {
  margin: -1.5rem 0 2rem;
  color: #555;
  font-size: 0.95rem;
  line-height: 1.7;
  max-width: 60ch;
}

.bv-article-meta {
  margin-bottom: 6px;
  color: #6b6b6b;
  font-size: 12px;
  line-height: 1.4;
  letter-spacing: 0.01em;
}

/* --- Magazine cards: اقرأ المزيد pinned to the card bottom ---------------- */
/* Abdulhadi, 2026-08-28: "the buttons اقرأ المزيد are not aligned."
   Measured live: the three cards in a row were all 548px tall (the grid
   stretches them), but their buttons sat at 854 / 794 / 822 — a 60px spread —
   because the white content block is only as tall as its text. A two-line
   title or a shorter excerpt moved the button up with it.

   Equal card height was never the problem; the button simply was not pinned.
   So: make the card a flex column, let the white block grow to fill whatever
   height the grid hands it, and push the button wrapper down with margin-top
   auto. The button then lands on the same line in every card no matter how
   the title wraps.

   Keyed off [data-bv-card], which enhanceMagazineIndex() stamps on each card
   it processes — a stable hook, unlike the theme's Tailwind class soup, and
   it means the rule cannot touch anything the JS did not recognise. */
salla-infinite-scroll [data-bv-card] {
  display: flex;
  flex-direction: column;
  height: 100%;
}

salla-infinite-scroll [data-bv-card] > .bg-white {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
}

/* The read-more wrapper is the last child of the white block. */
salla-infinite-scroll [data-bv-card] > .bg-white > .text-center {
  margin-top: auto;
}

/* Image must not stretch when the card grows to match a taller sibling. */
salla-infinite-scroll [data-bv-card] > a img {
  flex-shrink: 0;
}

/* The card's white content block sets its own horizontal padding; the meta
   line is inserted as a sibling of the title anchor inside that block, so it
   inherits that padding and needs no offset of its own. */


/* ==========================================================================
   PRODUCT PAGE — Abdulhadi's 2026-08-30 review
   ========================================================================== */

/* --- No brand logos on product pages -------------------------------------
   Abdulhadi, 2026-08-30: "Cosrex product shows the brand logo. remove it, i
   don't want any brand logo in any product page."

   Measured: 24 of 92 products render it (COSRX, Anua, Beauty of Joseon,
   ARENCIA, SKIN1004, The Ordinary). The theme emits
     <div class="product-brand mb-5 w-12">
       <a class="brand-logo" href="/brand-<id>"><img alt="<Name>"></a>
     </div>
   right after the product:single.description.start hook.

   salla-research checked for a platform lever first, per the standing rule:
   there is NO dashboard or theme-editor toggle for this, and Salla exposes the
   brand name only as alt/title text — never as visible text — so the template
   hardcodes the image tag. CSS is the correct instrument here, not a shortcut.

   He also said: if a brand is shown later, it should be the NAME, set neatly,
   and it differs between desktop and mobile. That is a design task, not this
   one — this rule only removes the logo. Deliberately not inventing a text
   treatment he has not seen. */
.product-brand {
  display: none !important;
}

/* --- The price is printed twice ------------------------------------------
   Abdulhadi, 2026-08-30: "the price of the product appeares twice."
   CONFIRMED on all 92 products — this is template-level, not a data problem:

     1. h4.total-price directly under the h1, unlabelled.
     2. h4.total-price again inside form.product-form, in a
        .center-between row with a «السعر» label, just above add-to-cart.

   Keep (1): title-then-price is the order a shopper expects, and it is where
   the prototype puts it. Hide (2) — the whole labelled row, not just the
   number, or the orphaned «السعر» label would be left pointing at nothing.

   :has() is the right tool: it fails CLOSED. In a browser without support the
   rule simply does not apply and the page stays exactly as it is today — no
   worse, never broken. */
form.product-form .center-between:has(> h4.total-price) {
  display: none;
}


/* --- Pre-launch bar -------------------------------------------------------
   Abdulhadi, 2026-08-30: tell visitors we are pre-launch. Copy approved by
   brand-check the same day.

   Sits in normal flow as the first child of the body. .store-header is
   position:relative (measured, not assumed), so the bar pushes the header down
   by its own height — nothing to keep in sync, and it cannot cover the logo.

   Colours deliberately neutral: the palette is NOT locked yet (CLAUDE.md), so
   this uses a plain dark ground rather than inventing a brand colour. */
.bv-prelaunch {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: nowrap;
  gap: 10px;
  padding: 9px 40px 9px 12px;   /* right padding clears the close button */
  background: #1F2421;
  color: #F4F6F3;
  font-size: 13px;
  line-height: 1.4;
  position: relative;
  text-align: center;
}

/* Must survive one line at 375px — it is the narrowest phone we support.
   min-width:0 lets the text shrink instead of forcing the row to overflow. */
.bv-prelaunch__text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bv-prelaunch__cta {
  flex: 0 0 auto;
  background: none;
  border: 0;
  padding: 0;
  color: inherit;
  font: inherit;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.bv-prelaunch__cta:hover { opacity: .85; }
.bv-prelaunch__cta:focus-visible,
.bv-prelaunch__close:focus-visible {
  outline: 2px solid #F4F6F3;
  outline-offset: 2px;
}

/* 44px touch target without making the bar 44px tall: the button is padded out
   past the bar's own box rather than stretching it. */
.bv-prelaunch__close {
  position: absolute;
  inset-inline-end: 4px;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 0;
  color: inherit;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  opacity: .75;
}
.bv-prelaunch__close:hover { opacity: 1; }

@media (max-width: 420px) {
  .bv-prelaunch { font-size: 12px; gap: 7px; padding-inline-start: 8px; }
}


/* --- Commercial registration line in the footer ---------------------------
   Abdulhadi, 2026-08-31. Small, neutral — a legal identifier, not a promise. */
.bv-footer-legal {
  font-size: 12px;
  opacity: 0.7;
  margin-top: 8px;
}


/* --- BP-6: "bundle for this concern" callout on a concern category page ---
   specs/2026-08-27-bundle-project-plan.md. Injected by custom.js
   (injectConcernBundleCallout) as the sole child of the native
   `.s-before-products-list` slot (verified real, empty on both live category
   pages — see custom.js's comment on injectConcernBundleCallout), on exactly
   the 2 concern categories that have a matching bundle today.

   Kept deliberately supporting-weight — a bordered card, not a hero banner —
   and neutral per CLAUDE.md (colors/typography aren't locked). Reuses the
   same grayscale range as .bv-soon above (#231f1e / #6b6660 / #1a1a1a). */
.bv-concern-bundle-callout {
  margin: 0 16px 20px;
  padding: 16px 18px;
  background: #faf9f7;
  border: 1px solid #e5e2dd;
  border-radius: 8px;
}

.bv-concern-bundle-callout__eyebrow {
  display: block;
  margin: 0 0 4px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .02em;
  color: #6b6660;
  text-transform: uppercase;
}

.bv-concern-bundle-callout__title {
  margin: 0 0 6px;
  font-size: 15px;
  font-weight: 700;
  color: #231f1e;
  line-height: 1.5;
}

.bv-concern-bundle-callout__line {
  margin: 0 0 14px;
  font-size: 13px;
  line-height: 1.7;
  color: #6b6660;
}

.bv-concern-bundle-callout__cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;            /* WCAG 2.1 AA touch target */
  padding: 0 22px;
  background: #1a1a1a;
  color: #fff;
  border: 0;                   /* now a <button>, not an <a> — reset UA button chrome */
  border-radius: 6px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background .15s;
}
.bv-concern-bundle-callout__cta:hover { background: #000; }
.bv-concern-bundle-callout__cta:focus-visible { outline: 2px solid #1a1a1a; outline-offset: 3px; }

@media (max-width: 480px) {
  .bv-concern-bundle-callout { margin-inline: 12px; }
  .bv-concern-bundle-callout__cta { width: 100%; }
}

/* ==========================================================================
   HOMEPAGE BUNDLE TILES — copy overlay
   Spec: specs/2026-09-03-homepage-bundle-cta-replacement.md. custom.js's
   fixBannerLabels() appends `.bv-bundle-tile-copy` inside each already-
   `position: relative` `.banner-entry` (rule near the top of this file).
   Absolutely positioned so it adds NO height/width to the tile — the spec's
   own out-of-scope note is explicit that the tile's existing size/shape
   rules are not to change, only destination + copy.
   ========================================================================== */
.bv-bundle-tile-copy {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  padding: 10px 14px 12px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* Legibility over an arbitrary background image without changing the
     image itself — a bottom gradient, not a flat block, so the top of the
     tile stays untouched. Neutral (grayscale), no new palette. */
  background: linear-gradient(to top, rgba(17,17,17,.82) 0%, rgba(17,17,17,.55) 55%, rgba(17,17,17,0) 100%);
  color: #fff;
  text-align: start;
  pointer-events: none;   /* the tile <a> itself remains the click target */
}
.bv-bundle-tile-copy__name {
  display: block;
  font-size: 15px;
  font-weight: 700;
  line-height: 1.35;
}
.bv-bundle-tile-copy__line {
  display: block;
  font-size: 12px;
  line-height: 1.5;
  color: #f2f0ed;
}
.bv-bundle-tile-copy__line-en {
  display: block;
  margin-top: 2px;
  font-size: 10px;
  line-height: 1.4;
  color: #d8d5d0;
}
@media (max-width: 480px) {
  .bv-bundle-tile-copy { padding: 8px 10px 10px; }
  .bv-bundle-tile-copy__name { font-size: 13px; }
  .bv-bundle-tile-copy__line { font-size: 11px; }
  .bv-bundle-tile-copy__line-en { font-size: 9px; }
}

/* ==========================================================================
   BUNDLES LISTING PAGE
   Spec: specs/2026-09-03-bundles-listing-page.md. Built entirely by
   custom.js's injectBundlesListingPage() into the Salla Informative Page's
   `#bv-bundles-root` anchor. Neutral/grayscale only, matching the range
   already used elsewhere in this file (#111/#1a1a1a/#231f1e/#4a4642/#6b6560/
   #faf9f7 family) — colours are not locked per CLAUDE.md.
   ========================================================================== */

/* --- Diagnosis-CTA panel (spec §6) ----------------------------------------
   Must pass the squint test: the single most visually dominant element on
   the page, above the filter bar and grid in both DOM order and visual
   order. A full-bleed dark block with a large button is the simplest way to
   win that test against a grid of bordered white cards. */
#bv-bundles-root { display: block; }

.bv-bundles-hero {
  margin: 0 0 28px;
  padding: 40px 20px;
  background: #1a1a1a;
  color: #fff;
  text-align: center;
  border-radius: 0;
}
.bv-bundles-hero__heading {
  margin: 0 0 18px;
  font-size: 26px;
  font-weight: 700;
  line-height: 1.35;
}
.bv-bundles-hero__cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 56px;             /* within spec's 48-60px band */
  padding: 0 40px;
  background: #fff;
  color: #1a1a1a;
  border: 0;
  border-radius: 8px;
  font-family: inherit;
  font-size: 17px;
  font-weight: 700;
  cursor: pointer;
  transition: background .15s, color .15s;
}
.bv-bundles-hero__cta:hover { background: #ece9e5; }
.bv-bundles-hero__cta:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

@media (max-width: 480px) {
  .bv-bundles-hero { padding: 32px 16px; }
  .bv-bundles-hero__heading { font-size: 21px; }
  .bv-bundles-hero__cta { width: 100%; min-height: 52px; font-size: 16px; }
}

/* --- Filter bar (spec §2) — 3 facets, all multi-select checkboxes -------- */
.bv-bundles-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 20px 28px;
  margin: 0 16px 24px;
  padding: 18px 20px;
  background: #faf9f7;
  border: 1px solid #e8e5e0;
  border-radius: 10px;
}
.bv-bundles-facet {
  min-width: 160px;
  border: 0;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 8px;
}
.bv-bundles-facet__legend {
  display: flex;
  flex-direction: column;
  flex: 1 0 100%; /* own full-width line above the chip row, per direction */
  margin: 0 0 4px;
  padding: 0;
  font-weight: 700;
}
.bv-bundles-facet__legend-ar { font-size: 13px; color: #231f1e; }
.bv-bundles-facet__legend-en { font-size: 10px; font-weight: 500; color: #6b6560; }

/* Compact chip: sized to content, wraps within its facet, not a full-width
   stacked row anymore (2026-09-04, mobile scroll-length fix). Checkbox stays
   in the DOM and operable; the chip's own border/background carries the
   checked state via :has() so the tiny native square isn't the only cue. */
.bv-bundles-facet__option {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  min-height: 44px; /* WCAG 2.1 AA touch target, held even though the chip is visually compact */
  box-sizing: border-box;
  padding: 6px 12px;
  font-size: 13px;
  color: #4a4642;
  cursor: pointer;
  border: 1px solid #e8e5e0;
  border-radius: 999px;
  background: #fff;
}
.bv-bundles-facet__option:has(input:checked) {
  background: #1a1a1a;
  border-color: #1a1a1a;
}
.bv-bundles-facet__option:has(input:checked) .bv-bundles-facet__option-ar,
.bv-bundles-facet__option:has(input:checked) .bv-bundles-facet__option-en {
  color: #faf9f7;
}
.bv-bundles-facet__option input[type="checkbox"] {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  accent-color: #1a1a1a;
}
.bv-bundles-facet__option-ar { font-size: 13px; }
.bv-bundles-facet__option-en { font-size: 11px; color: #6b6560; }

@media (max-width: 480px) {
  .bv-bundles-filters { margin-inline: 12px; gap: 18px; }
  /* Each facet takes the full row so its chip row has the whole width to
     wrap across, instead of being squeezed into a ~160px column. */
  .bv-bundles-facet { flex-basis: 100%; }
}

/* --- Card grid (spec §3) ---------------------------------------------------
   No fixed card height: descriptions render in full per bundle (spec's own
   "size a fixed height to the longest description, never the shortest" rule
   is safest satisfied by not fixing a height at all — content sets it). */
.bv-bundles-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 18px;
  margin: 0 16px 24px;
  /* WAS align-items:start (Abdulhadi screenshot, 2026-09-04): that forced
     every card to its own content height, so cards in the same row could
     differ visibly — content still decides height (no fixed height added),
     but now the row's TALLEST card sets the row and every card in that row
     stretches to match it (CSS Grid's own row-sizing, not a JS measurement).
     .bv-bundle-card is display:flex/flex-direction:column with no height
     rule fighting this, and .bv-bundle-cta's existing margin-top:auto
     absorbs the extra stretched space by pushing the CTA to the bottom
     instead of leaving a visible gap mid-card. */
  align-items: stretch;
}

/* Same bug, same fix, as the card rule below: bvApplyBundleFilters() sets
   grid.hidden = true when a filter combination matches zero bundles, but the
   bare .bv-bundles-grid display:grid declaration above always wins over the
   UA [hidden] rule — confirmed live, getComputedStyle(grid).display stayed
   "grid" with hidden=true. This compound selector (0,2,0) beats it. */
.bv-bundles-grid[hidden] { display: none; }

/* [hidden] compound selector (specificity 0,2,0) beats the bare .bv-bundle-card
   class rule below (0,1,0) that sets display:flex — without this, setting the
   `hidden` DOM property (as bvApplyBundleFilters() does) has no visual effect,
   because an author-stylesheet `display` declaration always wins over the
   browser's own UA `[hidden] { display: none }` rule regardless of specificity,
   and even where specificity is compared, this compound selector is higher
   anyway. Filtered-out cards were rendering hidden=true with getComputedStyle
   display still "flex" and full on-screen dimensions — confirmed live. */
.bv-bundle-card[hidden] { display: none; }

.bv-bundle-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 18px 18px 16px;
  background: #fff;
  border: 1px solid #e5e2dd;
  border-radius: 10px;
}

/* Card -> detail link (bundle-detail-page spec §1): wraps chips through the
   routine-length badge. Block, no underline/color change — the card's own
   inner elements already carry all the visual styling; this anchor is a
   transparent hit-target, not a new visual treatment. Its own flex column +
   gap replaces the gap the removed direct children used to get from
   .bv-bundle-card's gap:10px above (now only between the link and the CTA
   button, its two remaining direct children). */
.bv-bundle-card__link {
  display: flex;
  flex-direction: column;
  gap: 10px;
  color: inherit;
  text-decoration: none;
}

.bv-bundle-card__chips { display: flex; flex-wrap: wrap; gap: 6px; }
.bv-bundle-card__chip {
  display: inline-flex;
  flex-direction: column;
  padding: 4px 12px;
  border-radius: 100px;
  background: #1a1a1a;
  color: #fff;
}
.bv-bundle-card__chip-ar { font-size: 12px; font-weight: 700; line-height: 1.4; }
.bv-bundle-card__chip-en { font-size: 10px; font-weight: 500; line-height: 1.3; color: #ded9d2; }

.bv-bundle-card__chip--also {
  background: #ece9e5;
  color: #4a4642;
}
.bv-bundle-card__chip--also .bv-bundle-card__chip-ar { color: #4a4642; font-weight: 600; }
.bv-bundle-card__chip--also .bv-bundle-card__chip-en { color: #6b6560; }

.bv-bundle-card__name { margin: 0; display: flex; flex-direction: column; gap: 2px; }
.bv-bundle-card__name-ar { font-size: 17px; font-weight: 700; color: #231f1e; line-height: 1.4; }
.bv-bundle-card__name-en { font-size: 12px; font-weight: 500; color: #6b6560; }

.bv-bundle-card__desc {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* No max-height / overflow / -webkit-line-clamp here on purpose — a clamp
     is exactly the "cut mid-sentence" bug the spec calls out. */
}
.bv-bundle-card__desc-ar { font-size: 13px; line-height: 1.8; color: #4a4642; }
.bv-bundle-card__desc-en { font-size: 12px; line-height: 1.6; color: #837c74; }

.bv-bundle-card__also {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding-top: 4px;
  border-top: 1px dashed #e5e2dd;
}
.bv-bundle-card__also-label { font-size: 11px; font-weight: 700; color: #6b6560; }
.bv-bundle-card__also-label-en { font-size: 10px; color: #a39c93; margin-inline-end: 4px; }

.bv-bundle-card__badge { margin: 0; display: flex; flex-direction: column; gap: 1px; }
.bv-bundle-card__badge-ar { font-size: 12px; font-weight: 700; color: #231f1e; }
.bv-bundle-card__badge-en { font-size: 10px; color: #6b6560; }

/* --- Purchase component (spec §4) ------------------------------------------
   Disabled = temporarily unavailable, never "broken"/"sold out": no
   strikethrough, contrast kept readable, just visually quieter than the
   enabled state below it. */
.bv-bundle-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;              /* primary card action — 48px floor, not 44 */
  margin-top: auto;
  padding: 0 20px;
  border: 0;
  border-radius: 8px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: background .15s;
}
.bv-bundle-cta[data-bv-cta-state="disabled"] {
  background: #ece9e5;
  color: #6b6560;
  cursor: default;
}
.bv-bundle-cta[data-bv-cta-state="enabled"] {
  background: #1a1a1a;
  color: #fff;
}
.bv-bundle-cta[data-bv-cta-state="enabled"]:hover { background: #000; }
.bv-bundle-cta:focus-visible { outline: 2px solid #1a1a1a; outline-offset: 3px; }

/* --- Empty filter-state (spec §5) ------------------------------------------
   Same slot the grid occupies — one or the other is hidden, never both. */
.bv-bundles-empty {
  margin: 0 16px 24px;
  padding: 28px 20px;
  text-align: center;
  background: #faf9f7;
  border: 1px solid #e8e5e0;
  border-radius: 10px;
}
.bv-bundles-empty__ar {
  margin: 0 0 10px;
  font-size: 15px;
  line-height: 1.8;
  color: #231f1e;
}
.bv-bundles-empty__en {
  margin: 0 0 18px;
  font-size: 13px;
  line-height: 1.6;
  color: #6b6560;
}
.bv-bundles-empty__cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 0 28px;
  background: #1a1a1a;
  color: #fff;
  border: 0;
  border-radius: 8px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}
.bv-bundles-empty__cta:hover { background: #000; }
.bv-bundles-empty__cta:focus-visible { outline: 2px solid #1a1a1a; outline-offset: 3px; }

/* --- Zero-match concern-facet fallback ------------------------------------
   Shown ABOVE the grid (not instead of it) when the AND filter degrades and
   reduction finds a partial match. Same calm panel register as
   .bv-bundles-empty — a signpost, not a warning: no red, no icon. The clear
   link is a quiet text link, not a button: clicking it produces no visible
   change (the grid already shows the reduced result), and a prominent
   button that visibly does nothing reads as broken. */
.bv-bundles-fallback {
  margin: 0 16px 16px;
  padding: 20px 20px;
  text-align: center;
  background: #faf9f7;
  border: 1px solid #e8e5e0;
  border-radius: 10px;
}
.bv-bundles-fallback__msg {
  margin: 0 0 8px;
  font-size: 14px;
  line-height: 1.8;
  color: #45403c;
}
.bv-bundles-fallback__clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 8px;
  background: transparent;
  border: 0;
  color: #6b6560;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
}
.bv-bundles-fallback__clear:hover { color: #1a1a1a; }
.bv-bundles-fallback__clear:focus-visible { outline: 2px solid #1a1a1a; outline-offset: 3px; }

@media (max-width: 480px) {
  .bv-bundles-grid { grid-template-columns: 1fr; margin-inline: 12px; }
  .bv-bundles-empty { margin-inline: 12px; }
  .bv-bundles-empty__cta { width: 100%; }
  .bv-bundles-fallback { margin-inline: 12px; }
}

/* ==========================================================================
   BUNDLE DETAIL PAGE
   Spec: specs/2026-09-04-bundle-detail-page.md. Built by
   injectBundleDetailPage() into the SAME `#bv-bundles-root` anchor as the
   listing above (reached via `?bundle=<code>` on the same page). Same
   neutral/grayscale palette and spacing conventions as the listing page
   directly above — no new colour scheme.
   ========================================================================== */

.bv-bundle-detail { margin: 0 16px 32px; }

.bv-bundle-detail__header { margin: 24px 0 28px; }

/* Back link (spec §4 point 1) — real, clickable, WCAG 2.1 AA touch target. */
.bv-bundle-detail__back {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 4px;
  margin-bottom: 12px;
  color: #6b6560;
  text-decoration: none;
  font-size: 13px;
  font-weight: 600;
}
.bv-bundle-detail__back:hover { color: #1a1a1a; }
.bv-bundle-detail__back:focus-visible { outline: 2px solid #1a1a1a; outline-offset: 2px; }
.bv-bundle-detail__back-ar { font-size: 13px; }
.bv-bundle-detail__back-en { font-size: 12px; color: #837c74; }

.bv-bundle-detail__title-ar { display: block; margin: 0 0 10px; font-size: 24px; font-weight: 700; color: #231f1e; line-height: 1.35; }
.bv-bundle-detail__title-en { display: block; margin: 0 0 10px; font-size: 16px; font-weight: 500; color: #6b6560; }

.bv-bundle-detail__intro { margin: 0; }
.bv-bundle-detail__intro-ar { font-size: 14px; line-height: 1.8; color: #4a4642; }
.bv-bundle-detail__intro-en { font-size: 13px; line-height: 1.6; color: #837c74; }

/* Routine sections (spec §4 point 4) — Morning -> Evening -> Weekly, a
   whole section (heading + steps) omitted entirely when its array is empty
   (handled in JS; nothing here assumes a section always exists). */
.bv-bundle-detail__section { margin: 0 0 28px; }
.bv-bundle-detail__section-heading-ar { margin: 0 0 14px; font-size: 18px; font-weight: 700; color: #231f1e; }
.bv-bundle-detail__section-heading-en { margin: 0 0 14px; font-size: 14px; font-weight: 500; color: #6b6560; }

.bv-bundle-detail__steps {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* One step (spec §5 hard requirement): EVERY step in a section — including
   the required-but-not-included SPF step on BV-STD-ACN-LITE/BV-STD-POR-LITE
   — uses this exact same rule. No modifier class exists for
   `included_in_bundle:false` on purpose: there is nothing here to key a
   grey-out/outline-only/"not included" treatment off of, by design. */
.bv-bundle-step {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 16px 18px;
  background: #fff;
  border: 1px solid #e5e2dd;
  border-radius: 10px;
}
.bv-bundle-step__num {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: #1a1a1a;
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}
.bv-bundle-step__body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.bv-bundle-step__name-ar { margin: 0; font-size: 15px; font-weight: 700; color: #231f1e; line-height: 1.4; }
.bv-bundle-step__name-en { margin: 0; font-size: 13px; font-weight: 500; color: #6b6560; }
.bv-bundle-step__instruction-ar { margin: 0; font-size: 13px; line-height: 1.8; color: #4a4642; }
.bv-bundle-step__instruction-en { margin: 0; font-size: 12px; line-height: 1.6; color: #837c74; }

/* Step product image + link (2026-09-06 — constraint lifted, see the JS
   comment on bvRenderBundleStep for why). Fixed square footprint so a
   missing image (the placeholder span below) occupies the exact same space
   as a real <img> and never collapses the row; min-width:44px/min-height:44px
   on the whole media block keeps the link a legal touch target even though
   the visual box is a bit larger. Sized down at the 480px breakpoint so it
   never crowds the instruction text into a narrow column on mobile. */
.bv-bundle-step__media {
  flex: 0 0 auto;
  display: block;
  width: 56px;
  height: 56px;
  min-width: 44px;
  min-height: 44px;
  border-radius: 8px;
  overflow: hidden;
  background: #faf9f7;
  border: 1px solid #e5e2dd;
}
.bv-bundle-step__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.bv-bundle-step__img-placeholder {
  display: block;
  width: 100%;
  height: 100%;
  background: #f0eeea;
}
.bv-bundle-step__stock {
  margin-top: 2px;
  font-size: 11px;
  font-weight: 600;
  color: #837c74;
}

/* Purchase CTA (spec §6) — reuses .bv-bundle-cta verbatim; this wrapper only
   adds the section-to-CTA spacing that .bv-bundle-card's flex gap provided
   on the listing card. .bv-bundle-cta's own margin-top:auto is a harmless
   no-op in this plain-block context (auto margins only resolve in a flex/
   grid formatting context). */
.bv-bundle-detail__cta { margin-top: 8px; }
.bv-bundle-detail__cta .bv-bundle-cta { width: 100%; max-width: 320px; }

/* Not-found state (spec §8) — same visual register as .bv-bundles-empty. */
.bv-bundle-detail__notfound {
  margin: 40px 16px;
  padding: 28px 20px;
  text-align: center;
  background: #faf9f7;
  border: 1px solid #e8e5e0;
  border-radius: 10px;
}
.bv-bundle-detail__notfound-ar { margin: 0 0 18px; font-size: 15px; line-height: 1.8; color: #231f1e; }
.bv-bundle-detail__notfound-en { margin: 0 0 18px; font-size: 13px; line-height: 1.6; color: #6b6560; }
.bv-bundle-detail__notfound-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 0 28px;
  background: #1a1a1a;
  color: #fff;
  border-radius: 8px;
  text-decoration: none;
  font-size: 14px;
  font-weight: 700;
}
.bv-bundle-detail__notfound-cta:hover { background: #000; }
.bv-bundle-detail__notfound-cta:focus-visible { outline: 2px solid #1a1a1a; outline-offset: 3px; }

@media (max-width: 480px) {
  .bv-bundle-detail { margin-inline: 12px; }
  .bv-bundle-detail__header { margin-top: 16px; }
  .bv-bundle-detail__title-ar { font-size: 20px; }
  .bv-bundle-step { padding: 14px 14px; gap: 10px; }
  .bv-bundle-step__media { width: 44px; height: 44px; }
  .bv-bundle-detail__cta .bv-bundle-cta { max-width: none; }
  .bv-bundle-detail__notfound { margin-inline: 12px; }
  .bv-bundle-detail__notfound-cta { width: 100%; }
}
h