/* ============================================================================
   HYPER · حياة — Editorial Minimal Theme
   Design language: "Quiet Editorial" — هادئ · واسع · مقروء
   ----------------------------------------------------------------------------
   Palette : Paper #FFFFFF · Ink #14110F · Warm well #F7F6F4 · Sale #A3402F
   Type    : Cormorant Garamond / Amiri (display) · Jost / Tajawal (body)
   Shape   : square corners · hairline rules · no shadows
   Density : 8px grid · 96px sections · long line-height, generous leading
   Prefix  : .hy-*   (new components)   ·   .taqa-*  (legacy, being replaced)
   ----------------------------------------------------------------------------
   PRINCIPLES
     1. White space is the layout. Nothing floats; things sit.
     2. Ink is the only action colour. Colour is reserved for meaning (sale).
     3. A hairline separates. A shadow interrupts — so we don't use shadows.
     4. Type carries the brand: a high-contrast serif against a quiet sans.
     5. Motion is slow and small. 300ms, a few pixels, never a bounce.
   ----------------------------------------------------------------------------
   ⚠️  ARABIC RULE — letter-spacing breaks Arabic letter joining. Every
       `letter-spacing` in this file MUST be scoped to [dir="ltr"] / lang="en".
   ----------------------------------------------------------------------------
   SECTIONS
     01. Design tokens
     02. Base & reset
     03. Typography
     04. Layout primitives (container / section / heading / grid)
     05. Buttons
     06–11. RETIRED. These were the .taqa-* legacy — badges, cards, forms,
            navigation, PDP primitives. Their .hy-* replacements landed in 15
            and the originals were deleted. The numbers are kept as a gap
            rather than reused, so a comment elsewhere that still points at
            "section 09" is obviously stale instead of silently wrong.
            ⚠️  Section 12 went out with them AND SHOULD NOT HAVE: the JS that
            builds toasts stayed, so every message rendered unstyled until it
            was restored. Check main.js before deleting a section whose
            markup is created at runtime — grep for the class, not the file.
     12. Feedback · toast
     13. Third-party overrides (Swiper · GLightbox)
     14. Utilities & motion
     15. HYPER components · .hy-*
     16. Touch targets · @media (pointer: coarse)
   ========================================================================= */

/* ==========================================================================
   01. DESIGN TOKENS
   Every var below is retargetable from the Theme Editor via the :root block
   that layout.njk prints straight after this file.

   NOTE ON NAMING — the variable names are inherited from the previous theme
   on purpose. 23 widgets read them. Renaming would be a cosmetic win and a
   functional break, so the names stay and only the VALUES carry the new
   identity. `--color-accent` is now ink, not blue; `--color-deal` is now a
   muted brick red, not orange. Nothing downstream needs to know.
   ========================================================================== */
:root {
  /* ---- Surfaces — paper, and one warm well ---- */
  --color-bg-primary: #ffffff; /* the page — paper white */
  --color-bg-secondary: #ffffff; /* cards sit ON the paper, not above it */
  --color-bg-tertiary: #f7f6f4; /* warm well — inputs, insets, empty states */
  --color-surface: #ffffff;

  /* Section alternation — paper ↔ warm well */
  --color-section-a: #ffffff;
  --color-section-b: #f7f6f4;

  /* ---- Accent · Ink (action) ------------------------------------------
       In this theme the accent IS the ink. A minimal store has one voice;
       giving buttons a second colour is what makes a page look busy. ---- */
  --color-accent: #14110f;
  --color-accent-hover: #2e2a26;
  --color-accent-dark: #000000; /* text on an ink wash */
  --color-accent-soft: #a29b93; /* rings, active borders */
  --color-accent-light: #f2f0ec; /* wash — chips, eyebrows */

  /* ---- Ink · the same black, named for structural use ---- */
  --color-ink: #14110f;
  --color-ink-hover: #2e2a26;
  --color-ink-light: #f2f0ec;

  /* ---- Deal · the one colour that is allowed to mean something ---- */
  --color-deal: #a3402f;
  --color-deal-hover: #8a3527;
  --color-deal-light: #fbf1ef;

  /* ---- Text — warm greys, never pure black on pure white ---- */
  --color-text-primary: #14110f;
  --color-text-secondary: #6e6862;
  --color-text-tertiary: #a29b93;
  --color-text-on-accent: #ffffff;
  --color-text-on-ink: #ffffff;
  /* The third of the set, and it was missing. Accent and ink each had a
     token for what sits ON them; the deal colour did not, so every surface
     painted with it hardcoded #fff — and `dealColor` is a Theme Editor
     setting. A merchant picking a pale sale colour got white on pale: the
     exact failure the on-accent token was introduced to end. */
  --color-text-on-deal: #ffffff;

  /* ---- Lines — the only separator this theme owns ---- */
  --color-border: #e8e5e0;
  --color-border-strong: #d6d1ca;
  --color-border-hover: #14110f;
  --color-border-focus: #14110f;

  /* ---- Header ---- */
  --color-header: #ffffff;
  --color-header-text: #14110f;
  --color-header-border: #e8e5e0;
  --header-h: 76px;

  /* ---- Footer ---- */
  --color-footer: #ffffff;
  --color-footer-text: #56534e;
  --color-footer-text-muted: #8a857e;
  --color-footer-heading: #14110f;
  --color-footer-border: #e6e2dc;

  /* ---- Status — desaturated so they sit inside the palette ---- */
  --color-success: #3e6b4f;
  --color-success-light: #eff4f0;
  --color-error: #a3402f;
  --color-error-light: #fbf1ef;
  --color-warning: #b08540;
  --color-info: #14110f;

  /* ---- Typography stacks ----
       Display is a high-contrast serif — that IS the brand. Body is a quiet
       geometric sans that gets out of its way. ---- */
  --font-display-ar: "Amiri", "Cormorant Garamond", Georgia, serif;
  --font-display-en: "Cormorant Garamond", Georgia, "Times New Roman", serif;
  --font-body-ar: "Tajawal", system-ui, -apple-system, sans-serif;
  --font-body-en: "Jost", system-ui, -apple-system, sans-serif;
  /* Numerals ride on the Latin body — Jost has clean lining figures. */
  --font-num: "Jost", "Tajawal", system-ui, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  --font-size-base: 16px;

  /* ---- Tracking — LTR ONLY. Never applied to Arabic. ---- */
  --track-nav: 0.1em;
  --track-micro: 0.14em;

  /* ---- Spacing · 4px grid, tuned generous ---- */
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;
  --space-9: 96px;
  --space-10: 128px;

  /* ---- Layout ---- */
  --container-max: 1440px;
  --container-px: 32px;
  --container-px-sm: 20px;
  --section-py: 96px;
  --section-py-sm: 56px;

  /* ---- Radii — square. This is editorial, not an app. ---- */
  --radius-xs: 0px;
  --radius-sm: 0px;
  --radius-md: 0px;
  --radius-lg: 0px;
  --radius-xl: 0px;
  --radius-2xl: 0px;
  --radius-pill: 999px;
  /* semantic — the Theme Editor retargets these four */
  --radius-card: var(--radius-md);
  --radius-btn: var(--radius-md);
  --radius-input: var(--radius-md);
  --radius-media: var(--radius-md);

  /* ---- Shadows — almost none. Kept so legacy rules resolve. ---- */
  --shadow-xs: none;
  --shadow-sm: 0 1px 0 rgba(20, 17, 15, 0.06);
  --shadow-md: 0 2px 16px -8px rgba(20, 17, 15, 0.16);
  --shadow-lg: 0 12px 40px -20px rgba(20, 17, 15, 0.22);
  --shadow-xl: 0 24px 64px -28px rgba(20, 17, 15, 0.26);
  --shadow-accent: none;
  --shadow-deal: none;
  --shadow-focus: 0 0 0 2px rgba(20, 17, 15, 0.18);
  /* semantic — retargeted by the "card elevation" setting */
  --shadow-card: none;

  /* ---- Motion — slow and small. Elegance is restraint. ---- */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
  --t-fast: 180ms;
  --t-base: 300ms;
  --t-image: 700ms;

  /* Height the page reserves for the PDP's fixed buy bar. Declared HERE, not
     on .hy-pdp: custom properties inherit DOWN, and the rule that consumes
     this one is on <body> — an ancestor of .hy-pdp, which would never see it.
     Measured, not guessed: 12px padding × 2 + a 44px control. */
  --hy-buybar-h: 68px;

  /* ---- Z-index scale ---- */
  --z-header: 100;
  --z-mega: 110;
  --z-overlay: 200;
  --z-drawer: 210;
  --z-modal: 220;
  --z-buybar: 300;
}

/* ==========================================================================
   02. BASE & RESET
   ========================================================================== */

/* ⚠️  border-box is set HERE, not left to Tailwind's preflight.

   Tailwind on this theme is the Play CDN: it generates its stylesheet only
   after its script has run and scanned the DOM, so for the first frames of
   every page load NO preflight exists. Under the default `content-box`,
   `.hy-container` — `width: 100%` plus `padding-inline: 32px` — computes to
   the viewport PLUS 64px, and every section on the page overflows sideways
   before snapping back. Measured on a 1400px viewport: container 1452px,
   219 elements outside the viewport, a horizontal scrollbar on the body.

   It is one declaration and it removes the dependency entirely. */
*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Alpine.js — hide until initialised */
[x-cloak] {
  display: none !important;
}

*,
*::before,
*::after {
  -webkit-tap-highlight-color: transparent;
}

html {
  scroll-behavior: smooth;
  /* Clearance so an #anchor does not land UNDER the fixed header.
     Measured, not typed: this was a flat 96px while the header is 38 + 84 + 58
     = 180px on desktop, so every anchor target arrived 84px behind it. The
     header's own script already measures itself for its spacer; it now writes
     that same number here, so the two can never disagree.
     Falls back to 0 — with `sticky` switched off there is no fixed header to
     clear, and the variable is only ever set when there is one. */
  scroll-padding-top: var(--hy-header-h, 0px);
}

body {
  /* Page-level safety net for horizontal scroll. `clip`, NOT `hidden`:
     `overflow: hidden` on body makes the element a scroll container, which
     silently kills `position: sticky` on the header and breaks
     `scroll-behavior: smooth` for #anchors. `clip` cuts the overflow without
     creating a scroll port, so both keep working.
     Only the inline axis is clipped — clipping the block axis would cut the
     sticky buy bar and any element that legitimately hangs below the fold. */
  overflow-x: clip;
  font-family: var(--font-body-ar);
  font-size: var(--font-size-base);
  font-weight: 400;
  line-height: 1.65;
  background: var(--color-bg-primary);
  color: var(--color-text-primary);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

html[lang="en"] body,
html[dir="ltr"] body {
  font-family: var(--font-body-en);
}

a {
  color: inherit;
  text-decoration: none;
  transition: color var(--t-fast) var(--ease-soft);
}

img,
svg,
video,
canvas,
/* `iframe` belongs here too: a merchant pasting a YouTube embed into a page or
   an article otherwise ships a fixed 560px box that overflows every phone. */
iframe {
  display: block;
  max-width: 100%;
}

::selection {
  background: var(--color-accent);
  color: var(--color-text-on-accent);
}

:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* Native scrollbar — well track, steel thumb */
@supports selector(::-webkit-scrollbar) {
  body::-webkit-scrollbar {
    width: 12px;
  }
  body::-webkit-scrollbar-track {
    background: var(--color-bg-tertiary);
  }
  body::-webkit-scrollbar-thumb {
    background: var(--color-border-strong);
    border-radius: var(--radius-pill);
    border: 3px solid var(--color-bg-tertiary);
  }
  body::-webkit-scrollbar-thumb:hover {
    background: var(--color-border-hover);
  }
}

/* Thin scrollbar for inner rails (spec tables, drawers, compare tables) */
.taqa-scroll-thin {
  scrollbar-width: thin;
}
.taqa-scroll-thin::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}
.taqa-scroll-thin::-webkit-scrollbar-track {
  background: transparent;
}
.taqa-scroll-thin::-webkit-scrollbar-thumb {
  background: var(--color-border-strong);
  border-radius: var(--radius-pill);
}

.taqa-text-sm {
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--color-text-secondary);
}

/* Long-form body — articles, product descriptions */
.taqa-prose {
  max-width: 72ch;
  color: var(--color-text-secondary);
  line-height: 1.8;
  font-size: 0.9375rem;
  /* Merchant-authored HTML: a pasted URL or an unbroken product code has no
       break opportunity and pushes the whole page wider on a phone. */
  overflow-wrap: anywhere;
}
.taqa-prose img {
  max-width: 100%;
  height: auto;
}
.taqa-prose pre {
  overflow-x: auto;
}
.taqa-prose > * + * {
  margin-top: 1em;
}
/* The Latin face is not optional here: .taqa-prose is live code — it renders
   every blog article body (widgets/article-page) and every CMS page
   (widgets/page-content). Left Arabic-only, an English store set all of its
   article and page headings in Amiri, a naskh face. It carries Latin glyphs so
   nothing renders as boxes, which is exactly why this went unnoticed: the
   headings simply read in the wrong typeface, against the theme's own type
   system. Same pattern the .hy-* components already use for this. */
html[lang="en"] .taqa-prose h2,
html[lang="en"] .taqa-prose h3,
html[lang="en"] .taqa-prose h4,
html[dir="ltr"] .taqa-prose h2,
html[dir="ltr"] .taqa-prose h3,
html[dir="ltr"] .taqa-prose h4 {
  font-family: var(--font-display-en);
}
.taqa-prose h2,
.taqa-prose h3,
.taqa-prose h4 {
  font-family: var(--font-display-ar);
  color: var(--color-text-primary);
  font-weight: 700;
  line-height: 1.3;
  margin-top: 1.75em;
  letter-spacing: -0.015em;
}
.taqa-prose h2 {
  font-size: 1.375rem;
}
.taqa-prose h3 {
  font-size: 1.125rem;
}
.taqa-prose h4 {
  font-size: 1rem;
}
.taqa-prose a {
  color: var(--color-accent);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.taqa-prose a:hover {
  color: var(--color-accent-hover);
}
.taqa-prose ul,
.taqa-prose ol {
  padding-inline-start: 1.35em;
}
.taqa-prose ul {
  list-style: disc;
}
.taqa-prose ol {
  list-style: decimal;
}
.taqa-prose li + li {
  margin-top: 0.4em;
}
.taqa-prose li::marker {
  color: var(--color-accent);
}
.taqa-prose img {
  border-radius: var(--radius-media);
  margin-block: 1.5em;
}
.taqa-prose blockquote {
  border-inline-start: 3px solid var(--color-accent);
  padding-inline-start: var(--space-4);
  color: var(--color-text-primary);
  font-weight: 500;
}
.taqa-prose code {
  font-family: var(--font-mono);
  font-size: 0.875em;
  background: var(--color-bg-tertiary);
  padding: 2px 6px;
  border-radius: var(--radius-xs);
}
/* A size chart or a spec table has a minimum content width and will not shrink
   to a 360px phone. main.js wraps every prose table in `.hy-scroll-x` at load,
   so the TABLE scrolls inside its own box instead of the page scrolling. The
   table itself is left as a real table — `display: block` would have been
   simpler but throws away the column layout.

   ⚠️  `.hy-pdp-panel-body` is listed beside `.taqa-prose` in every rule below.
   It is the THIRD place this theme renders merchant-authored HTML (the product
   description, in a panel that opens by default) and it was the only one not
   guarded: a spec table there pushed the whole product page wider than the
   viewport, which in turn threw every `position: fixed` element — the drawer
   and its overlay — off the visible area. Add a fourth surface for authored
   HTML and it has to be listed here as well. */
.hy-scroll-x {
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.taqa-prose table,
.hy-pdp-panel-body table {
  width: 100%;
  min-width: 380px;
  border-collapse: collapse;
  font-size: 0.875rem;
}
.taqa-prose th,
.taqa-prose td,
.hy-pdp-panel-body th,
.hy-pdp-panel-body td {
  border: 1px solid var(--color-border);
  padding: 10px 12px;
  text-align: start;
}
.taqa-prose th,
.hy-pdp-panel-body th {
  background: var(--color-bg-tertiary);
  font-weight: 600;
  color: var(--color-text-primary);
}
.taqa-bg-ink .taqa-text-sm {
  color: rgba(255, 255, 255, 0.72);
}

/* ---- Grids & rails ----
   Column counts are driven by CSS vars so a widget can set them inline from
   its schema without shipping a class per permutation. */
.taqa-grid {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(var(--taqa-cols-mobile, 2), minmax(0, 1fr));
}
@media (min-width: 768px) {
  .taqa-grid {
    gap: var(--space-5);
    grid-template-columns: repeat(var(--taqa-cols-tablet, 3), minmax(0, 1fr));
  }
}
@media (min-width: 1024px) {
  .taqa-grid {
    grid-template-columns: repeat(var(--taqa-cols, 4), minmax(0, 1fr));
  }
}

/* ==========================================================================
   05. BUTTONS
   Square-ish, confident, no gradients. Blue = primary action only.
   ========================================================================== */

.taqa-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 22px;
  border: 1px solid transparent;
  border-radius: var(--radius-btn);
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  transition:
    background-color var(--t-fast) var(--ease-soft),
    border-color var(--t-fast) var(--ease-soft),
    color var(--t-fast) var(--ease-soft),
    box-shadow var(--t-fast) var(--ease-soft),
    transform var(--t-fast) var(--ease-out);
  letter-spacing: -0.005em;
}
.taqa-btn:active:not(:disabled) {
  transform: translateY(1px);
}
.taqa-btn:disabled,
.taqa-btn[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}
.taqa-btn svg {
  flex: none;
}

.taqa-btn--primary {
  background: var(--color-accent);
  color: var(--color-text-on-accent);
  border-color: var(--color-accent);
}
.taqa-btn--primary:hover:not(:disabled) {
  background: var(--color-accent-hover);
  border-color: var(--color-accent-hover);
  box-shadow: var(--shadow-accent);
}

.taqa-btn--outline {
  background: transparent;
  color: var(--color-text-primary);
  border-color: var(--color-border-strong);
}
.taqa-btn--outline:hover:not(:disabled) {
  border-color: var(--color-accent);
  color: var(--color-accent);
  background: var(--color-accent-light);
}

.taqa-btn--soft {
  background: var(--color-accent-light);
  color: var(--color-accent-dark);
  border-color: transparent;
}
.taqa-btn--soft:hover:not(:disabled) {
  background: var(--color-accent-soft);
}

.taqa-btn--ghost {
  background: transparent;
  color: var(--color-text-secondary);
  border-color: transparent;
  padding-inline: 12px;
}
.taqa-btn--ghost:hover:not(:disabled) {
  background: var(--color-bg-tertiary);
  color: var(--color-text-primary);
}
.taqa-btn--sm {
  padding: 9px 16px;
  font-size: 0.8125rem;
}

/* Icon buttons */
.taqa-btn-icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex: none;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-text-primary);
  cursor: pointer;
  transition:
    background-color var(--t-fast) var(--ease-soft),
    border-color var(--t-fast) var(--ease-soft),
    color var(--t-fast) var(--ease-soft);
}
.taqa-btn-icon:hover {
  background: var(--color-bg-tertiary);
}

.taqa-btn-icon--outline {
  border-color: var(--color-border-strong);
  background: var(--color-surface);
}

/* one card per row, whatever column count the grid was set to */
.taqa-list-view .taqa-grid {
  grid-template-columns: minmax(0, 1fr) !important;
}

/* Overlay · drawer · modal */
/* Shared by the drawer, the search sheet and the quick-view modal.
   The tint is color-mix()'d off --color-ink rather than written out, so
   retinting the palette retints the dimmer with it. It went through a
   hardcoded navy (survived a whole palette change unnoticed) and then an
   --color-ink-rgb triple that the Theme Editor could not write, which froze it
   just as badly the moment a merchant picked a different ink. Lighter and unblurred:
   at 0.5 with a blur it reads as a heavy app modal, which is the opposite of
   what a page this quiet should feel like. */
.taqa-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  background: color-mix(in srgb, var(--color-ink) 34%, transparent);
}

/* --------------------------------------------------------------------------
   Drawer
   Off-canvas by default, slid in with `.is-open`. The physical side has to be
   resolved per direction (`right`/`left`, not `inset-inline-*`) because the
   translate that hides it must point the same way — logical props alone would
   slide an RTL drawer into view instead of out of it.
   -------------------------------------------------------------------------- */
.taqa-drawer {
  position: fixed;
  inset-block: 0;
  z-index: var(--z-drawer);
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 400px;
  background: var(--color-surface);
  box-shadow: var(--shadow-xl);
  transition: transform var(--t-base) var(--ease-out);
  will-change: transform;
}

/* Trailing edge (default) — end of the reading direction */
html[dir="rtl"] .taqa-drawer {
  left: 0;
  transform: translateX(-100%);
}
html[dir="ltr"] .taqa-drawer {
  right: 0;
  transform: translateX(100%);
}

/* Leading edge — mobile menu lives here */
html[dir="rtl"] .taqa-drawer--start {
  right: 0;
  left: auto;
  transform: translateX(100%);
}
html[dir="ltr"] .taqa-drawer--start {
  left: 0;
  right: auto;
  transform: translateX(-100%);
}

.taqa-drawer.is-open {
  transform: translateX(0) !important;
}

.taqa-drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex: none;
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--color-border);
}
.taqa-drawer-body {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-5);
}
.taqa-drawer-foot {
  flex: none;
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--color-border);
  background: var(--color-surface);
}

.taqa-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-8) var(--space-4);
  text-align: center;
  color: var(--color-text-secondary);
}
.taqa-empty-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  margin-bottom: 2px;
  border-radius: var(--radius-lg);
  background: var(--color-bg-tertiary);
  color: var(--color-text-tertiary);
}

/* --------------------------------------------------------------------------
   shop-products: the SSR copy stays hidden until it is AJAX-mounted
   ----------------------------------------------------------------------------
   `shop-products` is listed as its own widget in pages/shop.json, so Qumra
   renders it once in page order AND once inside
   #products-grid-wrapper[data-qumra-section="shop-products"]. Without this pair
   of rules the grid appears twice — so the widget wraps itself in
   .shop-products-content, which only becomes visible inside the mount point.

   ⚠️  Both lines are load-bearing. Drop the second one and the shop page shows
   an empty results column with a working product count beside it.
   -------------------------------------------------------------------------- */
/* The widget's own inline script moves the page-order copy INTO the wrapper on
   first load, so this pair no longer hides the only copy that exists. Keep
   both lines: they are what stops a second copy showing after a filter run. */
.shop-products-content {
  display: none;
}
[data-qumra-section="shop-products"] .shop-products-content {
  display: block;
}

/* ==========================================================================
   12. FEEDBACK  ·  TOAST
   --------------------------------------------------------------------------
   The section index at the top of this file has always listed a "12. Feedback
   (toast · …)" section. It was not in the file: sections 06–12 went out with
   the .taqa-* legacy, and the toast's rules went with them while the JS that
   creates it stayed. Toast._show() builds `class="toast toast-<type>"`, so
   every success and every error since then has rendered as bare unstyled text
   — no ground, no shape, and no green or red at all.

   Written as real CSS rather than utility classes on the host element, for the
   reason this file gives elsewhere: Tailwind here is the Play CDN and it
   generates nothing until its own script has run.
   ========================================================================== */

/* The host. `pointer-events: none` so a toast can never swallow a click meant
   for the page; nothing inside it is interactive. */
#toast-container {
  position: fixed;
  /* Below the header, not on top of it. `top: 20px` put the toast across the
     logo, the search field and the cart button — on a z-index of 9999, over a
     header whose own layer is 100. `--hy-header-h` is the height the header
     measures for its own spacer (see 15.1), so the toast follows a header that
     changes height instead of guessing at one. Unset — no sticky header — the
     fallback is the small offset a scrolling header does not need more than. */
  inset-block-start: calc(var(--hy-header-h, 20px) + 12px);
  inset-inline: 0;
  z-index: 9999;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding-inline: var(--space-4);
  pointer-events: none;
}

.toast {
  max-width: min(420px, 100%);
  padding: 12px var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--radius-btn);
  background: var(--color-text-primary);
  color: var(--color-bg-primary);
  font-family: var(--font-body-ar);
  font-size: 0.875rem;
  line-height: 1.5;
  text-align: start;
  /* A floating layer needs separation from whatever is under it — the same
     call the quick-view panel makes. Everything anchored to the page still
     uses a hairline. */
  box-shadow: var(--shadow-lg);
  animation: hyToastIn var(--t-base) var(--ease-out);
}
html[lang="en"] .toast,
html[dir="ltr"] .toast {
  font-family: var(--font-body-en);
}

/* The status colours are already desaturated to sit inside this palette, so a
   solid ground reads as meaning rather than as an alert box. `on-accent` is
   what the theme uses for text over any filled surface. */
.toast-success {
  background: var(--color-success);
  color: var(--color-text-on-accent);
}
.toast-error {
  background: var(--color-error);
  color: var(--color-text-on-accent);
}
.toast-warning {
  background: var(--color-warning);
  color: var(--color-text-on-accent);
}
.toast-info {
  background: var(--color-info);
  color: var(--color-text-on-accent);
}

@keyframes hyToastIn {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* ==========================================================================
   13. THIRD-PARTY OVERRIDES
   ========================================================================== */

/* ---- Swiper ----
   ⚠️  There used to be a global `.swiper { overflow: visible }` here, inherited
   from the previous theme. It is removed on purpose and must not come back.

   Beyond letting slides spill over neighbouring sections, it breaks LAYOUT:
   with the clipping gone, `.swiper-wrapper` — a flex row of flex-shrink:0
   slides — reports a min-content width equal to ALL the slides added together.
   Any grid or flex ancestor has `min-width: auto` and therefore cannot shrink
   below that, so a slider sitting in one half of a two-column section forces
   its column to the full width of its own track and collapses the other column
   to zero. Measured: a 10-card rail turned a 1fr/1fr grid into `0px 2148px`.

   Swiper's own stylesheet sets `overflow: hidden`, which is correct. Anything
   that genuinely needs to overflow should opt in on its own element, scoped. */
.swiper-wrapper {
  box-sizing: border-box;
}

.swiper-pagination-bullet {
  width: 6px;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--color-border-strong);
  opacity: 1;
  transition:
    width var(--t-base) var(--ease-out),
    background-color var(--t-base) var(--ease-soft);
}
.swiper-pagination-bullet-active {
  width: 22px;
  background: var(--color-accent);
}

.swiper-scrollbar {
  height: 3px !important;
  border-radius: var(--radius-pill);
  background: var(--color-bg-tertiary);
}
.swiper-scrollbar-drag {
  background: var(--color-accent);
  border-radius: var(--radius-pill);
}

/* ---- GLightbox ---- */
.glightbox-clean .gslide-description {
  background: var(--color-surface);
}
.glightbox-clean .gdesc-inner {
  padding: 20px;
}
html[lang="en"] .glightbox-clean .gslide-title,
html[dir="ltr"] .glightbox-clean .gslide-title {
  font-family: var(--font-display-en);
}
.glightbox-clean .gslide-title {
  font-family: var(--font-display-ar);
  font-weight: 700;
  color: var(--color-text-primary);
}
.glightbox-clean .gslide-desc {
  font-size: 0.875rem;
  color: var(--color-text-secondary);
}
.gbtn {
  background: color-mix(in srgb, var(--color-ink) 72%, transparent) !important;
  border-radius: var(--radius-md) !important;
}

/* ==========================================================================
   14. UTILITIES & MOTION
   ========================================================================== */

/* Numerals utility — apply anywhere a raw number is printed */
.font-num {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
}
.font-display {
  font-family: var(--font-display-ar);
}
html[dir="ltr"] .font-display {
  font-family: var(--font-display-en);
}
.font-mono {
  font-family: var(--font-mono);
}

/* Mirror a directional glyph (chevrons, arrows) when the page flips to RTL */
.rtl-flip {
  transition: transform var(--t-fast) var(--ease-soft);
}
html[dir="rtl"] .rtl-flip {
  transform: scaleX(-1);
}

@keyframes taqaSpin {
  to {
    transform: rotate(360deg);
  }
}

@keyframes taqaShimmer {
  100% {
    transform: translateX(100%);
  }
}
@keyframes taqaShimmerRtl {
  100% {
    transform: translateX(-100%);
  }
}

@keyframes taqaPulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}

@keyframes taqaToastIn {
  from {
    opacity: 0;
    transform: translateY(-12px) scale(0.97);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes taqaToastOut {
  to {
    opacity: 0;
    transform: translateY(-8px) scale(0.97);
  }
}

@keyframes taqaMarquee {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-100%);
  }
}
@keyframes taqaMarqueeRtl {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(100%);
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Print — spec sheets get printed in this category more than you'd think */
@media print {
  /* What a printed page is for is the CONTENT. Everything here is either
     navigation the paper cannot follow, a control the paper cannot accept, or
     an overlay that would print as a grey slab across the sheet.

     The list is explicit rather than a blanket `nav, button { display: none }`:
     the pager's numbers and the breadcrumb are genuinely useful on paper, and a
     blanket rule takes those too.

     ⚠️  An overlay or a fixed bar added to this theme belongs in this list. A
     `position: fixed` element that survives to print does not stay where it sat
     on screen — it repeats on EVERY page of the printout. */
  .taqa-drawer,
  .taqa-overlay,
  .hy-header,
  .hy-topbar,
  .hy-footer,
  .hy-pdp-bar,
  .hy-qv-modal,
  #toast-container,
  .hy-shop-tools,
  .hy-shop-aside,
  .hy-pcard-atc,
  .hy-pcard-eye,
  .hy-pdp-actions,
  .hy-pdp-share {
    display: none !important;
  }

  /* The spacer reserves room for a fixed header that is no longer there. */
  [id^="hy-header-spacer-"] {
    display: none !important;
  }

  body {
    background: #fff;
    /* The reservation for the sticky buy bar, which is also gone. */
    padding-block-end: 0 !important;
  }

  /* Links print as text; the URL behind them is lost either way, and an
     underline on every product name turns the sheet into noise. */
  a {
    text-decoration: none;
    color: inherit;
  }

  /* Ink. A full-bleed section background is a page of toner for no reading. */
  .hy-section,
  .hy-cols,
  .hy-shop {
    background: none !important;
  }

  /* Do not split a product row or a cart line across two sheets. */
  .hy-pcard,
  .hy-citem,
  .hy-pdp-panel {
    break-inside: avoid;
  }
}

/* ==========================================================================
   15. HYPER COMPONENTS  ·  .hy-*
   The new theme lives here. Sections 04–13 above are the previous theme's
   components, still in place so nothing 404s while we replace them one at a
   time. When a section's .hy-* replacement lands, delete its .taqa-* original.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Arabic tracking guard
   --------------------------------------------------------------------------
   Arabic is a connected script. Positive tracking pulls the joins apart into
   disconnected shapes; negative tracking crushes them. There is no correct
   non-zero value, so every .hy-* component reads its tracking from a token
   and RTL zeroes the tokens — one switch, no per-rule overrides.

   The block after it does the same job for the legacy .taqa-* classes that
   hardcode a value. Those rules disappear as their sections get replaced.
   -------------------------------------------------------------------------- */
html[dir="rtl"] {
  --track-nav: 0;
  --track-micro: 0;
}
html[dir="rtl"] .taqa-prose h2,
html[dir="rtl"] .taqa-prose h3,
html[dir="rtl"] .taqa-prose h4 {
  letter-spacing: 0;
}
html[dir="rtl"] .hy-num {
  letter-spacing: normal;
}

/* --------------------------------------------------------------------------
   15.0  CONTAINER
   --------------------------------------------------------------------------
   A real class, not Tailwind's `.container` utility. Tailwind here is the Play
   CDN: it generates CSS only after its script runs and scans the DOM, so on
   the first frames of every load `.container` does not exist and anything
   using it spans the full viewport, then snaps inward. For chrome that is
   visible before anything else — the header above all — that snap is the
   first thing a visitor sees. This ships with the stylesheet instead.
   -------------------------------------------------------------------------- */
.hy-container {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--container-px);
}
@media (max-width: 640px) {
  .hy-container {
    padding-inline: var(--container-px-sm);
  }
}
.hy-container--narrow {
  max-width: 860px;
}
.hy-container--flush {
  max-width: none;
  padding-inline: 0;
}

/* --------------------------------------------------------------------------
   15.0.1  IMAGE FRAMES  ·  every picture shows WHOLE
   --------------------------------------------------------------------------
   Every image of a thing — product, room, collection, article — is
   `object-fit: contain`, so the shopper sees the entire sofa instead of a
   centre crop with its legs cut off.

   `contain` letterboxes: when the photo's ratio differs from the frame's there
   is leftover space, and that space shows whatever sits behind. So every frame
   needs a ground of its own. These eleven had none — they relied on the crop
   filling the box. The wash is the theme's own well colour, so a transparent
   PNG and a square JPEG land on the same neutral card.

   ⚠️  Three images stay `cover` on purpose and are NOT listed here:
     .hy-pcard-swatch img / .hy-pdp-swatch--img img — a colour swatch is a
       SAMPLE; contain would letterbox it and it stops reading as a colour.
     .hy-hero-media img — a full-bleed photograph with the headline over it.
       It is the one crop in the theme that someone is around to AIM; see 15.5.
   -------------------------------------------------------------------------- */
.hy-pcard-media,
.hy-qv-shot,
.hy-citem-media,
.hy-promo,
.hy-coll-media,
.hy-cgrid-disc,
.hy-ctabs-pane,
.hy-fav-pane,
.hy-look-tile,
.hy-split-media,
.hy-col-figure {
  background: var(--color-bg-tertiary);
}

/* --------------------------------------------------------------------------
   15.1  HEADER  ·  .hy-topbar-* / .hy-mainbar / .hy-search-* / .hy-navbar
   --------------------------------------------------------------------------
   Three stacked rows, each with its own job and its own height:

     topbar   38px   links · message · locale + socials   (indigo strip)
     mainbar  84px   logo · [ category ▾ | search | ⌕ ] · link · ♡ · bag
     navbar   58px   Shop By … ▾   Rooms ▾   Tables ▾   …   On Sale

   The main row is a 3-column grid — `auto | 1fr | auto`. The search field is
   the only elastic column, so the logo and the action cluster keep their
   natural widths and the field absorbs every viewport between them. A flex row
   would have let the field's `min-width: auto` push the actions off-screen the
   moment a long store name appeared.

   ⚠️  ARABIC — every `letter-spacing` below is scoped to [dir="ltr"]. Tracking
   breaks Arabic letter joining; see the file header.

   ⚠️  The responsive rules at the end are plain media queries, not Tailwind
   utilities. Tailwind here is the Play CDN: it generates its CSS only after its
   script runs and scans the DOM, so for the first frames of every load no
   `lg:hidden` exists and the header paints the mobile chrome and the desktop
   chrome at once. This block ships with the stylesheet, so it is correct on
   frame one. Breakpoints mirror Tailwind's defaults.
   -------------------------------------------------------------------------- */

.hy-header {
  --hy-topbar-h: 38px;
  --hy-mainbar-h: 84px;
  --hy-navbar-h: 58px;
  /* The pill is the header's own shape and does NOT read --radius-input. The
     Theme Editor's corner setting squares off cards and buttons across the
     theme; squaring the search field as well would leave the main row with no
     shape at all, which is the one thing this design is built around. */
  --hy-search-r: 999px;
  --hy-search-h: 52px;
  --hy-hot: var(--color-deal);

  position: relative;
  z-index: var(--z-header);
  background: var(--color-header);
  color: var(--color-header-text);
}

.hy-header.is-sticky {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  transition: transform var(--t-base) var(--ease-out);
  will-change: transform;
}

/* Compact mode — the announcement strip slides out from under the main row on
   scroll. A transform, not a height collapse: the rows have to move as one
   unit, and animating height reflows the whole page on every frame. The JS
   only flips this class once scrollY has passed the strip's own height, so the
   spacer below has already scrolled away and no white gap is ever exposed. */
.hy-header.sticky-compact.is-scrolled {
  transform: translateY(calc(-1 * var(--hy-topbar-h)));
}

/* ==========================================================================
   TOPBAR
   ========================================================================== */

.hy-topbar {
  position: relative; /* containing block for the dismiss button */
  background: var(--hy-topbar-bg, #2a2aa0);
  color: var(--hy-topbar-fg, #fff);
}

/* ---- Dismiss ----
   Out of flow so the three-column row keeps its centred message. It inherits
   the bar's own foreground, so it reads on whatever colour the merchant picked
   without a second setting.

   `inset-inline-end` and not `right`: in Arabic the bar reads right-to-left and
   the close belongs at the end of the line, which is the LEFT edge there. */
.hy-topbar-close {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-end: 6px;
  transform: translateY(-50%);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  opacity: 0.65;
  cursor: pointer;
  transition: opacity var(--t-fast) var(--ease-soft);
}
.hy-topbar-close:hover {
  opacity: 1;
}
.hy-topbar-close:focus-visible {
  opacity: 1;
  outline: 2px solid currentColor;
  outline-offset: 1px;
}

.hy-topbar-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-4);
  min-height: var(--hy-topbar-h);
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1.4;
}

.hy-topbar-start,
.hy-topbar-end {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  min-width: 0;
}
.hy-topbar-end {
  justify-content: flex-end;
  gap: var(--space-4);
}

.hy-topbar-msg {
  margin: 0;
  text-align: center;
  color: inherit;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
a.hy-topbar-msg:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ---- Locale ----
   A label, not a picker, for the currency half: Qumra resolves the market
   server-side and exposes no endpoint to change it. The control is the
   language switcher. See the widget's header note. */
.hy-locale {
  position: relative;
}

.hy-locale-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 2px;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  white-space: nowrap;
  opacity: 0.92;
  transition: opacity var(--t-fast) var(--ease-soft);
}
.hy-locale-btn:hover {
  opacity: 1;
}

.hy-locale-cur {
  opacity: 0.72;
}

.hy-locale-drop {
  position: absolute;
  inset-block-start: calc(100% + 8px);
  inset-inline-end: 0;
  z-index: var(--z-mega);
  min-width: 168px;
  padding: 6px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-lg);
}
.hy-locale-drop form {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.hy-locale-drop button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  padding: 9px 12px;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-text-primary);
  font-size: 0.875rem;
  text-align: start;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease-soft);
}
.hy-locale-drop button:hover:not(:disabled) {
  background: var(--color-bg-tertiary);
}
.hy-locale-drop button:disabled {
  cursor: default;
  font-weight: 600;
}

/* ---- Socials ---- */
.hy-socials {
  display: flex;
  align-items: center;
  gap: 2px;
}

.hy-social {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  color: inherit;
  opacity: 0.9;
  transition:
    opacity var(--t-fast) var(--ease-soft),
    transform var(--t-fast) var(--ease-out);
}
.hy-social svg {
  width: 16px;
  height: 16px;
}
.hy-social:hover {
  opacity: 1;
  transform: translateY(-1px);
}

/* ==========================================================================
   MAIN BAR
   ========================================================================== */

.hy-mainbar {
  background: var(--color-header);
}

.hy-mainrow {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-6);
  min-height: var(--hy-mainbar-h);
}

/* ---- The brand slot ----
   ONE width budget, inherited by whichever child fills it. Before this the
   logo carried its own cap and the wordmark carried none at all, so a store
   whose name is long — the acceptance test uses 60 characters — rendered it at
   full length: at the clamped 22px that is roughly 660px, against 320px of
   container on a 360px screen. It did not wrap and it did not truncate, and
   with <body> now clipping the inline axis it would be cut off silently rather
   than pushing out a scrollbar you could at least see.

   `min-width: 0` is what lets the ellipsis happen at all — a flex/grid child
   defaults to `min-width: auto`, which refuses to shrink below its content and
   makes `text-overflow` unreachable. `flex-shrink: 0` stays: the cap is now
   explicit, so the slot should hold its size against its siblings rather than
   being squeezed by the search field. */
.hy-brand {
  --hy-brand-max: 220px;

  display: inline-flex;
  align-items: center;
  min-width: 0;
  max-width: var(--hy-brand-max);
  color: var(--color-header-text);
  flex-shrink: 0;
}
@media (max-width: 1023px) {
  /* Below 1024 the header collapses to "burger brand actions" over
     "search search search", so the brand shares a ROW with the burger and the
     action icons but holds a column of its own. Measured against that row at
     320, 360 and 390 — see the note on .hy-brand-logo. */
  .hy-brand {
    --hy-brand-max: min(220px, 38vw);
  }
}

/* ---- The uploaded logo ----
   Sized by HEIGHT with `width: auto`, so a 600×120 and a 300×300 both keep
   their own proportions — the merchant sets one number and no aspect ratio is
   ever imposed. `object-fit: contain` is the second guard: when the width cap
   below binds, the box stops matching the file's ratio, and contain letterboxes
   inside it instead of stretching. Both are needed; neither alone is enough.

   ⚠️  The cap is `min()`, not a flat 220px. Below 1024 the header collapses to
   `"burger actions" / "search search"`, so the logo shares one row with the
   burger and the action icons. Measured against that row: a 220px logo leaves
   -48px at 360 and -88px at 320 — the icons get pushed out, and since <body>
   now clips the inline axis they are silently cut rather than scrolled to.
   38vw is the widest value that clears 320, 360 and 390. */
.hy-brand-logo {
  height: var(--hy-logo-h, 30px);
  width: auto;
  /* The cap lives on .hy-brand now — one number for the logo and the wordmark,
     so the two can never be given different room. */
  max-width: 100%;
  object-fit: contain;
}

.hy-wordmark {
  /* Truncates rather than overflows. One line, because the header row's height
     is a fixed token — a wrapped wordmark would push the search field and the
     action icons out of their row. The name is also on the <a>'s aria-label,
     so a screen reader still gets it whole. */
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;

  font-family: var(--font-body-ar);
  font-size: clamp(1.375rem, 2.2vw, 1.875rem);
  line-height: 1;
  text-transform: uppercase;
}
html[lang="en"] .hy-wordmark,
html[dir="ltr"] .hy-wordmark {
  font-family: var(--font-body-en);
  letter-spacing: -0.02em;
}

/* ---- Search ----
   A real <form> posting to /shop, which IS this theme's search results page.
   The category picker swaps the form's action to /shop/<handle> rather than
   adding a parameter, because collectionFilter() reads the collection off the
   PATH. See the widget's header note. */
.hy-search {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  max-width: 860px;
  height: var(--hy-search-h);
  margin-inline: auto;
  padding-inline-end: 6px;
  background: var(--color-bg-tertiary);
  border: 1px solid transparent;
  border-radius: var(--hy-search-r);
  transition:
    border-color var(--t-fast) var(--ease-soft),
    background var(--t-fast) var(--ease-soft);
}
.hy-search:focus-within {
  background: var(--color-surface);
  border-color: var(--color-border-strong);
}

.hy-search-scope {
  position: relative;
  flex-shrink: 0;
  align-self: stretch;
  display: flex;
  align-items: center;
}

.hy-search-scope-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 100%;
  padding-inline: 24px 16px;
  border: 0;
  background: none;
  color: var(--color-text-primary);
  font-family: inherit;
  font-size: 0.9375rem;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
}

/* The divider between the scope and the field. A pseudo-element rather than a
   border on the input: the input is `border: 0` so its focus ring can be the
   pill's, and a border-inline-start there would have flipped sides in RTL
   against the rest of the row. */
.hy-search-scope::after {
  content: "";
  position: absolute;
  inset-block: 12px;
  inset-inline-end: 0;
  width: 1px;
  background: var(--color-border-strong);
}

.hy-search-scope-drop {
  position: absolute;
  inset-block-start: calc(100% + 6px);
  inset-inline-start: 8px;
  z-index: var(--z-mega);
  min-width: 220px;
  max-height: 340px;
  overflow-y: auto;
  padding: 6px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-lg);
}
.hy-search-scope-drop button {
  display: block;
  width: 100%;
  padding: 9px 12px;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-text-primary);
  font-family: inherit;
  font-size: 0.875rem;
  text-align: start;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease-soft);
}
.hy-search-scope-drop button:hover {
  background: var(--color-bg-tertiary);
}
.hy-search-scope-drop button.is-active {
  font-weight: 600;
  background: var(--color-accent-light);
}

.hy-search-input {
  flex: 1 1 auto;
  /* Without this an <input> refuses to shrink below its intrinsic size and
     pushes the submit button out of the pill on narrow viewports. */
  min-width: 0;
  height: 100%;
  padding-inline: 18px;
  border: 0;
  background: none;
  color: var(--color-text-primary);
  font-family: inherit;
  font-size: 0.9375rem;
}
.hy-search-input::placeholder {
  color: var(--color-text-tertiary);
}
.hy-search-input:focus {
  outline: none;
}
/* Safari draws its own clear button on type=search and it collides with the
   submit icon. */
.hy-search-input::-webkit-search-decoration,
.hy-search-input::-webkit-search-cancel-button {
  -webkit-appearance: none;
}

.hy-search-go {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border: 0;
  border-radius: var(--radius-pill);
  background: none;
  color: var(--color-text-primary);
  cursor: pointer;
  transition:
    background var(--t-fast) var(--ease-soft),
    color var(--t-fast) var(--ease-soft);
}
.hy-search-go:hover {
  background: var(--color-accent);
  color: var(--color-text-on-accent);
}

/* ---- Live results ---- */
.hy-search-drop {
  position: absolute;
  inset-block-start: calc(100% + 8px);
  inset-inline: 0;
  z-index: var(--z-mega);
  max-height: 460px;
  overflow-y: auto;
  padding: 8px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-lg);
}

.hy-search-state {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 96px;
  color: var(--color-text-secondary);
  font-size: 0.875rem;
}
.hy-search-state p {
  margin: 0;
}

.hy-search-hit {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 10px;
  border-radius: var(--radius-sm);
  transition: background var(--t-fast) var(--ease-soft);
}
.hy-search-hit:hover {
  background: var(--color-bg-tertiary);
}

.hy-search-hit-img {
  flex-shrink: 0;
  display: block;
  width: 52px;
  height: 62px;
  overflow: hidden;
  background: var(--color-bg-tertiary);
  border-radius: var(--radius-media);
}
.hy-search-hit-img img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.hy-search-hit-body {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.hy-search-hit-name {
  color: var(--color-text-primary);
  font-size: 0.875rem;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hy-search-hit-price {
  color: var(--color-text-secondary);
  font-family: var(--font-num);
  font-size: 0.8125rem;
}

/* Replaces the price on a sold-out hit rather than sitting beside it: a price
   is an offer, and printing one next to "out of stock" invites the click this
   label exists to prevent. Deal colour, which is the theme's one channel for
   "this means something". */
/* The keyboard highlight. Same treatment as :hover so a shopper moving with
   the arrows and one moving with the mouse are looking at the same thing —
   `@mouseenter` sets the same index, so the two can never disagree. */
.hy-search-hit.is-active {
  background: var(--color-bg-tertiary);
}

.hy-search-hit-out {
  color: var(--color-deal);
  font-size: 0.8125rem;
}

.hy-search-all {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-block-start: 6px;
  padding: 13px;
  border-block-start: 1px solid var(--color-border);
  color: var(--color-accent);
  font-size: 0.875rem;
  font-weight: 600;
}
.hy-search-all:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ---- Actions ---- */
.hy-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-6);
  flex-shrink: 0;
}

.hy-action-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--color-header-text);
  font-size: 0.9375rem;
  font-weight: 500;
  white-space: nowrap;
  transition: opacity var(--t-fast) var(--ease-soft);
}
.hy-action-link:hover {
  opacity: 0.6;
}
.hy-action-link svg {
  flex-shrink: 0;
}

/* Kept from the previous header — .hy-icon-btn is shared with the shop page,
   the search sheet, the drawer and the quick-view modal, so it does not belong
   to this section alone and must survive any rewrite of it. */
.hy-icon-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  margin-inline: -5px; /* optical: pull the icons to the true edge */
  border: 0;
  background: none;
  color: var(--color-header-text);
  cursor: pointer;
  transition: opacity var(--t-fast) var(--ease-soft);
}
.hy-icon-btn:hover {
  opacity: 0.55;
}

.hy-cart-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border: 0;
  background: none;
  color: var(--color-header-text);
  cursor: pointer;
  transition:
    background var(--t-fast) var(--ease-soft),
    opacity var(--t-fast) var(--ease-soft);
}
.hy-cart-btn:hover {
  opacity: 0.6;
}

.hy-cart-btn--circle {
  width: 52px;
  height: 52px;
  border-radius: var(--radius-pill);
  background: var(--color-bg-tertiary);
}
.hy-cart-btn--circle:hover {
  opacity: 1;
  background: var(--color-accent);
  color: var(--color-text-on-accent);
}

/* The count rides on both the bag and the wishlist. */
.hy-count {
  position: absolute;
  inset-block-start: 2px;
  inset-inline-end: 0;
  min-width: 17px;
  height: 17px;
  padding-inline: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--hy-hot);
  color: var(--color-text-on-deal);
  font-family: var(--font-num);
  font-size: 0.625rem;
  font-weight: 600;
  line-height: 1;
}
.hy-cart-btn--circle .hy-count {
  inset-block-start: 5px;
  inset-inline-end: 3px;
}

/* ==========================================================================
   NAV BAR
   ========================================================================== */

.hy-navbar {
  background: var(--color-header);
  border-block-start: 1px solid var(--color-header-border);
  border-block-end: 1px solid var(--color-header-border);
}

/* The containing block for a WIDE dropdown. A multi-column panel hanging off
   the last nav item would run past the viewport edge, so it is anchored to the
   container instead — which only works if the container, not the item, is the
   nearest positioned ancestor. `.hy-nav-item--wide` goes `position: static`
   for exactly that reason. */
.hy-navbar .hy-container {
  position: relative;
}

.hy-nav {
  display: flex;
  align-items: center;
  gap: var(--space-7);
  min-height: var(--hy-navbar-h);
}
.hy-nav--center {
  justify-content: center;
}

.hy-nav-item {
  position: relative;
  /* The dropdown opens on hover with a 160ms close delay, and the gap between
     the link and the panel is bridged by the item's own full height — so the
     pointer never crosses dead space on its way down. */
  display: flex;
  align-items: center;
  align-self: stretch;
}

/* Yields the containing block to .hy-navbar .hy-container so its wide panel
   can span the full content width. */
.hy-nav-item--wide {
  position: static;
}

.hy-nav-link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--color-header-text);
  font-size: 0.9375rem;
  font-weight: 600;
  white-space: nowrap;
  transition: color var(--t-fast) var(--ease-soft);
}
.hy-nav-link:hover {
  color: var(--color-accent);
}
.hy-nav-link.is-active {
  color: var(--color-accent);
}

/* The highlighted item — "On Sale" in the reference. The sparkle is a
   pseudo-element rather than an image so it inherits the picked colour. */
.hy-nav-link.is-hot {
  color: var(--hy-hot);
}
.hy-nav-link.is-hot:hover {
  color: var(--hy-hot);
  opacity: 0.78;
}
.hy-nav-link.is-hot::after {
  content: "✦";
  align-self: flex-start;
  margin-inline-start: -2px;
  font-size: 0.625rem;
  line-height: 1;
}

.hy-nav-badge {
  padding: 2px 7px;
  border-radius: var(--radius-pill);
  background: var(--hy-hot);
  color: var(--color-text-on-deal);
  font-size: 0.625rem;
  font-weight: 600;
  line-height: 1.5;
}

.hy-caret {
  flex-shrink: 0;
  transition: transform var(--t-fast) var(--ease-soft);
}
.hy-caret--sm {
  width: 9px;
  height: 9px;
}
.hy-nav-item:hover .hy-nav-caret {
  transform: rotate(180deg);
}

/* ---- Dropdown ---- */
.hy-drop {
  position: absolute;
  inset-block-start: 100%;
  inset-inline-start: -14px;
  z-index: var(--z-mega);
  padding-block-start: 2px;
}

/* Anchored to .hy-navbar .hy-container — see the note there. */
.hy-drop--wide {
  inset-inline: 0;
}

.hy-drop-inner {
  display: flex;
  gap: var(--space-6);
  padding: var(--space-5) var(--space-6);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-lg);
}

.hy-drop-links {
  display: grid;
  grid-template-columns: repeat(var(--hy-drop-cols, 1), minmax(150px, 1fr));
  gap: 2px var(--space-6);
  align-content: start;
  flex: 1 1 auto;
}

.hy-drop-link {
  display: block;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  color: var(--color-text-secondary);
  font-size: 0.875rem;
  white-space: nowrap;
  transition:
    background var(--t-fast) var(--ease-soft),
    color var(--t-fast) var(--ease-soft);
}
.hy-drop-link:hover {
  background: var(--color-bg-tertiary);
  color: var(--color-text-primary);
}

.hy-drop-promo {
  position: relative;
  flex-shrink: 0;
  display: block;
  width: 260px;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: var(--radius-media);
  background: var(--color-bg-tertiary);
}
.hy-drop-promo img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: transform var(--t-image) var(--ease-out);
}
.hy-drop-promo:hover img {
  transform: scale(1.04);
}
.hy-drop-promo-cap {
  position: absolute;
  inset-inline: 12px;
  inset-block-end: 12px;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.92);
  color: var(--color-text-primary);
  font-size: 0.8125rem;
  font-weight: 600;
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

@media (max-width: 1023px) {
  .hy-desk {
    display: none !important;
  }
}
@media (min-width: 1024px) {
  .hy-mob {
    display: none !important;
  }
}

@media (max-width: 1023px) {
  :root {
    --header-h: 64px;
  }

  .hy-header {
    --hy-mainbar-h: 64px;
    --hy-search-h: 44px;
  }

  /* The topbar keeps only the message. The links live in the drawer, and the
     locale + socials are restated at the bottom of it — three grid columns of
     13px text do not survive 360px. */
  .hy-topbar-row {
    grid-template-columns: 1fr;
    font-size: 0.75rem;
  }
  .hy-topbar-start,
  .hy-topbar-end {
    display: none;
  }

  /* The search drops to its own row under the logo: at this width a third
     grid column would leave it about 90px wide. */
  .hy-mainrow {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas:
      "burger brand  actions"
      "search search search";
    gap: var(--space-3) var(--space-4);
    padding-block: var(--space-3);
  }
  .hy-mainrow > .hy-icon-btn {
    grid-area: burger;
  }
  /* The brand gets a column of its own.

     It used to share the burger's grid area and clear the button with
     `margin-inline-start: 44px` — two items stacked in one area, held apart by
     a margin that had to match the button's width by hand. Under 400px that
     margin dropped to 8px while the button stayed 44px (it is min-width: 44px
     under `pointer: coarse`, i.e. every phone), so the logo sat 36px on top of
     the hamburger.

     A real column cannot drift out of sync with the button's width. The reason
     for the old overlap — keeping a long store name from wrapping the action
     cluster — is `minmax(0, 1fr)` here, plus the `min-width: 0` + `max-width`
     + ellipsis the brand already carries: the name truncates, it never pushes. */
  .hy-brand {
    grid-area: brand;
    justify-self: center;
  }
  .hy-search {
    grid-area: search;
    max-width: none;
  }
  .hy-actions {
    grid-area: actions;
    gap: var(--space-4);
  }

  .hy-search-scope {
    display: none; /* the drawer is where a phone filters */
  }
  .hy-search-input {
    padding-inline: 18px 4px;
  }

  .hy-cart-btn--circle {
    width: 40px;
    height: 40px;
  }

  .hy-navbar {
    display: none; /* the drawer carries the nav */
  }
}

@media (max-width: 400px) {
  /* The brand's clearance is structural now, so nothing about it belongs here.
     Only the action gap tightens — that is a real gain at 360px. */
  .hy-actions {
    gap: var(--space-2);
  }
}

/* --------------------------------------------------------------------------
   15.2  SHARED PRIMITIVES
   The small pieces the new sections build from. They land here as each
   section needs them, so the set grows only with something that uses it.
   -------------------------------------------------------------------------- */

/* ---- Label — the small caps line that titles a group ---- */
.hy-label {
  display: block;
  font-family: var(--font-body-en);
  font-size: 0.6875rem;
  font-weight: 400;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
  margin: 0;
  letter-spacing: var(--track-micro);
}
html[dir="rtl"] .hy-label {
  font-family: var(--font-body-ar);
  font-size: 0.8125rem;
  text-transform: none;
}

/* ---- Link — a rule under the text, not a colour change. The palette has one
        colour; underlining is the only affordance left, so it does the work. -- */
.hy-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding-bottom: 2px;
  font-size: 0.8125rem;
  color: var(--color-text-primary);
  border-bottom: 1px solid var(--color-border-strong);
  transition: border-color var(--t-fast) var(--ease-soft);
}
.hy-link:hover {
  border-color: var(--color-text-primary);
}

/* ---- Prices ---- */
.hy-price,
.hy-price-was,
.hy-num {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-feature-settings:
    "tnum" 1,
    "lnum" 1;
}
.hy-price {
  font-size: 0.875rem;
  font-weight: 400;
  color: var(--color-text-primary);
}
.hy-price-was {
  font-size: 0.8125rem;
  color: var(--color-text-tertiary);
  text-decoration: line-through;
}
/* Sale price takes the one saturated colour the palette allows. */
.hy-price--sale {
  color: var(--color-deal);
}

/* ---- Spinner — a hairline ring, matching the rest of the theme ---- */
.hy-spinner {
  display: inline-block;
  width: 22px;
  height: 22px;
  border: 1px solid var(--color-border);
  border-top-color: var(--color-text-primary);
  border-radius: 50%;
  animation: hy-spin 700ms linear infinite;
}
@keyframes hy-spin {
  to {
    transform: rotate(360deg);
  }
}
@media (prefers-reduced-motion: reduce) {
  .hy-spinner {
    animation-duration: 2s;
  }
}

/* ---- Empty state ---- */
.hy-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  text-align: center;
  padding-block: var(--space-8);
  color: var(--color-text-secondary);
}
.hy-empty-title {
  font-family: var(--font-display-ar);
  font-size: 1.25rem;
  font-weight: 400;
  color: var(--color-text-primary);
}
html[dir="ltr"] .hy-empty-title {
  font-family: var(--font-display-en);
}

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

/* --------------------------------------------------------------------------
   15.3  SEARCH SHEET
   --------------------------------------------------------------------------
   Drops over the whole header rather than sitting under it. Search is a mode,
   not a panel: while it is open nothing else on the page is actionable, and
   covering the chrome is what says so — a sheet that leaves the nav visible
   invites a click that the overlay then eats.

   The field is a single baseline rule at display size. A bordered box with a
   filled submit button is the correct control for a store that sells on specs
   and needs a visible CTA; here the sheet IS the CTA — it already covers the
   page, so the field only has to hold a cursor.
   -------------------------------------------------------------------------- */

.hy-search-sheet {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: var(--z-drawer);
  max-height: 92vh;
  overflow-y: auto;
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
}

.hy-search-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block: var(--space-5);
}

/* ---- The field ---- */
.hy-search-field {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding-block: var(--space-4);
  border-bottom: 1px solid var(--color-text-primary);
}

.hy-search-input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  outline: none;
  font-family: var(--font-display-en);
  font-weight: 400;
  font-size: clamp(1.5rem, 4vw, 2.5rem);
  line-height: 1.2;
  color: var(--color-text-primary);
}
html[dir="rtl"] .hy-search-input {
  font-family: var(--font-display-ar);
  font-size: clamp(1.25rem, 3.4vw, 2rem);
  line-height: 1.5; /* Amiri's ascenders clip against a tight box */
}
.hy-search-input::placeholder {
  color: var(--color-text-tertiary);
}
/* Safari draws its own clear affordance on type=search; we ship our own. */
.hy-search-input::-webkit-search-cancel-button {
  -webkit-appearance: none;
}

.hy-search-clear {
  flex: none;
  padding: 4px 0 3px;
  border: 0;
  border-bottom: 1px solid transparent;
  background: none;
  font-family: var(--font-body-en);
  font-size: 0.6875rem;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
  cursor: pointer;
  transition:
    color var(--t-fast) var(--ease-soft),
    border-color var(--t-fast) var(--ease-soft);
  letter-spacing: var(--track-micro);
}
html[dir="rtl"] .hy-search-clear {
  font-family: var(--font-body-ar);
  font-size: 0.8125rem;
  text-transform: none;
}
.hy-search-clear:hover {
  color: var(--color-text-primary);
  border-bottom-color: var(--color-text-primary);
}

/* ---- Suggested queries — plain underlined words, not chips. A chip is a
        filled rounded object; this theme has neither fills nor corners. ---- */
.hy-sugg {
  display: flex;
  flex-wrap: wrap;
  gap: 10px var(--space-5);
}
.hy-sugg-link {
  padding-bottom: 2px;
  font-size: 0.875rem;
  color: var(--color-text-secondary);
  border-bottom: 1px solid var(--color-border);
  transition:
    color var(--t-fast) var(--ease-soft),
    border-color var(--t-fast) var(--ease-soft);
}
.hy-sugg-link:hover {
  color: var(--color-text-primary);
  border-color: var(--color-text-primary);
}

/* ---- Hits — a real product grid, not a list of rows. The shopper is picking
        a garment; at this size the image is the only thing they read. ---- */
.hy-hits {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-5) var(--space-4);
}

@media (min-width: 640px) {
  .hy-hits {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
@media (min-width: 1024px) {
  .hy-hits {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.hy-hit {
  display: block;
  min-width: 0;
}

.hy-hit-media {
  position: relative;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  background: var(--color-bg-tertiary);
  margin-bottom: 12px;
}
.hy-hit-media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: transform var(--t-image) var(--ease-out);
}
.hy-hit:hover .hy-hit-media img {
  transform: scale(1.03);
}

.hy-hit-empty {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-border-strong);
}

.hy-hit-title {
  margin: 0 0 5px;
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.45;
  color: var(--color-text-primary);
}

.hy-hit-prices {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
}

@media (max-width: 640px) {
  .hy-search-head {
    padding-block: var(--space-4);
  }
  .hy-hit-media {
    margin-bottom: 9px;
  }
}

/* --------------------------------------------------------------------------
   15.4  BUTTONS
   --------------------------------------------------------------------------
   Two families, and the split matters. On a white page a button is INK — the
   palette's only action colour. Over photography ink disappears into shadow,
   so the same button inverts to a white fill. `--light` is not a colour
   variant, it is the over-image family.
   -------------------------------------------------------------------------- */

.hy-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 48px;
  padding-inline: 32px;
  border: 1px solid transparent;
  border-radius: var(--radius-btn);
  /* ⚠️  Arabic-FIRST, sentence case, no tracking.

     The previous theme's button was 12px / weight 400 / UPPERCASE / tracked —
     a quiet editorial label. This design's buttons are the loudest small object
     on the page ("Shop Collection", "Use Code: FLASH30"), so they are 15px and
     semibold, and they keep their case.

     Uppercase and tracking are also both no-ops-at-best in Arabic: Arabic has
     no letter case, and tracking BREAKS letter joining outright. Writing the
     Latin treatment into the base and undoing it in an `html[dir="rtl"]`
     override — which is what this rule used to do — means every Arabic button
     pays for two declarations it then has to cancel. The base is now the
     Arabic one and only the FAMILY changes for Latin. */
  font-family: var(--font-body-ar);
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color var(--t-base) var(--ease-soft),
    border-color var(--t-base) var(--ease-soft),
    color var(--t-base) var(--ease-soft),
    opacity var(--t-base) var(--ease-soft);
}
/* Only the family differs — see the note in the base rule. */
html[lang="en"] .hy-btn,
html[dir="ltr"] .hy-btn {
  font-family: var(--font-body-en);
}
.hy-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* ---- On the page ---- */
.hy-btn--solid {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-text-on-accent);
}
.hy-btn--solid:hover:not(:disabled) {
  background: var(--color-accent-hover);
  border-color: var(--color-accent-hover);
}

.hy-btn--outline {
  background: transparent;
  border-color: var(--color-border-strong);
  color: var(--color-text-primary);
}
.hy-btn--outline:hover:not(:disabled) {
  border-color: var(--color-text-primary);
}

/* ---- Over photography ---- */
.hy-btn--light {
  background: #ffffff;
  border-color: #ffffff;
  color: var(--color-ink);
}
.hy-btn--light:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.86);
  border-color: rgba(255, 255, 255, 0.86);
}

.hy-btn--light-outline {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.7);
  color: #ffffff;
}
.hy-btn--light-outline:hover:not(:disabled) {
  border-color: #ffffff;
  background: rgba(255, 255, 255, 0.1);
}

/* ---- Sizes & shapes ---- */
.hy-btn--sm {
  height: 40px;
  padding-inline: 20px;
  font-size: 0.875rem;
}
.hy-btn--xs {
  height: 32px;
  padding-inline: 15px;
  font-size: 0.8125rem;
}
.hy-btn--lg {
  height: 56px;
  padding-inline: 36px;
  font-size: 1rem;
}
.hy-btn--block {
  width: 100%;
}
.hy-btn--pill {
  border-radius: 999px;
}
.hy-btn--square {
  border-radius: 0;
}

/* --------------------------------------------------------------------------
   15.5  HERO  ·  .hy-hero-*
   --------------------------------------------------------------------------
   One photograph per slide, copy centred over it, controls on its lower edge:

       ┌──────────────────────────────────────────────┐
       │                                              │
       │                  Eyebrow                     │
       │              BIG SERIF-LESS TITLE            │
       │                 ( Shop CTA )                 │
       │                                              │
       │              ‹   ·  ▬  ·   ›                 │
       └──────────────────────────────────────────────┘

   The stage is INSET by default — the page gutter on both sides and a rounded
   corner — rather than edge-to-edge. That inset is what makes the photograph
   read as an object on the page instead of a background behind it, and it lines
   the hero up with every section under it. `stage_width: full` opts out.

   Heights land on `min-height`, never `height`. A long Arabic title wraps
   taller than its English twin, and a ceiling would clip it instead of letting
   the stage grow.

   ⚠️  ARABIC — every `letter-spacing` below is scoped to [dir="ltr"] / lang.
   -------------------------------------------------------------------------- */

.hy-hero {
  position: relative;
}

.hy-hero-stage {
  position: relative;
  overflow: hidden;
  width: 100%;
  border-radius: var(--hy-hero-radius, 14px);
  /* Dark, not the light well: this is what shows for the instant before the
     blurred fill paints, and behind a slide with no image at all. White hero
     copy over a light ground is invisible for that instant. */
  background: color-mix(in srgb, var(--color-ink) 88%, #000);
}

/* The inset stage lines up with .hy-container's CONTENT box, not its border
   box — otherwise it would sit `--container-px` wider than every section under
   it on any viewport past --container-max, and the hero would read as
   misaligned rather than as full-bleed.

     .hy-container  →  max-width 1440, padding-inline 32  →  content 1376
     this stage     →  max-width 1440 - 32*2 = 1376       →  the same edge

   Both halves are needed: `width` handles viewports narrower than the cap
   (where the gutter is what limits it) and `max-width` handles wider ones. */
.hy-hero--inset .hy-hero-stage {
  width: calc(100% - var(--container-px) * 2);
  max-width: calc(var(--container-max) - var(--container-px) * 2);
  margin-inline: auto;
}

/* Mirrors .hy-container's own gutter switch — which is at 640px, not 768. */
@media (max-width: 640px) {
  .hy-hero--inset .hy-hero-stage {
    width: calc(100% - var(--container-px-sm) * 2);
    max-width: calc(var(--container-max) - var(--container-px-sm) * 2);
  }
}

.hy-hero--full .hy-hero-stage {
  width: 100%;
  max-width: none;
  margin-inline: 0;
  border-radius: 0;
}

/* ---- Slide ---- */
.hy-hero-slide {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  /* min-height is printed per widget — see the note in the section header. */
}

/* ---- Media ---- */
.hy-hero-media {
  position: absolute;
  inset: 0;
}
/* The photograph fills the stage. `cover`.

   ── The one thing to understand before changing this ──
   No object-fit can make an image whose ratio differs from the stage's look
   right. There are exactly three behaviours and each fails a different way:

     cover    fills the stage, CROPS      a 4:5 portrait in a 1.9:1 stage
                                          shows its middle 42%
     contain  shows it all, LETTERBOXES   bands beside the picture; filling
                                          them with a blurred copy of the same
                                          photograph put the headline on a
                                          smear instead of on the picture
     fill     fills and shows it all,     furniture squashed wider than it is,
              but STRETCHES               which misrepresents the product

   All three were shipped here in turn. The fix was never the fit — it is the
   ARTWORK: at the stage's own ratio all three render identically, and the
   setting stops mattering. Roughly 2400 × 1260 at the default `large` height
   (1376 × 720 → 1.9:1); `medium` wants 2400 × 1000 and `compact` 2400 × 800.
   Portrait art belongs in the slide's "صورة الموبايل" field, where the stage
   is portrait too.

   `cover` is the default because it is the kindest failure of the three: a
   wrongly-shaped image is cropped, and a crop still shows real furniture at
   real proportions. And the crop is AIMED — the slide's "تركيز الصورة" setting
   writes `object-position` inline (centre / top for faces / bottom), so a
   merchant decides what survives it. That aiming is exactly what a product
   card has no one around to do, which is why cover is right here and wrong
   there. */
.hy-hero-media img {
  position: relative;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* object-position is set inline from the slide's focus setting. */
}

/* A slide with no image yet — the state a merchant hits before uploading. */
.hy-hero-slide--empty {
  background: var(--color-bg-tertiary);
}

/* ---- Scrim ----
   A FLAT wash across the whole stage, not a radial one.

   The radial version darkened the middle and let the corners stay bright. Over
   a full-bleed photograph that reads as a vignette — a lens effect printed on
   top of the picture — and it leaves the eyebrow and the CTA, which sit above
   and below the centre, on the pale part of the gradient. Flat darkens the
   photograph evenly: the copy is legible wherever it lands, and the image still
   reads as itself.

   Kept deliberately gentle — enough to carry white text, not enough to grey out
   the furniture. `strong` is for a genuinely bright shot and is the only one
   heavy enough to change the mood of the photograph. */
.hy-hero-scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.hy-hero-scrim--light {
  background: color-mix(in srgb, var(--color-ink) 22%, transparent);
}
.hy-hero-scrim--medium {
  background: color-mix(in srgb, var(--color-ink) 38%, transparent);
}
.hy-hero-scrim--strong {
  background: color-mix(in srgb, var(--color-ink) 52%, transparent);
}

/* ---- Content ----
   The outer element carries the gutter and the full width; the inner one caps
   the measure. Splitting them is what lets a long description stay readable
   while the block itself stays centred on the stage. */
.hy-hero-content {
  position: relative;
  z-index: 1;
  display: flex;
  justify-content: center;
  width: 100%;
  padding-inline: var(--space-6);
  /* Clears the controls on the lower edge so a tall copy block never lands
     under the arrows. */
  padding-block: var(--space-9) calc(var(--space-9) + 28px);
}
.hy-hero-content--start {
  justify-content: flex-start;
  padding-inline: clamp(var(--space-6), 7vw, 96px);
}

.hy-hero-copy {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  max-width: 780px;
  text-align: center;
}
.hy-hero-content--start .hy-hero-copy {
  align-items: flex-start;
  text-align: start;
}

.hy-hero-eyebrow {
  margin: 0;
  font-family: var(--font-body-ar);
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.4;
}
html[lang="en"] .hy-hero-eyebrow,
html[dir="ltr"] .hy-hero-eyebrow {
  font-family: var(--font-body-en);
}

.hy-hero-title {
  margin: 0;
  font-family: var(--font-body-ar);
  font-size: clamp(2.5rem, 7vw, 5.5rem);
  font-weight: 800;
  /* 1.05 is as tight as Arabic goes before the descenders of one line touch the
     dots of the next. English can take 0.98 and does, below. */
  line-height: 1.15;
}
html[lang="en"] .hy-hero-title,
html[dir="ltr"] .hy-hero-title {
  font-family: var(--font-body-en);
  line-height: 1;
  letter-spacing: -0.03em;
}

/* The content is the merchant's `text` field, which arrives as block HTML —
   its own <p> would add margins the container cannot reach. */
.hy-hero-desc > :first-child {
  margin-block-start: 0;
}
.hy-hero-desc > :last-child {
  margin-block-end: 0;
}

.hy-hero-desc {
  margin: 0;
  max-width: 56ch;
  font-size: 1.0625rem;
  line-height: 1.65;
}

.hy-hero-ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block-start: var(--space-3);
}

/* ---- Tone ----
   Copy that sits ON a photograph is white whatever the palette says — that is
   the point of the `is-light` tone. But a hardcoded #FFFFFF also makes the
   section's own Title/Body pickers dead on exactly the sections most likely to
   be recoloured. So the white is a FALLBACK: ui/section-colors.njk sets
   `--hy-title-on-media` / `--hy-text-on-media` when the merchant picks one, and
   nothing changes when they don't. */
.hy-hero-slide.is-light .hy-hero-eyebrow,
.hy-hero-slide.is-light .hy-hero-desc {
  color: var(--hy-text-on-media, #ffffff);
}
.hy-hero-slide.is-light .hy-hero-title {
  color: var(--hy-title-on-media, #ffffff);
}

.hy-hero-slide.is-dark .hy-hero-eyebrow,
.hy-hero-slide.is-dark .hy-hero-desc {
  color: var(--color-text-secondary);
}
.hy-hero-slide.is-dark .hy-hero-title {
  color: var(--color-text-primary);
}

/* ==========================================================================
   CONTROLS  ·  ‹  ·  ▬  ·  ›   on the stage's lower edge
   ========================================================================== */

.hy-hero-controls {
  position: absolute;
  inset-block-end: var(--space-6);
  inset-inline: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  pointer-events: none; /* the row is a spacer; only its children take clicks */
}
.hy-hero-controls > * {
  pointer-events: auto;
}

.hy-hero-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 0;
  background: none;
  color: #fff;
  cursor: pointer;
  opacity: 0.85;
  transition:
    opacity var(--t-fast) var(--ease-soft),
    transform var(--t-fast) var(--ease-out);
}
.hy-hero-arrow:hover {
  opacity: 1;
}
.hy-hero-arrow:active {
  transform: scale(0.92);
}

/* Swiper writes this class on the arrows when loop is off and an end is
   reached. Without the rule the arrow looks live and does nothing. */
.hy-hero-arrow.swiper-button-disabled {
  opacity: 0.3;
  cursor: default;
}

/* ---- Pagination ----
   A dot per slide, the active one stretched into a bar. Swiper owns the
   `-active` class; everything about the shape is here.

   `!important` on the bullet's own properties is not decorative: Swiper's
   bundled CSS ships `.swiper-pagination-bullet { width: var(--swiper-pagination-bullet-width, 8px) }`
   from a stylesheet loaded AFTER this one in layouts/layout.njk, at the same
   specificity. Source order would hand it the win. */
.hy-hero-dots {
  display: flex !important;
  align-items: center;
  justify-content: center;
  gap: 7px;
  position: static !important;
  width: auto !important;
}

.hy-hero-dots .swiper-pagination-bullet {
  width: 8px !important;
  height: 8px !important;
  margin: 0 !important;
  border-radius: 999px !important;
  background: #fff !important;
  opacity: 0.45 !important;
  transition:
    width var(--t-base) var(--ease-out),
    opacity var(--t-base) var(--ease-soft);
}
.hy-hero-dots .swiper-pagination-bullet-active {
  width: 44px !important;
  opacity: 1 !important;
}

/* On a light photograph the white controls disappear. The widget's inline
   script reads the active slide's tone and flags the stage, exactly like the
   copy above it. */
.hy-hero-stage.on-light .hy-hero-arrow {
  color: var(--color-text-primary);
}
.hy-hero-stage.on-light .hy-hero-dots .swiper-pagination-bullet {
  background: var(--color-text-primary) !important;
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

@media (max-width: 767px) {
  .hy-hero-content {
    padding-inline: var(--space-5);
    padding-block: var(--space-8) calc(var(--space-8) + 28px);
  }
  .hy-hero-content--start {
    padding-inline: var(--space-5);
  }

  .hy-hero-copy {
    gap: var(--space-3);
  }

  .hy-hero-desc {
    font-size: 0.9375rem;
  }

  /* Two CTAs stack rather than shrink — a pill button that has lost its
     padding stops reading as a button at all. */
  .hy-hero-ctas {
    width: 100%;
    flex-direction: column;
    align-items: stretch;
  }
  .hy-hero-content:not(.hy-hero-content--start) .hy-hero-ctas .hy-btn {
    justify-content: center;
  }

  .hy-hero-controls {
    inset-block-end: var(--space-4);
    gap: var(--space-3);
  }
  .hy-hero-dots .swiper-pagination-bullet-active {
    width: 32px !important;
  }
}

/* --------------------------------------------------------------------------
   15.6  SCROLLING TEXT (marquee)
   --------------------------------------------------------------------------
   A full-bleed strip of text that loops forever.

   The seam is the whole problem. The usual recipe — duplicate the content
   once, animate translateX(-50%) — only looks continuous when the content is
   already wider than the viewport. Three short phrases on a 27" monitor leave
   a visible gap that marches across the screen every cycle. So the group is
   cloned by SCRIPT until the track is at least twice the viewport wide, and
   the shift is set to one group's measured width in pixels rather than a
   percentage. One group's width is exactly one period of a repeating pattern,
   so the wrap is invisible at any content length and any screen size.

   The track is forced to `direction: ltr` while each phrase keeps the page's
   direction. Letting the flex track inherit RTL would reverse the order the
   groups sit in and make "shift by one group" land somewhere else; the phrases
   themselves still need RTL to read correctly, so the two are separated.
   -------------------------------------------------------------------------- */

.hy-mq {
  position: relative;
  width: 100%;
  overflow: hidden;
  background: var(--mq-bg, var(--color-ink));
  color: var(--mq-fg, #ffffff);
  user-select: none;
}

.hy-mq-track {
  display: flex;
  flex: none;
  width: max-content;
  direction: ltr;
  will-change: transform;
  animation: hy-mq-scroll var(--mq-dur, 30s) linear infinite;
}
/* Reverse, not a second keyframe set: the track is a repeating pattern, so
   playing the same translation backwards is visually identical except for the
   direction of travel — which is what RTL needs (new text arrives from the
   start edge, i.e. the left). */
html[dir="rtl"] .hy-mq-track {
  animation-direction: reverse;
}

/* `--reverse` flips whatever the current direction is, so two strips stacked
   with one modifier between them always travel against each other — in both
   writing directions.

   The RTL rule has to be written out again rather than left to inherit: as
   `html[dir="rtl"] .hy-mq-track` it scores 0-2-1, which already outranks a
   plain `.hy-mq--reverse .hy-mq-track` at 0-2-0. Without the longer selector
   below, the modifier would simply do nothing in Arabic. */
.hy-mq--reverse .hy-mq-track {
  animation-direction: reverse;
}
html[dir="rtl"] .hy-mq--reverse .hy-mq-track {
  animation-direction: normal;
}

.hy-mq.is-pausable:hover .hy-mq-track {
  animation-play-state: paused;
}

@keyframes hy-mq-scroll {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(calc(-1 * var(--mq-shift, 50%)));
  }
}

/* Motion this persistent is a genuine accessibility problem, not a preference.
   Stopped, the first group still reads as a static strip. */
@media (prefers-reduced-motion: reduce) {
  .hy-mq-track {
    animation: none;
  }
}

.hy-mq-group {
  display: flex;
  align-items: center;
  flex: none;
}

.hy-mq-item {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  padding-block: var(--mq-py, 14px);
  font-size: var(--mq-fs, 1rem);
  line-height: 1.4;
  color: inherit;
}
html[dir="rtl"] .hy-mq-item {
  direction: rtl;
}

a.hy-mq-item {
  transition: opacity var(--t-fast) var(--ease-soft);
}
a.hy-mq-item:hover {
  opacity: 0.7;
}

.hy-mq--display .hy-mq-item {
  font-family: var(--font-display-en);
  font-weight: 400;
}
html[dir="rtl"] .hy-mq--display .hy-mq-item {
  font-family: var(--font-display-ar);
}

.hy-mq--body .hy-mq-item {
  font-family: var(--font-body-en);
  font-weight: 400;
}
html[dir="rtl"] .hy-mq--body .hy-mq-item {
  font-family: var(--font-body-ar);
}

/* Tracking only in LTR — see the Arabic tracking guard in 15.0. */
.hy-mq--upper .hy-mq-item {
  text-transform: uppercase;
  letter-spacing: var(--track-nav);
}
html[dir="rtl"] .hy-mq--upper .hy-mq-item {
  text-transform: none;
}

.hy-mq-sep {
  display: inline-flex;
  align-items: center;
  flex: none;
  padding-inline: var(--mq-gap, 40px);
  color: inherit;
  opacity: 0.85;
}

/* ---- A product in the separator's slot ----
   The block's image replaces the glyph after that phrase, so the strip reads
   as one sentence with pictures in it rather than a list of captions.

   `contain`, not `cover`: these are cut-outs on a flat ground and `cover` would
   crop the legs off a chair. The height is the dial — width follows the ratio,
   so a wide sofa and a tall lamp both sit on the same baseline. */
.hy-mq-img {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  height: var(--mq-img-h, 56px);
  /* ⚠️  The strip's spacing lives on the SEPARATOR, not on the items — the
     group and the phrases have no gap of their own. Without this line the
     picture sits flush against the words on both sides. Same property and same
     variable as `.hy-mq-sep`, so a glyph strip and an image strip breathe
     identically. */
  padding-inline: var(--mq-gap, 40px);
}
.hy-mq-img img {
  height: 100%;
  width: auto;
  max-width: none;
  object-fit: contain;
}
.hy-mq-sep svg {
  display: block;
}

/* --------------------------------------------------------------------------
   15.7  PRODUCT CARD
   --------------------------------------------------------------------------
   ⚠️  THIS CARD HAS A TWIN: HayatCard.build() in assets/main.js emits the same
   markup for AJAX results (search, filters, lazy grids). Change the structure
   here and change it there. Styling is only in this file, so the two cannot
   drift visually — only the markup order has to stay in sync.

   Structure, and the reason for it:

     article.hy-pcard
       div.hy-pcard-frame        ← the positioning context for EVERYTHING that
         a.hy-pcard-media          floats on the photograph
         span.hy-pcard-badge
         button.hy-pcard-eye
         div.hy-pcard-atc
         div.hy-pcard-soldout
       div.hy-pcard-info         ← outside the frame

   The frame exists because the overlays must anchor to the IMAGE, not to the
   card. In the overlay variant the two are the same height so it makes no
   difference — but the moment `info` becomes static and sits below the image,
   the card grows taller than the frame and an overlay anchored to the card
   lands underneath the price instead of on the photograph.

   `info` sits outside the frame for the mirror-image reason: it has to be able
   to leave the image and become a normal block under it. Three variants, one
   DOM, one class flipped:

     --tray     info over the image, hidden on hover; ATC revealed on hover
     --always   info below the image; ATC always on the image, full width
     --compact  info below the image; ATC always on the image, 40px bag icon
   -------------------------------------------------------------------------- */

.hy-pcard {
  position: relative;
  display: block;
}

.hy-pcard-frame {
  position: relative;
  overflow: hidden;
  background: var(--color-bg-tertiary);
}

/* ---- Media ---- */
.hy-pcard-media {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: var(--pcard-ratio, 2 / 3);
  overflow: hidden;
}

.hy-pcard-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition:
    opacity var(--t-base) var(--ease-soft),
    transform var(--t-image) var(--ease-out);
}
.hy-pcard-img--alt {
  opacity: 0;
}
.hy-pcard:hover .hy-pcard-img--primary {
  opacity: 0;
}
.hy-pcard:hover .hy-pcard-img--alt {
  opacity: 1;
}

/* The zoom is on BOTH images, not just the one without a twin: when a second
   image exists the two cross-fade while they scale together, so the swap reads
   as one movement instead of a cut. :focus-within gives keyboard users the
   same feedback the mouse gets. */
.hy-pcard:hover .hy-pcard-img,
.hy-pcard:focus-within .hy-pcard-img {
  transform: scale(1.05);
}

@media (prefers-reduced-motion: reduce) {
  .hy-pcard:hover .hy-pcard-img,
  .hy-pcard:focus-within .hy-pcard-img {
    transform: none;
  }
}

.hy-pcard-noimg {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-border-strong);
}

/* ---- Badge ----
   Vertical is the reference's look, and it only works for Latin: Arabic is a
   connected script with no vertical typesetting, so `writing-mode` renders it
   as a column of broken glyphs. RTL therefore forces horizontal regardless of
   the setting — a correctness rule, not a preference. */
.hy-pcard-badge {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  z-index: 2;
  padding: 12px 8px;
  background: var(--color-surface);
  color: var(--color-text-primary);
  font-family: var(--font-body-en);
  font-size: 0.6875rem;
  text-transform: uppercase;
  line-height: 1;
  letter-spacing: var(--track-micro);
}
.hy-pcard-badge--vertical {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  padding: 8px 12px;
}
.hy-pcard-badge--sale {
  color: var(--color-deal);
}

html[dir="rtl"] .hy-pcard-badge {
  font-family: var(--font-body-ar);
  font-size: 0.75rem;
  text-transform: none;
}
html[dir="rtl"] .hy-pcard-badge--vertical {
  writing-mode: horizontal-tb;
  transform: none;
  padding: 12px 8px;
}

/* Logical inset properties resolve against the ELEMENT'S OWN writing mode, and
   this badge sets `writing-mode: vertical-rl` on itself. That rotates its axes:
   `inset-inline-start` becomes the TOP and `inset-block-start` becomes the
   RIGHT — so the badge pins to the top-right corner instead of the top-left.

   Invisible in Arabic, because the rule above resets the badge to
   horizontal-tb there and the logical properties behave normally again. It only
   ever went wrong in English, which is exactly why it survived until the theme
   was rendered in both. Physical properties, scoped to the one case that needs
   them. */
html[dir="ltr"] .hy-pcard-badge--vertical {
  inset-block-start: auto;
  inset-inline-start: auto;
  top: 0;
  left: 0;
}

/* ---- Quick view ----
   Opposite corner from the badge. The reference stacks them in the same slot,
   which collides the moment a discounted product is hovered — and every
   product in a sale row is discounted. */
.hy-pcard-eye {
  position: absolute;
  inset-block-start: 12px;
  inset-inline-end: 12px;
  z-index: 3;
  width: 42px;
  height: 42px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 999px;
  background: var(--color-surface);
  color: var(--color-text-primary);
  cursor: pointer;
  opacity: 0;
  transform: translateY(-6px);
  transition:
    opacity var(--t-base) var(--ease-soft),
    transform var(--t-base) var(--ease-out);
}
.hy-pcard:hover .hy-pcard-eye,
.hy-pcard-eye:focus-visible {
  opacity: 1;
  transform: translateY(0);
}
/* Coarse pointers never hover, so the control would be unreachable. */
@media (hover: none) {
  .hy-pcard-eye {
    opacity: 1;
    transform: none;
  }
}

/* ---- Add to cart ----
   Anchored to the frame, so it is always on the photograph whatever the
   variant does with the information block. */
.hy-pcard-atc {
  position: absolute;
  inset-block-end: 22px;
  inset-inline: 24px;
  z-index: 3;
  transition:
    opacity var(--t-base) var(--ease-soft),
    transform var(--t-base) var(--ease-out);
}
.hy-pcard-atc .hy-btn {
  width: 100%;
}

.hy-pcard-atc-face {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.hy-pcard-atc-face svg {
  flex: none;
}

/* ---- Info ---- */
.hy-pcard-info {
  display: flex;
  flex-direction: column;
  transition: opacity var(--t-base) var(--ease-soft);
}

.hy-pcard-prices {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 9px;
}
.hy-pcard-prices .hy-price {
  font-size: 1.0625rem;
  font-weight: 500;
}
.hy-pcard-prices .hy-price-was {
  font-size: 0.9375rem;
}

.hy-pcard-name {
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.5;
  color: var(--color-text-primary);
}

/* ---- Sold out ---- */
.hy-pcard-soldout {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.62);
  font-family: var(--font-body-en);
  font-size: 0.6875rem;
  text-transform: uppercase;
  color: var(--color-text-primary);
  pointer-events: none;
  letter-spacing: var(--track-micro);
}
html[dir="rtl"] .hy-pcard-soldout {
  font-family: var(--font-body-ar);
  font-size: 0.8125rem;
  text-transform: none;
}

/* ==== Variant: --tray — everything on the image ==== */
.hy-pcard--tray .hy-pcard-info {
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  z-index: 2;
  padding: 22px 24px;
  /* A light wash, not a dark one: the text is ink. It is invisible against a
       light dress on a light background and it rescues the dark product shots
       that would otherwise swallow the price entirely. */
  background: linear-gradient(
    to top,
    rgba(255, 255, 255, 0.82) 0%,
    rgba(255, 255, 255, 0) 100%
  );
}
.hy-pcard--tray .hy-pcard-prices {
  margin-bottom: 6px;
}
.hy-pcard--tray .hy-pcard-atc-face svg {
  display: none;
}

.hy-pcard--tray .hy-pcard-atc {
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
}
.hy-pcard--tray:hover .hy-pcard-atc,
.hy-pcard--tray:focus-within .hy-pcard-atc {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
.hy-pcard--tray:hover .hy-pcard-info,
.hy-pcard--tray:focus-within .hy-pcard-info {
  opacity: 0;
}

/* ==== Variant: --always — button stays on the image, info drops below ==== */
.hy-pcard--always .hy-pcard-info {
  padding: 14px 0 0;
}
.hy-pcard--always .hy-pcard-prices {
  margin-bottom: 6px;
}
.hy-pcard--always .hy-pcard-atc-face svg {
  display: none;
}

@media (max-width: 640px) {
  .hy-pcard--tray .hy-pcard-info {
    padding: 14px 16px;
  }
  .hy-pcard-atc {
    inset-block-end: 14px;
    inset-inline: 16px;
  }
  .hy-pcard-name {
    font-size: 0.9375rem;
  }
  .hy-pcard-prices .hy-price {
    font-size: 1rem;
  }
  .hy-pcard-prices .hy-price-was {
    font-size: 0.875rem;
  }
  .hy-pcard-eye {
    width: 36px;
    height: 36px;
    inset-block-start: 9px;
    inset-inline-end: 9px;
  }
}

/* ⚠️  Touch has no hover, so the desktop swap cannot happen — and the old rule
   here resolved that by showing the button and setting the INFO to opacity 0.
   The result on every phone was a wall of anonymous photographs: no product
   name, no price, just an "add to cart" button. `--tray` is the theme's default
   card style, so that was the whole shop.

   On touch the card falls back to the `--always` arrangement instead: the
   button stays on the photograph, and the name and price move below it where
   they are always readable. Placed AFTER the 640px block so its padding wins. */
@media (hover: none) {
  .hy-pcard--tray .hy-pcard-info {
    position: static;
    opacity: 1;
    padding: 14px 0 0;
    background: none;
  }
  .hy-pcard--tray .hy-pcard-atc {
    opacity: 1;
    transform: none;
    pointer-events: auto;
  }
  .hy-pcard--tray .hy-pcard-atc-face svg {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   15.8  PRODUCT ROW (merchandising carousel)  ·  .hy-prow-* / .hy-promo-*
   --------------------------------------------------------------------------
     New Arrivals                                    New Arrivals   Hot Items
     One line of supporting copy.                    ‾‾‾‾‾‾‾‾‾‾‾‾
     [ promo ] [ card ] [ card ] [ card ] [ card ⋯
     ▬▬▬▬▬▬▬────────────────────────────      ( ‹ ) ( › )

   ⚠️  RTL — nothing here uses a physical side. The head is a flex row with
   `space-between`, so the title takes the start edge and the tabs the end edge
   in BOTH directions with no override. The controls row works the same way, and
   the rail's fill is Swiper's own (it honours `rtl: true`, which the widget
   passes explicitly). The one physical property in the file is the promo CTA's
   `inset-inline-*`, which is logical too.
   -------------------------------------------------------------------------- */

.hy-prow-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-5);
  margin-block-end: var(--space-6);
}

.hy-prow-headings {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}

.hy-prow-title {
  margin: 0;
  font-family: var(--font-body-ar);
  font-size: clamp(1.75rem, 3.4vw, 2.75rem);
  font-weight: 800;
  line-height: 1.2;
  color: var(--color-text-primary);
}
html[lang="en"] .hy-prow-title,
html[dir="ltr"] .hy-prow-title {
  font-family: var(--font-body-en);
  letter-spacing: -0.02em;
}

.hy-prow-sub {
  margin: 0;
  max-width: 62ch;
  font-size: 1rem;
  line-height: 1.6;
  color: var(--color-text-secondary);
}

/* ---- Tabs ----
   An underline that belongs to the active tab, not a pill or a segmented
   control: the strip sits beside a heading, and a filled control there reads as
   a second call to action competing with the row itself. */
.hy-prow-tabs {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  flex-shrink: 0;
}

.hy-prow-tab {
  position: relative;
  padding: 0 0 6px;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: 1rem;
  font-weight: 600;
  white-space: nowrap;
  color: var(--color-text-tertiary);
  cursor: pointer;
  transition: color var(--t-fast) var(--ease-soft);
}
.hy-prow-tab:hover {
  color: var(--color-text-secondary);
}

/* The rule is a pseudo-element rather than a border-bottom so it can grow from
   the centre without moving the label.

   ⚠️  It animates `transform: scaleX`, NOT `width` off a 50% inset. `translateX`
   is a PHYSICAL transform — it does not flip with `dir` — so the inset/translate
   pairing that centres the rule in LTR pushes it off the tab in RTL. scaleX
   about a centre origin has no direction to get wrong, and it animates on the
   compositor instead of relaying out the tab on every frame. */
.hy-prow-tab::after {
  content: "";
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  height: 2px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: center;
  transition: transform var(--t-base) var(--ease-out);
}

.hy-prow-tab.is-active {
  color: var(--color-text-primary);
}
.hy-prow-tab.is-active::after {
  transform: scaleX(1);
}

/* ---- Stage ----
   `overflow: visible` on the swiper, not hidden: the row is meant to bleed a
   sliver of the next card past the container edge, and hiding overflow here
   would clip that sliver into a hard edge — the exact cue that tells a shopper
   the row scrolls. Swiper's own wrapper still clips at the element bounds. */
.hy-prow-stage {
  position: relative;
}
.hy-prow-stage .swiper {
  overflow: visible;
  /* Without a clip on the inline axis the off-screen slides widen the document
     and the whole page gets a horizontal scrollbar. `clip` on one axis leaves
     the other free, which `hidden` would not. */
  overflow-x: clip;
}

.hy-prow-panel {
  min-width: 0;
}

/* ---- Controls ----
   Rail and arrows on one row, so they can never drift apart vertically and the
   rail takes whatever width the arrows leave it at every breakpoint. */
.hy-prow-controls {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  margin-block-start: var(--space-7);
}

.hy-prow-rail {
  position: relative;
  flex: 1 1 auto;
  height: 2px;
  background: var(--color-border);
  border-radius: 999px;
  cursor: grab;
}
.hy-prow-rail:active {
  cursor: grabbing;
}

/* Swiper's draggable handle. It positions this itself and it honours `rtl`, so
   the fill starts on the correct edge in both directions with nothing here. */
.hy-prow-rail .swiper-scrollbar-drag {
  height: 2px;
  background: var(--color-text-primary);
  border-radius: 999px;
}

.hy-prow-nav {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
}

.hy-prow-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border: 1px solid var(--color-border-strong);
  border-radius: 999px;
  background: transparent;
  color: var(--color-text-primary);
  cursor: pointer;
  transition:
    background var(--t-fast) var(--ease-soft),
    border-color var(--t-fast) var(--ease-soft),
    color var(--t-fast) var(--ease-soft);
}
.hy-prow-arrow:hover:not(.swiper-button-disabled) {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-text-on-accent);
}

/* Swiper writes this class when an end is reached. Without the rule the arrow
   looks live and does nothing. */
.hy-prow-arrow.swiper-button-disabled {
  opacity: 0.35;
  cursor: default;
}

/* ==========================================================================
   PROMO TILE  ·  the first slide
   ========================================================================== */

/* It matches a product card's frame height rather than the whole card: the
   cards below it carry an info block, so matching the CARD would leave the
   promo floating with a gap under it on one breakpoint and overhanging on the
   next. `aspect-ratio` ties it to the same ratio the frames use. */
.hy-promo {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  overflow: hidden;
  height: 100%;
  min-height: 100%;
  padding: var(--space-5);
  border-radius: var(--hy-cat-radius, 10px);
  background: var(--color-bg-tertiary);
  color: #fff;
  isolation: isolate;
}

.hy-promo-img {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: transform var(--t-image) var(--ease-out);
}
.hy-promo:hover .hy-promo-img {
  transform: scale(1.04);
}

/* Top-weighted: the copy sits at the top of the tile, so the darkening does
   too. A flat wash over the whole image would flatten the photograph. */
.hy-promo-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--color-ink) 50%, transparent) 0%,
    color-mix(in srgb, var(--color-ink) 18%, transparent) 55%,
    color-mix(in srgb, var(--color-ink) 5%, transparent) 100%
  );
}

.hy-promo-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.hy-promo-eyebrow {
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.4;
}

.hy-promo-title {
  font-family: var(--font-body-ar);
  font-size: clamp(1.5rem, 2.4vw, 2.125rem);
  font-weight: 800;
  line-height: 1.2;
}
html[lang="en"] .hy-promo-title,
html[dir="ltr"] .hy-promo-title {
  font-family: var(--font-body-en);
  letter-spacing: -0.02em;
}

/* Pinned to the lower edge rather than sitting after the copy, so tiles with a
   one-word title and tiles with a three-line one put their button in the same
   place across the row. */
.hy-promo-cta {
  position: absolute;
  inset-block-end: var(--space-5);
  inset-inline-start: var(--space-5);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 48px;
  padding-inline: var(--space-6);
  border-radius: 999px;
  background: #fff;
  color: var(--color-text-primary);
  font-size: 0.9375rem;
  font-weight: 600;
  white-space: nowrap;
  transition:
    background var(--t-fast) var(--ease-soft),
    color var(--t-fast) var(--ease-soft);
}
.hy-promo:hover .hy-promo-cta {
  background: var(--color-accent);
  color: var(--color-text-on-accent);
}

/* Swiper sets each slide's height from the tallest one when
   `autoHeight` is off, which is what lets the promo stretch. Without this the
   flex item collapses to its content. */
.hy-prow-stage .swiper-slide {
  height: auto;
  /* ⚠️  The width is computed here as well as by Swiper, and it has to be.

     Swiper's stylesheet gives an uninitialised `.swiper-slide` `width: 100%`,
     so for the frames before its script runs every card was the full row — a
     visible flash on every page load — and a tab pane that never gets built
     (the `offsetParent` guard) stayed that way for good.

     This is Swiper's own formula for a fractional `slidesPerView`, so the two
     agree and its inline width lands on top of an already-correct layout. Keep
     `--pr-cols` in step with `slidesPerView` in the widget. */
  width: calc(
    (100% - var(--pr-gap, 16px) * (var(--pr-cols, 2.25) - 1)) /
      var(--pr-cols, 2.25)
  );
}

@media (min-width: 768px) {
  .hy-prow-stage {
    --pr-cols: var(--pr-cols-md, 3.4);
    --pr-gap: 20px;
  }
}
@media (min-width: 1024px) {
  .hy-prow-stage {
    --pr-cols: var(--pr-cols-lg, 4.4);
    --pr-gap: 24px;
  }
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

@media (max-width: 767px) {
  .hy-prow-head {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-4);
    margin-block-end: var(--space-5);
  }

  /* The tabs scroll rather than wrap — a wrapped second row of tabs pushes the
     products below the fold on a phone. */
  .hy-prow-tabs {
    gap: var(--space-5);
    overflow-x: auto;
    scrollbar-width: none;
  }
  .hy-prow-tabs::-webkit-scrollbar {
    display: none;
  }

  .hy-prow-sub {
    font-size: 0.9375rem;
  }

  .hy-prow-controls {
    margin-block-start: var(--space-5);
    gap: var(--space-4);
  }

  /* The arrows go: a thumb swipes the row and the rail already reports where it
     is, so two 46px targets are spending width to duplicate a gesture. */
  .hy-prow-nav {
    display: none;
  }

  .hy-promo {
    padding: var(--space-4);
  }
  .hy-promo-cta {
    inset-block-end: var(--space-4);
    inset-inline-start: var(--space-4);
    height: 42px;
    padding-inline: var(--space-5);
    font-size: 0.875rem;
  }
}

/* --------------------------------------------------------------------------
   15.9  PRODUCT CARD · COMPACT VARIANT
   --------------------------------------------------------------------------
   The rails beside an editorial image. Information below the photograph, name
   above price, no badge, no quick view, and the add-to-cart reduced to a bag
   square on the image's lower corner.

   Same DOM as every other variant — see the structure note in 15.7. The
   price/name swap is a flex `order`, not a second markup order, so the SSR
   partial and HayatCard.build() stay one structure with one class flipped.
   -------------------------------------------------------------------------- */

.hy-pcard--compact .hy-pcard-info {
  padding: 12px 0 0;
}

.hy-pcard--compact .hy-pcard-name {
  font-size: 0.875rem;
}
.hy-pcard--compact .hy-pcard-prices {
  margin-top: 6px;
}
.hy-pcard--compact .hy-pcard-prices .hy-price {
  font-size: 0.9375rem;
}
.hy-pcard--compact .hy-pcard-prices .hy-price-was {
  font-size: 0.8125rem;
}

/* Reading order is a per-USE decision, not a per-variant one. On a rail beside
   an editorial image the picture is too small to identify the garment, so the
   name leads; on a card floating over a banner the offer is the point, so the
   price does. A flex `order` keeps both out of the markup, so the two twins
   stay one structure. */
.hy-pcard--name-first .hy-pcard-name {
  order: 1;
}
.hy-pcard--name-first .hy-pcard-prices {
  order: 2;
}

/* The badge and the quick-view eye are NOT hidden here. Whether they render is
   the template's call (show_badge / show_quick_view), and a CSS rule that
   overrules it would silently ignore the setting. */

/* ---- The bag square ---- */
.hy-pcard--compact .hy-pcard-atc {
  inset-block-end: 0;
  inset-inline-start: 0;
  inset-inline-end: auto;
}
.hy-pcard--compact .hy-pcard-atc .hy-btn {
  width: 40px;
  height: 40px;
  padding: 0;
  border-radius: 0;
}
/* The icon carries the meaning here; the label stays in the DOM for the
   accessible name and for the other variants, which hide the icon instead. */
.hy-pcard--compact .hy-pcard-atc-label {
  display: none;
}

@media (max-width: 640px) {
  .hy-pcard--compact .hy-pcard-atc .hy-btn {
    width: 34px;
    height: 34px;
  }
  .hy-pcard--compact .hy-pcard-name {
    font-size: 0.8125rem;
  }
}

/* --------------------------------------------------------------------------
   15.9.1  PRODUCT CARD · CATALOG VARIANT   ·   .hy-pcard--catalog
   --------------------------------------------------------------------------
   The card the merchandising rows use:

       ┌─────────────┐
       │ [Sale]      │   image on a soft well, rounded, square-ish
       │     🪑      │
       └─────────────┘
        CHAIRS           ← collection eyebrow (uppercase, LTR only)
        Cross Chair       ← name
        $589  $600        ← sale price, then the struck original
        ■ ■               ← option swatches

   Same DOM as every other variant — see the structure note in 15.7. What
   differs is the info block's ORDER (a flex `order`, so the two twins keep one
   markup) plus two rows the other variants do not render at all.

   ⚠️  RTL — the eyebrow's tracking and the uppercase are scoped to [dir="ltr"].
   Tracking breaks Arabic letter joining, and Arabic has no letter case, so
   `text-transform: uppercase` is a no-op there that only costs a repaint.
   -------------------------------------------------------------------------- */

/* The media well. The reference photography is already shot on a soft grey, so
   this is a fallback ground rather than a frame — it shows while the image
   loads and behind any product shot with transparency. */
.hy-pcard--catalog .hy-pcard-frame {
  overflow: hidden;
  border-radius: var(--hy-cat-radius, 10px);
  background: var(--color-bg-tertiary);
}

/* A catalog card still carries its add-to-cart mode class, so it arrives as
   `.hy-pcard--tray .hy-pcard--catalog` in the common case. `--tray` pins the
   info block over the image behind a white gradient; every declaration it sets
   has to be undone here, not just overridden in part. Same specificity (0,2,0),
   and this section is later in the file, so it wins. */
.hy-pcard--catalog .hy-pcard-info {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  position: static;
  inset: auto;
  z-index: auto;
  padding: 14px 2px 0;
  text-align: start;
  background: none;
}

/* The order lives here rather than in the markup so ui/product-card.njk and
   HayatCard.build() keep ONE structure with one class flipped. */
.hy-pcard--catalog .hy-pcard-eyebrow {
  order: 1;
}
.hy-pcard--catalog .hy-pcard-name {
  order: 2;
}
.hy-pcard--catalog .hy-pcard-prices {
  order: 3;
}
.hy-pcard--catalog .hy-pcard-swatches {
  order: 4;
}

.hy-pcard-eyebrow {
  display: block;
  color: var(--color-text-tertiary);
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1.4;
}
html[lang="en"] .hy-pcard-eyebrow,
html[dir="ltr"] .hy-pcard-eyebrow {
  text-transform: uppercase;
  letter-spacing: var(--track-micro);
}

.hy-pcard--catalog .hy-pcard-name {
  color: var(--color-text-primary);
  font-size: 1.125rem;
  font-weight: 700;
  line-height: 1.4;
}
.hy-pcard--catalog .hy-pcard-name:hover {
  color: var(--color-accent);
}

.hy-pcard--catalog .hy-pcard-prices {
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.hy-pcard--catalog .hy-price {
  font-size: 1.125rem;
  font-weight: 700;
}
/* Only a DISCOUNTED price turns red. A red price on everything stops meaning
   "reduced" and starts meaning "price". */
.hy-pcard--catalog .hy-price--sale {
  color: var(--color-deal);
}
.hy-pcard--catalog .hy-price-was {
  font-size: 1rem;
  font-weight: 500;
  color: var(--color-text-tertiary);
  text-decoration: line-through;
}

/* ---- Swatches ----
   Qumra's option values carry an IMAGE (`values[].image.fileUrl`), not a hex
   code — the same mechanism the product page paints its swatches from. So a
   value with no image uploaded renders nothing here and the row simply does not
   appear; there is no colour to invent. See the note in ui/product-card.njk. */
.hy-pcard-swatches {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-block-start: 2px;
}

.hy-pcard-swatch {
  display: block;
  width: 24px;
  height: 24px;
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-radius: 3px;
  background: var(--color-bg-tertiary);
}
.hy-pcard-swatch img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The cap and the empty case are BOTH handled here rather than in the template.
   Nunjucks scopes `{% set %}` to the block it runs in, so neither a running
   count nor a "did anything render" flag survives a `{% for %}` — see the note
   in ui/product-card.njk. CSS has no such problem.

   Past five the row wraps and stops reading as a hint. */
.hy-pcard-swatch:nth-child(n + 6) {
  display: none;
}

/* Every value was text-only, so nothing rendered. `:empty` would not do — the
   template leaves whitespace inside the element even when it emits no swatch. */
.hy-pcard-swatches:not(:has(.hy-pcard-swatch)) {
  display: none;
}

/* The badge is a filled pill on this variant, not the vertical tab the default
   card uses — it sits inside a rounded frame, and a flush-to-the-edge tab
   fights the corner radius.

   ⚠️  `transform: none` is load-bearing. `cardBadgeStyle` defaults to
   'vertical' theme-wide, so the badge usually also carries
   `.hy-pcard-badge--vertical`, which is `writing-mode: vertical-rl` PLUS
   `rotate(180deg)`. Undoing only the writing-mode leaves the rotation standing
   and the pill renders upside down. */
.hy-pcard--catalog .hy-pcard-badge {
  inset-block-start: 12px;
  inset-inline-start: 12px;
  inset-inline-end: auto;
  writing-mode: horizontal-tb;
  text-orientation: initial;
  transform: none;
  /* A rounded RECTANGLE, not a pill. `--radius-pill` made it an oval, which
     reads as a status dot rather than a label — the reference badge is a small
     tag with a modest corner. Measured against it: ~30px tall, 8px radius. */
  display: inline-flex;
  align-items: center;
  height: 30px;
  padding: 0 12px;
  border-radius: 8px;
  background: var(--color-deal);
  color: var(--color-text-on-deal);
  font-family: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1;
}

/* ---- The "New" badge ----
   Same pill as the sale badge, a different colour. Green because it is the one
   badge that is not about price, and reusing the sale red would make a launch
   read as a discount. */
.hy-pcard--catalog .hy-pcard-badge--new {
  background: var(--color-success);
  color: #fff;
}

/* ---- "Selling fast" ----
   A strip on the image's lower edge when stock is genuinely low. */
.hy-pcard--catalog .hy-pcard-rush {
  position: absolute;
  inset-block-end: 16px;
  inset-inline: 16px;
  z-index: 2;
  overflow: hidden;
  height: 44px;
  display: flex;
  align-items: center;
  border-radius: 8px;
  background: var(--color-surface);
  box-shadow: 0 6px 18px -10px rgba(20, 17, 15, 0.4);
}

.hy-pcard-rush-track {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-inline: 12px;
  white-space: nowrap;
  font-size: 0.9375rem;
  font-weight: 700;
  color: var(--color-text-primary);
  /* The template emits FOUR copies, so translating by half the track puts copy
     three where copy one was — the loop is seamless. */
  animation: hyRush 14s linear infinite;
}

/* ⚠️  `translateX` is PHYSICAL — it does not flip with `dir`. In RTL the text
   runs right-to-left, so the track has to travel the other way or the strip
   scrolls into its own empty tail. Two keyframes, one per direction. */
@keyframes hyRush {
  to {
    transform: translateX(-50%);
  }
}
@keyframes hyRushRtl {
  to {
    transform: translateX(50%);
  }
}
html[dir="rtl"] .hy-pcard-rush-track {
  animation-name: hyRushRtl;
}

/* An endlessly moving strip is exactly what this setting is for. */
@media (prefers-reduced-motion: reduce) {
  .hy-pcard-rush-track {
    animation: none;
  }
}

@media (max-width: 767px) {
  .hy-pcard--catalog .hy-pcard-rush {
    inset-block-end: 10px;
    inset-inline: 10px;
    height: 32px;
  }
  .hy-pcard-rush-track {
    font-size: 0.75rem;
  }
  .hy-pcard--catalog .hy-pcard-name {
    font-size: 0.9375rem;
  }
  .hy-pcard--catalog .hy-price {
    font-size: 0.9375rem;
  }
}

/* --------------------------------------------------------------------------
   15.10  COLLECTION TABS  ·  .hy-ctabs-*
   --------------------------------------------------------------------------
     Featured Collections            ┌───────────────────────────┐
     Heritage Living                 │                           │
     ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾                 │      the active            │
     The Haven Collection            │      collection's          │
     Solace Series                   │      photograph            │
     [▫][▫][▫][▫]                    │                           │
     One line of copy.               │                           │
     ( Shop Collection )             └───────────────────────────┘

   The collection NAMES are the tabs — there is no separate tab strip. Picking
   one swaps the photograph, the thumbnails, the copy and the button together.

   ── The photograph reaches the viewport edge; the copy does not ──
   The section is a full-bleed two-column grid, so the media half runs off the
   page edge on its own side. The copy half then has to line itself up with
   `.hy-container`'s CONTENT edge by hand, or the text would start at the raw
   viewport edge and every section above and below it would look indented by
   comparison:

     padding-inline-<side>:
       max(--container-px, (100vw - --container-max) / 2 + --container-px)

   i.e. the gutter on a narrow screen, and half the leftover plus the gutter
   once the viewport is wider than the container. It is `padding`, never
   `width`, so the classic `100vw`-versus-scrollbar overflow cannot happen here;
   the worst case is a few pixels of drift on a browser with a classic
   scrollbar, which is invisible against a photograph.

   ── RTL ──
   `media_position` is start/end, not left/right, and every offset below is a
   logical property — so "media on the end side" puts the photograph on the
   right in Arabic and on the left in English with nothing to configure. The
   only physical value in the section is `order`, which is direction-agnostic.

   ⚠️  ARABIC — the `letter-spacing` below is scoped to [dir="ltr"].
   -------------------------------------------------------------------------- */

.hy-ctabs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
  background: var(--hy-sec-bg, var(--color-surface));
}

/* ---- Empty state ----
   A block with no collection name renders no tab and no pane, so a section
   where NONE of them is named would come out as a bare grey half beside a bare
   white one. Instead the skeleton shows and the real content hides — the swap
   is driven by `:has()` on the rendered tab buttons, because counting the named
   blocks would mean accumulating inside `{% for %}` and Nunjucks scopes
   `{% set %}` to the block it runs in.

   The skeleton is always in the markup and hidden by default; only a section
   with no tabs at all reveals it. */
.hy-ctabs-ph,
.hy-ctabs-pane--ph {
  display: none;
}

.hy-ctabs:not(:has(.hy-ctabs-tab)) .hy-ctabs-ph {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-5);
  width: 100%;
}
.hy-ctabs:not(:has(.hy-ctabs-tab)) .hy-ctabs-pane--ph {
  display: block;
  position: absolute;
  inset: 0;
  border: 1px dashed var(--color-border-strong);
  background: var(--color-bg-tertiary);
}

/* The copy half leads in source order, so the media only has to move for the
   other arrangement. `order` is not a direction-sensitive property — it acts on
   the grid's own flow, which has already flipped for RTL. */
.hy-ctabs--media-start .hy-ctabs-media {
  order: -1;
}

/* ---- Copy half ---- */
.hy-ctabs-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: var(--space-5);
  padding-block: clamp(var(--space-7), 6vw, var(--space-10));
  padding-inline: clamp(var(--space-5), 4vw, var(--space-8));
  min-width: 0;
}

/* See the alignment note in the section header. */
.hy-ctabs--media-end .hy-ctabs-body {
  padding-inline-start: max(
    var(--container-px),
    calc((100vw - var(--container-max)) / 2 + var(--container-px))
  );
}
.hy-ctabs--media-start .hy-ctabs-body {
  padding-inline-end: max(
    var(--container-px),
    calc((100vw - var(--container-max)) / 2 + var(--container-px))
  );
}

.hy-ctabs-eyebrow {
  margin: 0;
  font-family: var(--font-body-ar);
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.4;
  color: var(--color-text-primary);
}
html[lang="en"] .hy-ctabs-eyebrow,
html[dir="ltr"] .hy-ctabs-eyebrow {
  font-family: var(--font-body-en);
}

/* ---- The tabs ---- */
.hy-ctabs-list {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  width: 100%;
}

/* `align-self: flex-start` plus `width: auto` is what keeps the rule under the
   active name the width of the NAME rather than the width of the column. */
.hy-ctabs-tab {
  align-self: flex-start;
  width: auto;
  max-width: 100%;
  padding: 0 0 6px;
  border: 0;
  border-block-end: 3px solid transparent;
  background: none;
  font-family: var(--font-body-ar);
  font-size: clamp(1.75rem, 3.4vw, 3rem);
  font-weight: 800;
  line-height: 1.2;
  text-align: start;
  color: var(--color-text-tertiary);
  cursor: pointer;
  transition:
    color var(--t-base) var(--ease-soft),
    border-color var(--t-base) var(--ease-soft);
}
html[lang="en"] .hy-ctabs-tab,
html[dir="ltr"] .hy-ctabs-tab {
  font-family: var(--font-body-en);
  letter-spacing: -0.02em;
}
.hy-ctabs-tab:hover {
  color: var(--color-text-secondary);
}
.hy-ctabs-tab.is-active {
  color: var(--color-text-primary);
  border-block-end-color: currentColor;
}

/* ---- Thumbnails ---- */
.hy-ctabs-thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block-start: var(--space-2);
}

.hy-ctabs-thumb {
  display: block;
  width: clamp(76px, 8vw, 106px);
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: 10px;
  background: var(--color-bg-tertiary);
  transition: transform var(--t-fast) var(--ease-out);
}
.hy-ctabs-thumb:hover {
  transform: translateY(-2px);
}
.hy-ctabs-thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* The panel is a flex column of its own, not a bare wrapper: it sits inside
   `.hy-ctabs-body`'s flex column, so without this its three children (thumbs,
   copy, button) stack flush against each other and the body's `gap` never
   reaches them. */
.hy-ctabs-panel {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-5);
  width: 100%;
}

/* The unfilled tile. A dashed edge rather than another grey block: the filled
   tiles are also grey, so a solid one would read as a product whose photograph
   failed to load. */
.hy-ctabs-thumb--ph {
  border: 1px dashed var(--color-border-strong);
  background: var(--color-bg-tertiary);
  cursor: default;
}
.hy-ctabs-thumb--ph:hover {
  transform: none;
}

/* Same reason as every other `text` container — see 15.5. */
.hy-ctabs-text > :first-child {
  margin-block-start: 0;
}
.hy-ctabs-text > :last-child {
  margin-block-end: 0;
}

.hy-ctabs-text {
  margin: 0;
  max-width: 42ch;
  font-size: 1rem;
  line-height: 1.65;
  color: var(--color-text-secondary);
}

/* ---- Media half ---- */
.hy-ctabs-media {
  position: relative;
  overflow: hidden;
  /* The floor is what stops a tab with no image yet from collapsing the row to
     the height of its copy. */
  min-height: 420px;
  background: var(--color-bg-tertiary);
}

.hy-ctabs-pane {
  position: absolute;
  inset: 0;
}
.hy-ctabs-pane img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.hy-ctabs-noimg {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: var(--color-text-tertiary);
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

@media (max-width: 900px) {
  .hy-ctabs {
    grid-template-columns: 1fr;
  }

  /* The photograph goes first on a phone whichever side it took on desktop —
     it is the reason to stop scrolling, and a column of collection names above
     it reads as a menu with nothing attached. */
  .hy-ctabs-media {
    order: -1;
    min-height: 0;
    aspect-ratio: 4 / 3;
  }
  .hy-ctabs--media-start .hy-ctabs-media {
    order: -1;
  }

  /* The hand-computed container alignment is only needed while the two halves
     sit side by side. In one column the plain gutter is the correct value, and
     leaving the calc in place would indent the copy by half the viewport. */
  .hy-ctabs--media-end .hy-ctabs-body,
  .hy-ctabs--media-start .hy-ctabs-body {
    padding-inline: var(--container-px);
  }
  .hy-ctabs-body {
    padding-block: var(--space-7);
    gap: var(--space-4);
  }
}

@media (max-width: 640px) {
  .hy-ctabs--media-end .hy-ctabs-body,
  .hy-ctabs--media-start .hy-ctabs-body {
    padding-inline: var(--container-px-sm);
  }
  .hy-ctabs-tab {
    font-size: 1.625rem;
  }
  .hy-ctabs-thumb {
    width: 68px;
  }
}

/* --------------------------------------------------------------------------
   15.11  COLLECTIONS CAROUSEL
   --------------------------------------------------------------------------
   Tall photographic tiles, everything set over the image: a small caps meta
   row along the top (label on the start edge, product count on the end), and
   the collection's name plus a "more" link along the bottom.

   Legibility comes from two gradients rather than one flat wash. A single
   overlay dims the whole photograph, which is the one thing these tiles are
   for; two gradients darken only the two bands that carry text and leave the
   middle — where the subject is — untouched.
   -------------------------------------------------------------------------- */

.hy-coll-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  margin-bottom: var(--space-5);
}

.hy-coll-title {
  font-family: var(--font-display-en);
  font-weight: 400;
  font-size: clamp(1.75rem, 3.4vw, 2.75rem);
  line-height: 1.1;
  color: var(--color-text-primary);
  margin: 0;
}
html[dir="rtl"] .hy-coll-title {
  font-family: var(--font-display-ar);
  font-size: clamp(1.5rem, 3vw, 2.25rem);
  line-height: 1.4;
}

.hy-coll-stage {
  position: relative;
  min-width: 0;
}

/* ---- Tile ---- */
.hy-coll-tile {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--color-bg-tertiary);
  aspect-ratio: var(--coll-ratio, 2 / 3);
}

.hy-coll-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: transform var(--t-image) var(--ease-out);
}
.hy-coll-tile:hover .hy-coll-img {
  transform: scale(1.05);
}
@media (prefers-reduced-motion: reduce) {
  .hy-coll-tile:hover .hy-coll-img {
    transform: none;
  }
}

.hy-coll-noimg {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-border-strong);
}

/* Two bands, not one wash — see the note above. */
.hy-coll-tile::before,
.hy-coll-tile::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  pointer-events: none;
  z-index: 1;
}
.hy-coll-tile::before {
  inset-block-start: 0;
  height: 32%;
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--color-ink) 42%, transparent) 0%,
    color-mix(in srgb, var(--color-ink) 0%, transparent) 100%
  );
}
.hy-coll-tile::after {
  inset-block-end: 0;
  height: 46%;
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--color-ink) 62%, transparent) 0%,
    color-mix(in srgb, var(--color-ink) 0%, transparent) 100%
  );
}

/* ---- Meta row ---- */
.hy-coll-meta {
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: 20px 22px;
  /* over the tile's dark band — see the note at .hy-hero-slide.is-light */
  color: var(--hy-text-on-media, #ffffff);
}

.hy-coll-label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-body-en);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: var(--track-micro);
}
html[dir="rtl"] .hy-coll-label {
  font-family: var(--font-body-ar);
  font-size: 0.8125rem;
  text-transform: none;
}
.hy-coll-label svg {
  flex: none;
}

.hy-coll-count {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: 0.8125rem;
}

/* ---- Foot ---- */
.hy-coll-foot {
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  z-index: 2;
  padding: 22px;
  color: var(--hy-title-on-media, #ffffff);
}

.hy-coll-name {
  display: block;
  font-family: var(--font-display-en);
  font-weight: 400;
  font-size: clamp(1.125rem, 1.6vw, 1.5rem);
  line-height: 1.2;
  margin-bottom: 10px;
}
html[dir="rtl"] .hy-coll-name {
  font-family: var(--font-display-ar);
  line-height: 1.5;
}

.hy-coll-more {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--font-body-en);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: var(--track-micro);
}
html[dir="rtl"] .hy-coll-more {
  font-family: var(--font-body-ar);
  font-size: 0.8125rem;
  text-transform: none;
}
/* The arrow leans out of the tile — so it has to lean the other way in RTL,
   which a scaleX flip does without a second icon. */
.hy-coll-more svg {
  flex: none;
  transition: transform var(--t-base) var(--ease-out);
}
html[dir="rtl"] .hy-coll-more svg {
  transform: scaleX(-1);
}
.hy-coll-tile:hover .hy-coll-more svg {
  transform: translate(3px, -3px);
}
html[dir="rtl"] .hy-coll-tile:hover .hy-coll-more svg {
  transform: scaleX(-1) translate(3px, -3px);
}

/* ---- Arrows ---- */
.hy-coll-arrow {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-end: 18px;
  transform: translateY(-50%);
  z-index: 5;
  width: 52px;
  height: 52px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 999px;
  background: var(--color-surface);
  color: var(--color-text-primary);
  cursor: pointer;
  transition:
    opacity var(--t-base) var(--ease-soft),
    background-color var(--t-base) var(--ease-soft);
}
.hy-coll-arrow--prev {
  inset-inline-end: auto;
  inset-inline-start: 18px;
}
.hy-coll-arrow:hover {
  background: var(--color-bg-tertiary);
}
.hy-coll-arrow.swiper-button-disabled {
  opacity: 0;
  pointer-events: none;
}

@media (max-width: 640px) {
  .hy-coll-meta {
    padding: 14px 16px;
  }
  .hy-coll-foot {
    padding: 16px;
  }
  .hy-coll-arrow {
    width: 42px;
    height: 42px;
    inset-inline-end: 10px;
  }
  .hy-coll-arrow--prev {
    inset-inline-start: 10px;
  }
}

/* --------------------------------------------------------------------------
   15.11.1  CATEGORY GRID  ·  .hy-cgrid-*
   --------------------------------------------------------------------------
   Circular category tiles ruled into a table:

     Shop By Categories                        Shop All Products ›
     ┌────────┬────────┬────────┬────────┬────────┬────────┐
     │  ( ● ) │  ( ○ ) │  ( ○ ) │  ( ○ ) │  ( ○ ) │  ( ○ ) │
     │  Sale  │ Tables │ Lights │  Sofa  │ Storage│ Chairs │
     ├────────┼────────┼────────┼────────┼────────┼────────┤
     │   …    │   …    │   …    │   …    │   …    │   …    │
     └────────┴────────┴────────┴────────┴────────┴────────┘

   ⚠️  THE RULES ARE RINGS ON THE CELLS, not borders and not the frame.

   Each cell carries `box-shadow: 0 0 0 1px` — a spread-only ring 1px outside
   its border box — and the grid runs `gap: 1px`. Two neighbours therefore paint
   into the same 1px channel and land exactly on each other: every interior rule
   is drawn once and never doubled.

   The alternative — a border on each cell plus `:nth-child` resets to strip the
   outer edges — has to know the column count to know which cells sit on an
   edge, and that count changes at every breakpoint. This version has nothing to
   know: correct at any column count, in any direction, no physical side
   property anywhere in it.

   It replaced an earlier version that got the same result by painting the FRAME
   in the line colour and letting the gaps show it through. That drew the rules
   just as well, but it also painted every grid position with no cell in it:
   seven tiles in a six-column row left five empty positions on row two and they
   came out as one solid slab of border colour. Rings are cast by cells, so an
   empty position paints nothing.

   `overflow: hidden` on the frame is what lets the rounded corner clip the
   cells' square ones.
   -------------------------------------------------------------------------- */

.hy-cgrid-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-5);
  margin-block-end: var(--space-6);
}

.hy-cgrid-title {
  margin: 0;
  font-family: var(--font-body-ar);
  font-size: clamp(1.75rem, 3.4vw, 2.75rem);
  font-weight: 800;
  line-height: 1.2;
  color: var(--color-text-primary);
}
html[lang="en"] .hy-cgrid-title,
html[dir="ltr"] .hy-cgrid-title {
  font-family: var(--font-body-en);
  letter-spacing: -0.02em;
}

.hy-cgrid-all {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex-shrink: 0;
  color: var(--color-text-primary);
  font-size: 1rem;
  font-weight: 700;
  white-space: nowrap;
  transition: color var(--t-fast) var(--ease-soft);
}
.hy-cgrid-all:hover {
  color: var(--color-accent);
}

/* ---- The ruled frame ---- */
.hy-cgrid-frame {
  display: grid;
  grid-template-columns: repeat(var(--cg-cols, 6), minmax(0, 1fr));
  gap: 1px;
  /* SURFACE, not the line colour. The frame used to be painted in the line
     colour and let the gaps show it through — which drew the rules correctly
     but also painted every grid position that had NO cell in it. Seven tiles
     in a six-column row left five empty positions on row two, and they came
     out as one solid slab of border colour half the width of the section.
     The rules now come from the cells themselves, below. */
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--hy-cg-radius, 10px);
  overflow: hidden;
}

/* Rules off: no gaps, no frame, and the cells lose their own ground so the
   section background shows through instead. */
.hy-cgrid-frame--plain {
  gap: var(--space-4);
  background: none;
  border: 0;
  border-radius: 0;
}
.hy-cgrid-frame--plain .hy-cgrid-cell {
  background: none;
  box-shadow: none;
}

.hy-cgrid-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  padding: clamp(var(--space-5), 2.4vw, var(--space-7)) var(--space-3);
  background: var(--color-surface);
  color: var(--color-text-primary);
  text-align: center;
  /* The ring is the rule. A spread-only shadow sits 1px outside the border box,
     so two neighbours each paint into the same 1px gap and land on each other —
     every interior rule is drawn exactly once and never doubled, which is the
     property the old frame-background trick was chosen for. This keeps it while
     dropping its blind spot: an empty grid position has no cell, so it paints
     nothing and the frame's surface shows instead of a block of border colour.
     Still no `:nth-child`, still no column count to know, still no physical
     side property — and `outline` stays free to carry the focus ring. */
  box-shadow: 0 0 0 1px var(--color-border);
  transition: background var(--t-fast) var(--ease-soft);
}
.hy-cgrid-cell:hover {
  background: var(--color-bg-tertiary);
}

/* ---- The disc ---- */
.hy-cgrid-disc {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  /* Capped so a six-column row on a wide screen does not blow the discs up to
     fill it, and floored so a two-column phone row does not shrink them into
     bullets. */
  max-width: var(--cg-disc, 156px);
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: 50%;
  background: var(--color-bg-tertiary);
}

.hy-cgrid-disc img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: transform var(--t-image) var(--ease-out);
}
.hy-cgrid-cell:hover .hy-cgrid-disc img {
  transform: scale(1.06);
}

/* The highlight tile — a solid disc carrying a word instead of a photograph.
   It is the one saturated thing in the grid, so it is opt-in per section. */
.hy-cgrid-disc--solid {
  background: var(--cg-solid, var(--color-deal));
  color: var(--color-text-on-deal);
}

.hy-cgrid-disc-text {
  padding-inline: var(--space-3);
  font-family: var(--font-body-ar);
  font-size: clamp(1.125rem, 1.9vw, 1.625rem);
  font-weight: 700;
  line-height: 1.25;
}
html[lang="en"] .hy-cgrid-disc-text,
html[dir="ltr"] .hy-cgrid-disc-text {
  font-family: var(--font-body-en);
}

/* The empty-state tile (ui/placeholder.njk, kind='collection_disc'). It reuses
   `.hy-ph-media`, which is a flex box with the well as its ground — inside a
   disc that ground is already there, so only the art needs sizing. */
.hy-cgrid-disc.hy-ph-media {
  background: var(--color-bg-tertiary);
}
.hy-cgrid-disc .hy-ph-art {
  height: 52%;
  max-width: 62%;
}

.hy-cgrid-noimg {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-tertiary);
}

.hy-cgrid-label {
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.4;
  color: inherit;
}

.hy-cgrid-count {
  font-family: var(--font-num);
  font-size: 0.8125rem;
  color: var(--color-text-tertiary);
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

@media (max-width: 1023px) {
  .hy-cgrid-frame {
    grid-template-columns: repeat(var(--cg-cols-md, 4), minmax(0, 1fr));
  }
  .hy-cgrid-disc {
    max-width: 120px;
  }
}

@media (max-width: 640px) {
  .hy-cgrid-head {
    margin-block-end: var(--space-5);
  }
  .hy-cgrid-frame {
    grid-template-columns: repeat(var(--cg-cols-sm, 3), minmax(0, 1fr));
  }
  .hy-cgrid-cell {
    gap: var(--space-3);
    padding: var(--space-4) var(--space-2);
  }
  .hy-cgrid-disc {
    max-width: 88px;
  }
  .hy-cgrid-label {
    font-size: 0.8125rem;
  }
  .hy-cgrid-all {
    font-size: 0.9375rem;
  }
}

/* --------------------------------------------------------------------------
   15.12  MULTI-COLUMN  ·  .hy-cols-* / .hy-col-*
   --------------------------------------------------------------------------
   Two looks from one grid, picked by `style`:

     icon    ⌖  Free shipping        a service bar — small glyph, tight rows
             a compact strip, usually bordered top and bottom

     image   ┌────────┐              a feature row — a photograph per column
             │ photo  │              with a heading and a paragraph under it
             └────────┘
             Comfortable
             Bow Chair is available in…

   The two share the grid, the column count and the alignment; only the media
   and the type scale differ. Splitting them into separate widgets would have
   duplicated the responsive column logic for no gain.

   ⚠️  ARABIC — the `letter-spacing` below is scoped to [dir="ltr"].
   -------------------------------------------------------------------------- */

.hy-cols-grid {
  display: grid;
  grid-template-columns: repeat(var(--cols-d, 3), minmax(0, 1fr));
  gap: var(--cols-gap, 28px);
}

.hy-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  text-align: center;
  color: inherit;
}
.hy-cols--start .hy-col {
  align-items: flex-start;
  text-align: start;
}

/* ---- The icon look ---- */
.hy-col-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-primary);
}
.hy-col-icon img {
  width: var(--icon-size, 30px);
  height: var(--icon-size, 30px);
  object-fit: contain;
}

/* ---- The image look ----
   A photograph, so it fills its frame: `cover`, unlike the icon above, which is
   a cut-out and uses `contain`. */
.hy-col-figure {
  width: 100%;
  aspect-ratio: var(--cols-ratio, 1 / 1);
  overflow: hidden;
  border-radius: var(--cols-radius, 10px);
  background: var(--color-bg-tertiary);
  margin-block-end: var(--space-3);
}
.hy-col-figure img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.hy-col-noimg {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: var(--color-text-tertiary);
}

/* ---- Type ---- */
.hy-col-title {
  margin: 0;
  font-family: var(--font-body-ar);
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.4;
  color: var(--color-text-primary);
}
html[lang="en"] .hy-col-title,
html[dir="ltr"] .hy-col-title {
  font-family: var(--font-body-en);
}

/* The image look carries a real heading, not a label on a service bar. */
.hy-cols--image .hy-col-title {
  font-size: clamp(1.25rem, 1.8vw, 1.5rem);
  font-weight: 800;
}
html[lang="en"] .hy-cols--image .hy-col-title,
html[dir="ltr"] .hy-cols--image .hy-col-title {
  letter-spacing: -0.01em;
}

.hy-col-text {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--color-text-secondary);
}
.hy-cols--image .hy-col-text {
  font-size: 1rem;
  line-height: 1.65;
}
/* A `text` field arrives as HTML and is printed with `| safe`, so the copy is a
   `<p>` inside this element — its own margin does not reach it. */
.hy-col-text > :first-child {
  margin-block-start: 0;
}
.hy-col-text > :last-child {
  margin-block-end: 0;
}

/* ---- Rules ---- */
.hy-cols--bordered {
  border-block-end: 1px solid var(--color-border);
}
.hy-cols--bordered-top {
  border-block-start: 1px solid var(--color-border);
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

@media (max-width: 1023px) {
  .hy-cols-grid {
    grid-template-columns: repeat(var(--cols-md, 2), minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  .hy-cols-grid {
    grid-template-columns: repeat(var(--cols-sm, 1), minmax(0, 1fr));
    gap: var(--space-6);
  }
  .hy-col-icon {
    margin-block-end: var(--space-2);
  }
  .hy-col-text {
    font-size: 0.875rem;
  }
  .hy-cols--image .hy-col-title {
    font-size: 1.125rem;
  }
  .hy-cols--image .hy-col-text {
    font-size: 0.9375rem;
  }
}

/* --------------------------------------------------------------------------
   15.13  FAVOURITE PRODUCTS  ·  .hy-fav-*
   --------------------------------------------------------------------------
     ┌───────────────────────┬───────────────────────────┐
     │  lifestyle photograph │  Our Favorite Products    │
     │      ┌────────┐       │                           │
     │      │ product│       │  “Each piece is authentic │
     │      │  card  │       │   and carefully sourced.” │
     │      └────────┘       │                           │
     │                       │  Julie Mathers            │
     │                       │  Australia                │
     │                       │  ‹  2/3  ›                │
     └───────────────────────┴───────────────────────────┘

   A quote carousel with a photograph and one product per slide. The product
   card floats ON the photograph rather than sitting beside it — that overlap is
   the whole composition, and it is why the media half is `position: relative`
   and the card is absolutely placed inside it.

   ⚠️  THE COUNTER IS FORCED LTR — same rule as the countdown's clock. "2/3" is
   a fraction, and a fraction reads left-to-right in Arabic too; letting it flow
   RTL renders it "3/2", which is not a stylistic choice, it is the wrong
   number.

   ⚠️  ARABIC — the `letter-spacing` below is scoped to [dir="ltr"].
   -------------------------------------------------------------------------- */

.hy-fav {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
}

/* The photograph leads in source order, so only the other arrangement moves.
   `order` acts on the grid's own flow, which has already flipped for RTL. */
.hy-fav--media-end .hy-fav-media {
  order: 1;
}

/* ---- Media half ---- */
.hy-fav-media {
  position: relative;
  overflow: hidden;
  min-height: 520px;
  background: var(--color-bg-tertiary);
}

.hy-fav-pane {
  position: absolute;
  inset: 0;
}
.hy-fav-pane img {
  width: 100%;
  height: 100%;
  /* `cover` — the photograph fills the media half edge to edge.

     It was `contain`, which showed the whole image and left bands wherever the
     photo's ratio differed from this half's (520px tall against half a
     container, so bands on almost everything portrait). A frame with a picture
     floating inside it is not a card; the picture IS the card here.

     What cover cuts is not aimed — the per-slide focus setting that used to
     write `object-position` is gone. That is fine for this section and not for
     the hero: this half is close to square, so a centred crop of a portrait or
     a landscape shot both keep their subject. Wide panoramas are the one shape
     to avoid feeding it. */
  object-fit: cover;
}

.hy-fav-noimg {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: var(--color-text-tertiary);
}

/* ---- The floating card ----
   Centred on the media half rather than pinned to a corner: the photograph
   behind it is chosen per slide and a corner that works for one crop covers the
   subject of the next. */
.hy-fav-card {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
  width: min(var(--hy-fav-card-w, 300px), 76%);
  padding: 16px;
  border-radius: 14px;
  background: var(--color-surface);
  box-shadow: 0 24px 60px -24px rgba(20, 17, 15, 0.35);
}
/* `translate` is physical, so the inline-start anchor has to be undone on the
   other axis when the document flips — otherwise the card sits half a card
   width off centre in Arabic. */
html[dir="rtl"] .hy-fav-card {
  transform: translate(50%, -50%);
}

/* The card inside is the catalog product card, minus its shopping affordances —
   see the locals passed in the widget. Its frame gets the tighter radius the
   sheet around it implies. */
.hy-fav-card .hy-pcard--catalog .hy-pcard-frame {
  --hy-cat-radius: 8px;
}
.hy-fav-card .hy-pcard-info {
  padding-inline: 2px;
}

/* ---- Panel half ---- */
.hy-fav-panel {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: var(--space-5);
  padding-block: clamp(var(--space-7), 6vw, var(--space-10));
  padding-inline: clamp(var(--space-5), 4vw, var(--space-8));
  background: var(--hy-fav-panel, #f3e9e7);
  color: var(--color-text-primary);
  min-width: 0;
}

/* The copy lines up with `.hy-container`'s CONTENT edge on the side away from
   the photograph — the same computation the collection tabs use, and for the
   same reason. See the note in 15.10. */
.hy-fav--media-start .hy-fav-panel {
  padding-inline-end: max(
    var(--container-px),
    calc((100vw - var(--container-max)) / 2 + var(--container-px))
  );
}
.hy-fav--media-end .hy-fav-panel {
  padding-inline-start: max(
    var(--container-px),
    calc((100vw - var(--container-max)) / 2 + var(--container-px))
  );
}

.hy-fav-eyebrow {
  margin: 0;
  font-family: var(--font-body-ar);
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.4;
}
html[lang="en"] .hy-fav-eyebrow,
html[dir="ltr"] .hy-fav-eyebrow {
  font-family: var(--font-body-en);
}

.hy-fav-slide {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4);
  width: 100%;
}

/* A `text` field arrives as HTML and is printed with `| safe`, so the quote's
   real content is a `<p>` inside this element — the element's own `margin: 0`
   does not reach it. Neutralising the wrapper's outer margins keeps the block
   flush whether the merchant typed one paragraph or three. */
.hy-fav-quote > :first-child {
  margin-block-start: 0;
}
.hy-fav-quote > :last-child {
  margin-block-end: 0;
}

.hy-fav-quote {
  margin: 0;
  max-width: 22ch;
  font-family: var(--font-body-ar);
  font-size: clamp(1.5rem, 2.9vw, 2.375rem);
  font-weight: 700;
  line-height: 1.3;
}
html[lang="en"] .hy-fav-quote,
html[dir="ltr"] .hy-fav-quote {
  font-family: var(--font-body-en);
  letter-spacing: -0.015em;
  max-width: 26ch;
}

.hy-fav-author {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.hy-fav-name {
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.4;
}

.hy-fav-role {
  font-size: 1rem;
  line-height: 1.4;
  color: var(--color-text-secondary);
}

/* ---- Controls ---- */
.hy-fav-nav {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-block-start: var(--space-4);
}

.hy-fav-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 0;
  background: none;
  color: var(--color-text-primary);
  cursor: pointer;
  transition:
    opacity var(--t-fast) var(--ease-soft),
    transform var(--t-fast) var(--ease-out);
}
.hy-fav-arrow:hover {
  opacity: 0.55;
}
.hy-fav-arrow:active {
  transform: scale(0.92);
}

/* Forced LTR — see the note in the section header. */
.hy-fav-count {
  direction: ltr;
  font-family: var(--font-num);
  font-size: 1rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
  min-width: 3.5ch;
  text-align: center;
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

@media (max-width: 900px) {
  .hy-fav {
    grid-template-columns: 1fr;
  }

  /* The photograph goes first on a phone whichever side it took on desktop. */
  .hy-fav-media,
  .hy-fav--media-end .hy-fav-media {
    order: -1;
    min-height: 0;
    aspect-ratio: 1 / 1;
  }

  /* The hand-computed container alignment only applies while the halves sit
     side by side; in one column the plain gutter is the correct value. */
  .hy-fav--media-start .hy-fav-panel,
  .hy-fav--media-end .hy-fav-panel {
    padding-inline: var(--container-px);
    padding-block: var(--space-7);
  }

  .hy-fav-card {
    width: min(var(--hy-fav-card-w, 300px), 62%);
  }
}

@media (max-width: 640px) {
  .hy-fav--media-start .hy-fav-panel,
  .hy-fav--media-end .hy-fav-panel {
    padding-inline: var(--container-px-sm);
  }
  .hy-fav-quote {
    font-size: 1.375rem;
    max-width: none;
  }
  .hy-fav-card {
    width: min(240px, 66%);
    padding: 12px;
  }
}

/* --------------------------------------------------------------------------
   15.13.1  SPACES GALLERY  ·  .hy-look-*
   --------------------------------------------------------------------------
     Get Inspired by Spaces
     ┌──┐ ┌────────┐ ┌──────────────────┐ ┌──┐
     │  │ │        │ │                  │ │  ⋯
     │ ③│ │      ③ │ │                ③ │ │
     └──┘ └────────┘ └──────────────────┘ └──┘
     ▬▬▬▬▬▬▬────────────────────────      ( ‹ ) ( › )

   Mixed widths on one row: every tile is the same HEIGHT and takes its width
   from its own aspect ratio, so a portrait shot and a panorama sit side by side
   without either being cropped to fit a grid cell.

   ⚠️  The ratio is a per-slide SETTING, not measured from the file. Swiper is
   told `slidesPerView: 'auto'` and reads each slide's width during init — which
   happens before the photographs have loaded, so an intrinsic width would be
   zero at that moment and every tile would collapse. Declaring the ratio also
   means no layout shift when the image finally arrives.

   ── Reused, not rebuilt ──
   The head (`.hy-prow-head` / `.hy-prow-title`) and the whole control row
   (`.hy-prow-controls` / `-rail` / `-nav` / `-arrow`) are the product row's,
   from 15.8. They are the same objects doing the same job; a second copy here
   would be two things to keep in step for no visual difference.
   -------------------------------------------------------------------------- */

.hy-look-stage {
  position: relative;
}

/* Same bleed rule as the product row: the sliver of the next tile past the
   container edge is what says the row scrolls, so overflow stays visible on the
   block axis and is clipped only inline — `hidden` would clamp both and add a
   page-wide horizontal scrollbar. */
.hy-look-stage .swiper {
  overflow: visible;
  overflow-x: clip;
}

/* `width: auto` is what lets `slidesPerView: 'auto'` measure each tile. */
.hy-look-stage .swiper-slide {
  width: auto;
  height: auto;
}

/* ⚠️  The width is CALCULATED from the height, not left to `aspect-ratio`.

   A block box's `width: auto` means "fill the containing block", and that wins
   over `aspect-ratio` — with `aspect-ratio` alone every tile came out the same
   width as its neighbour and the mixed-width row collapsed into equal columns.
   `--look-r` is the ratio as a DECIMAL (0.75 for 3:4, 1.778 for 16:9), printed
   per slide by the widget, so width and height can never disagree.

   `--look-h` is indirected through a local so the mobile override below has one
   value to change and both dimensions follow it. */
.hy-look-tile {
  --look-h: var(--hy-look-h, 560px);
  position: relative;
  display: block;
  height: var(--look-h);
  width: calc(var(--look-h) * var(--look-r, 0.75));
  /* A 16:9 tile at full height is wider than a phone. Capping the width crops
     the photograph a little further rather than pushing the row off-screen. */
  max-width: 88vw;
  overflow: hidden;
  border-radius: var(--hy-look-radius, 10px);
  background: var(--color-bg-tertiary);
}

.hy-look-tile img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: transform var(--t-image) var(--ease-out);
}
.hy-look-tile:hover img {
  transform: scale(1.03);
}

.hy-look-noimg {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: var(--color-text-tertiary);
}

/* ---- The tag badge ----
   A count of the products tagged in that photograph. It is only rendered when
   there are any, so it never shows a bare zero. */
.hy-look-badge {
  position: absolute;
  inset-block-end: 16px;
  inset-inline-start: 16px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 40px;
  padding-inline: 14px;
  border-radius: 999px;
  background: var(--color-surface);
  color: var(--color-text-primary);
  box-shadow: 0 6px 20px -8px rgba(20, 17, 15, 0.35);
  transition: transform var(--t-fast) var(--ease-out);
}
.hy-look-tile:hover .hy-look-badge {
  transform: translateY(-2px);
}

.hy-look-badge svg {
  flex-shrink: 0;
}

/* The numeral rides the Latin body face in both scripts — a count is a figure,
   and tabular figures keep every badge in the row the same width. */
.hy-look-count {
  font-family: var(--font-num);
  font-size: 0.9375rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
  line-height: 1;
}

@media (max-width: 640px) {
  .hy-look-tile {
    --look-h: var(--hy-look-h-sm, 340px);
    border-radius: 8px;
  }
  .hy-look-badge {
    inset-block-end: 12px;
    inset-inline-start: 12px;
    height: 34px;
    padding-inline: 11px;
  }
  .hy-look-count {
    font-size: 0.875rem;
  }
}

/* --------------------------------------------------------------------------
   15.13.2  OFFER CARDS  ·  .hy-offer-*
   --------------------------------------------------------------------------
     ┌──────────────────────────┐ ┌──────────────────────────┐
     │ Turn Chairs        ╭───╮ │ │ Cross Chairs       ╭───╮ │
     │ Elevate your space │40%│ │ │ Get 30% off …      │30%│ │
     │                    ╰───╯ │ │                    ╰───╯ │
     │ ( Shop Now )    [chair]  │ │ ( Shop Now )    [chair]  │
     └──────────────────────────┘ └──────────────────────────┘

   Two halves inside each card: copy on the start side, a cut-out product on the
   end side sitting on the card's bottom edge. The discount is a circle that
   straddles the two.

   ⚠️  The product image is `object-fit: contain` with `object-position: bottom`,
   NOT `cover`. These are cut-outs on a flat ground, not photographs to fill a
   frame — `cover` crops the legs off a chair and centres what is left.

   ⚠️  The badge is positioned with `inset-inline-end`, so it lands on the same
   side as the product in both directions with nothing to configure: the image
   is the end half, and the badge sits over its top corner.
   -------------------------------------------------------------------------- */

.hy-offer-grid {
  display: grid;
  grid-template-columns: repeat(var(--offer-cols, 2), minmax(0, 1fr));
  gap: var(--space-5);
}

.hy-offer {
  position: relative;
  display: grid;
  grid-template-columns: 1fr minmax(0, 44%);
  align-items: end;
  min-height: var(--hy-offer-h, 380px);
  overflow: hidden;
  border-radius: var(--hy-offer-radius, 10px);
  /* Per-card, so a row can pair a pink card with a green one. */
  background: var(--offer-bg, var(--color-bg-tertiary));
  color: var(--offer-fg, var(--color-text-primary));
}

.hy-offer-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4);
  /* Aligned to the top, not centred: the copy block is short and the card is
     tall, and a centred block drifts away from the title's own baseline. */
  align-self: stretch;
  justify-content: center;
  padding: clamp(var(--space-5), 3vw, var(--space-7));
  min-width: 0;
  z-index: 1;
}

.hy-offer-title {
  margin: 0;
  font-family: var(--font-body-ar);
  font-size: clamp(1.5rem, 2.8vw, 2.375rem);
  font-weight: 800;
  line-height: 1.2;
  color: inherit;
}
html[lang="en"] .hy-offer-title,
html[dir="ltr"] .hy-offer-title {
  font-family: var(--font-body-en);
  letter-spacing: -0.02em;
}

.hy-offer-text {
  margin: 0;
  max-width: 28ch;
  font-size: 1rem;
  line-height: 1.6;
  opacity: 0.72;
}
/* A `text` field arrives as HTML and is printed with `| safe`, so the real copy
   is a `<p>` inside this element — its own margin does not reach it. */
.hy-offer-text > :first-child {
  margin-block-start: 0;
}
.hy-offer-text > :last-child {
  margin-block-end: 0;
}

.hy-offer-media {
  position: relative;
  align-self: stretch;
  min-width: 0;
}

.hy-offer-media img {
  position: absolute;
  inset-block-end: 0;
  inset-inline-end: 0;
  width: 100%;
  height: 92%;
  object-fit: contain;
  object-position: bottom;
}

.hy-offer-noimg {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0.35;
}

/* ---- The discount circle ---- */
.hy-offer-badge {
  position: absolute;
  inset-block-start: clamp(var(--space-4), 2vw, var(--space-6));
  inset-inline-end: clamp(var(--space-4), 2vw, var(--space-6));
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  width: var(--hy-offer-badge, 96px);
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--badge-bg, var(--color-deal));
  color: var(--badge-fg, #fff);
  text-align: center;
  line-height: 1.1;
}

.hy-offer-badge-label {
  font-size: 0.8125rem;
  font-weight: 600;
}

.hy-offer-badge-value {
  font-family: var(--font-num);
  font-size: 1.5rem;
  font-weight: 800;
}

@media (max-width: 900px) {
  .hy-offer-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 520px) {
  .hy-offer {
    min-height: var(--hy-offer-h-sm, 300px);
    grid-template-columns: 1fr minmax(0, 38%);
  }
  .hy-offer-badge {
    width: 72px;
  }
  .hy-offer-badge-value {
    font-size: 1.125rem;
  }
  .hy-offer-badge-label {
    font-size: 0.6875rem;
  }
}

/* --------------------------------------------------------------------------
   15.13.3  IMAGE + TEXT  ·  .hy-imgtext-*
   --------------------------------------------------------------------------
     ┌───────────────────────┐   Meet Our Team
     │                  ╭──╮ │   The creative minds
     │   photograph     │◠◡│ │   behind our studio
     │                  ╰──╯ │   One or two lines of copy.
     │                       │   ⌖ Made locally
     │                       │   ☺ 4.8 review score
     │                       │   ▣ Over 50 products
     └───────────────────────┘   ( Contact Us )

   The seal straddles the photograph's corner, so the media column is the
   positioning context and the seal is allowed to overflow it — which is why
   `.hy-imgtext-media` is `position: relative` but never `overflow: hidden`.
   The IMAGE clips itself instead.

   ⚠️  The seal's ring of text is a real `<textPath>` on a circle, not a letter
   spun per character. Rotating characters one by one breaks Arabic outright:
   the shaping engine needs the run intact to join the letters, and a per-letter
   transform hands it isolated forms.

   ⚠️  ARABIC — the `letter-spacing` below is scoped to [dir="ltr"].
   -------------------------------------------------------------------------- */

.hy-imgtext {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  /* The floor is deliberately larger than the seal's overhang below. With a
     smaller gap the seal crossed the column boundary and sat on the headline —
     the two numbers have to be read together. */
  gap: clamp(var(--space-7), 5vw, var(--space-9));
}

/* The copy leads in source order, so only the other arrangement moves. `order`
   acts on the grid's own flow, which has already flipped for RTL. */
.hy-imgtext--media-end .hy-imgtext-media {
  order: 1;
}

/* ---- Media ---- */
.hy-imgtext-media {
  position: relative;
  /* No `overflow: hidden` — the seal has to hang past this corner. */
}

.hy-imgtext-figure {
  overflow: hidden;
  border-radius: var(--hy-it-radius, 14px);
  background: var(--color-bg-tertiary);
  aspect-ratio: var(--hy-it-ratio, 1 / 1);
}
.hy-imgtext-figure img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.hy-imgtext-noimg {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: var(--color-text-tertiary);
}

/* ---- The seal ----
   Pinned to the corner nearest the copy, so it reads as the bridge between the
   two halves rather than an ornament stuck on the far edge. */
.hy-imgtext-seal {
  position: absolute;
  inset-block-start: var(--hy-it-seal-y, 12%);
  /* Roughly a third of the seal hangs past the frame — enough to read as
     straddling the corner, small enough to stay inside the column gap above. */
  inset-inline-end: calc(var(--hy-it-seal, 150px) * -0.3);
  z-index: 2;
  display: block;
  width: var(--hy-it-seal, 150px);
  aspect-ratio: 1;
}
.hy-imgtext--media-end .hy-imgtext-seal {
  inset-inline-end: auto;
  inset-inline-start: calc(var(--hy-it-seal, 150px) * -0.3);
}

.hy-imgtext-seal-disc {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--hy-it-seal-bg, #efee8e);
}

/* The ring turns; the icon in the middle does not. Two elements rather than one
   rotating parent — a spinning icon reads as a loading spinner. */
.hy-imgtext-seal-ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.hy-imgtext--spin .hy-imgtext-seal-ring {
  animation: hySealSpin 18s linear infinite;
}
@keyframes hySealSpin {
  to {
    transform: rotate(360deg);
  }
}
@media (prefers-reduced-motion: reduce) {
  .hy-imgtext--spin .hy-imgtext-seal-ring {
    animation: none;
  }
}

/* The viewBox is 200 units wide and the seal renders at ~150px, so a unit is
   about 0.75px — 11 units came out barely 8px on screen. 14 lands near 10.5px,
   which is the smallest this ring stays legible at. */
.hy-imgtext-seal-ring text {
  fill: var(--hy-it-seal-fg, #141414);
  font-family: var(--font-body-ar);
  font-size: 14px;
  font-weight: 700;
  /* ⚠️  `direction: ltr` is why the ring is visible at all on an Arabic store.

     <text> inherits `direction: rtl` from <html dir="rtl">, and on a textPath
     that does not mirror the text — it lays it out from the END of the path
     BACKWARDS, i.e. before offset 0, which is off the rendered range. The
     glyphs are laid out and then clipped away, so the DOM shows a <text> with
     the right content and the screen shows nothing. That is exactly the
     symptom this ring had.

     This does NOT reverse the Arabic. Bidi resolves the Arabic run itself as
     RTL whatever the paragraph direction is, and the font still shapes and
     joins it — `direction` only sets the BASE direction the runs are placed
     in, and there is one run here. The letter-spacing rule below stays scoped
     to `html[dir="ltr"]`, so Arabic is still never tracked out. */
  direction: ltr;
}
html[lang="en"] .hy-imgtext-seal-ring text,
html[dir="ltr"] .hy-imgtext-seal-ring text {
  font-family: var(--font-body-en);
  letter-spacing: 0.14em;
}

.hy-imgtext-seal-icon {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--hy-it-seal-fg, #141414);
}
.hy-imgtext-seal-icon svg {
  width: 34%;
  height: 34%;
}

/* ---- Copy ---- */
.hy-imgtext-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4);
  min-width: 0;
}

.hy-imgtext-eyebrow {
  margin: 0;
  font-family: var(--font-body-ar);
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.4;
  color: var(--color-text-primary);
}
html[lang="en"] .hy-imgtext-eyebrow,
html[dir="ltr"] .hy-imgtext-eyebrow {
  font-family: var(--font-body-en);
}

.hy-imgtext-title {
  margin: 0;
  max-width: 18ch;
  font-family: var(--font-body-ar);
  font-size: clamp(1.75rem, 3.4vw, 2.75rem);
  font-weight: 800;
  line-height: 1.18;
  color: var(--color-text-primary);
}
html[lang="en"] .hy-imgtext-title,
html[dir="ltr"] .hy-imgtext-title {
  font-family: var(--font-body-en);
  letter-spacing: -0.02em;
}

.hy-imgtext-text {
  margin: 0;
  max-width: 46ch;
  font-size: 1.0625rem;
  line-height: 1.65;
  color: var(--color-text-secondary);
}
/* A `text` field arrives as HTML and is printed with `| safe`, so the copy is a
   `<p>` inside this element — its own margin does not reach it. */
.hy-imgtext-text > :first-child {
  margin-block-start: 0;
}
.hy-imgtext-text > :last-child {
  margin-block-end: 0;
}

/* ---- The proof rows ---- */
.hy-imgtext-facts {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-block: var(--space-3);
}

.hy-imgtext-fact {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.4;
  color: var(--color-text-primary);
}

.hy-imgtext-fact svg {
  flex-shrink: 0;
  color: var(--color-text-primary);
}

/* The unfilled row's icon slot — a dashed square where the icon will sit, so
   the row reads as a row and not as a stray line. */
.hy-imgtext-fact-ph {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  border: 1px dashed var(--color-border-strong);
  border-radius: 5px;
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

@media (max-width: 900px) {
  .hy-imgtext {
    grid-template-columns: 1fr;
    gap: var(--space-6);
  }
  /* The photograph leads on a phone whichever side it took on desktop. */
  .hy-imgtext-media,
  .hy-imgtext--media-end .hy-imgtext-media {
    order: -1;
  }
  /* Back inside the frame: at one column there is no facing half for it to
     bridge, and hanging off the edge would push the page sideways. */
  .hy-imgtext-seal,
  .hy-imgtext--media-end .hy-imgtext-seal {
    inset-inline-end: 16px;
    inset-inline-start: auto;
    width: clamp(96px, 26vw, 140px);
  }
}

@media (max-width: 640px) {
  .hy-imgtext-title {
    max-width: none;
  }
  .hy-imgtext-fact {
    font-size: 1rem;
    gap: 12px;
  }
}

/* --------------------------------------------------------------------------
   15.13.4  TESTIMONIALS  ·  .hy-quotes-*
   --------------------------------------------------------------------------
     ┌──────────────────────────────────────────────────────────────┐
     │                  What Clients Talk About Us                  │
     │  …faded    ‹   “Every item feels like a masterpiece.”   ›  …faded │
     │                        David Martinez                        │
     │                        Austin, Texas                         │
     └──────────────────────────────────────────────────────────────┘

   Centred slides with the neighbours showing through, dimmed. The section is
   full-bleed colour, so the quotes that spill past the centre one are still on
   the same ground and read as "more of these" rather than as clipped content.

   ⚠️  The arrows are NOT pinned to the viewport edges. They sit just outside the
   ACTIVE slide, so their gap tracks the slide width at every breakpoint:
   the nav is a full-width flex row capped at `100% / --q-cols` plus an
   allowance, then centred. Fixed insets drifted into the copy the moment the
   column count changed.

   ⚠️  ARABIC — the `letter-spacing` below is scoped to [dir="ltr"].
   -------------------------------------------------------------------------- */

.hy-quotes {
  position: relative;
  overflow: hidden;
}

.hy-quotes-eyebrow {
  margin: 0 0 var(--space-6);
  text-align: center;
  font-family: var(--font-body-ar);
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.4;
  color: var(--hy-q-fg, var(--color-text-primary));
}
html[lang="en"] .hy-quotes-eyebrow,
html[dir="ltr"] .hy-quotes-eyebrow {
  font-family: var(--font-body-en);
}

.hy-quotes-stage {
  position: relative;
}

/* Centred slides need the row to bleed past the container — the neighbours are
   the point. `overflow-x: clip` on the section handles the page-level cut. */
.hy-quotes-stage .swiper {
  overflow: visible;
}

/* ⚠️  Swiper gives an uninitialised slide `width: 100%`, so without this the
   first frames show one quote at full width and then jump. Keep `--q-cols` in
   step with `slidesPerView` in the widget. */
.hy-quotes-stage .swiper-slide {
  width: calc(100% / var(--q-cols, 1.15));
  height: auto;
}

/* The neighbours are present but recessive. `opacity`, not a colour change, so
   one rule covers the quote, the name and the place at once. */
.hy-quotes-slide {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  padding-inline: clamp(var(--space-4), 3vw, var(--space-7));
  text-align: center;
  opacity: 0.22;
  transition: opacity var(--t-base) var(--ease-soft);
}
.swiper-slide-active .hy-quotes-slide {
  opacity: 1;
}

.hy-quotes-text {
  margin: 0;
  max-width: 24ch;
  font-family: var(--font-body-ar);
  font-size: clamp(1.375rem, 2.9vw, 2.25rem);
  font-weight: 800;
  line-height: 1.3;
  color: var(--hy-q-fg, var(--color-text-primary));
}
html[lang="en"] .hy-quotes-text,
html[dir="ltr"] .hy-quotes-text {
  font-family: var(--font-body-en);
  letter-spacing: -0.015em;
  max-width: 28ch;
}
/* A `text` field arrives as HTML and is printed with `| safe`, so the quote is a
   `<p>` inside this element — its own margin does not reach it. */
.hy-quotes-text > :first-child {
  margin-block-start: 0;
}
.hy-quotes-text > :last-child {
  margin-block-end: 0;
}

.hy-quotes-who {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  margin-block-start: var(--space-3);
}

.hy-quotes-name {
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.4;
  color: var(--hy-q-fg, var(--color-text-primary));
}

.hy-quotes-role {
  font-size: 1rem;
  line-height: 1.4;
  /* `--hy-q-fg-2` falls back to `--hy-q-fg`, so with no secondary pick this is
     byte-identical to what it was — the dim comes from `opacity` as before. */
  color: var(--hy-q-fg-2, var(--hy-q-fg, var(--color-text-primary)));
  opacity: 0.6;
}

/* ---- Arrows ----
   A full-width row capped at the active slide's width plus an allowance, then
   centred — see the note in the section header. `space-between` puts prev on
   the correct side in both directions with no override. */
.hy-quotes-nav {
  position: absolute;
  inset-block-start: 50%;
  inset-inline: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  max-width: calc(
    100% / var(--q-cols, 1.15) + 2 * var(--hy-q-arrow-gap, 108px)
  );
  margin-inline: auto;
  transform: translateY(-50%);
  pointer-events: none;
}
.hy-quotes-nav > * {
  pointer-events: auto;
}

.hy-quotes-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border: 1px solid currentColor;
  border-radius: 999px;
  background: transparent;
  color: var(--hy-q-fg, var(--color-text-primary));
  cursor: pointer;
  opacity: 0.55;
  transition:
    opacity var(--t-fast) var(--ease-soft),
    background var(--t-fast) var(--ease-soft);
}
.hy-quotes-arrow:hover {
  opacity: 1;
  background: rgba(255, 255, 255, 0.35);
}
.hy-quotes-arrow.swiper-button-disabled {
  opacity: 0.2;
  cursor: default;
}

@media (max-width: 900px) {
  .hy-quotes-nav {
    /* The neighbours are barely visible at this width, so the arrows come in
       to the container edge instead of tracking a slide that fills it. */
    max-width: 100%;
    padding-inline: var(--space-4);
  }
  .hy-quotes-arrow {
    width: 42px;
    height: 42px;
  }
}

@media (max-width: 640px) {
  .hy-quotes-slide {
    padding-inline: var(--space-6);
  }
  .hy-quotes-text {
    font-size: 1.25rem;
    max-width: none;
  }
  /* Under the quote rather than beside it: two 42px targets either side of a
     phone-width quote leave the text about six words a line. */
  .hy-quotes-nav {
    position: static;
    justify-content: center;
    gap: var(--space-4);
    margin-block-start: var(--space-5);
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   15.13.5  SPLIT SLIDER  ·  .hy-split-*
   --------------------------------------------------------------------------
     ┌───────────────────────┐ ┌───────────┬───────────┐
     │ Dining & Kitchen      │ │   lamp    │   bench   │
     │ Difference in         │ │ Table Lamps│ Lounge   │
     │ the Details           │ ├───────────┼───────────┤
     │ ( Shop Now )          │ │   table   │   stool   │
     │      ‹  ·▬·  ›        │ │ Side tables│ Decors   │
     └───────────────────────┘ └───────────┴───────────┘

   A carousel on one half, a 2×2 grid of offer tiles on the other.

   ── Reused, not rebuilt ──
   The carousel's controls are the HERO's: `.hy-hero-controls` /
   `.hy-hero-arrow` / `.hy-hero-dots` from 15.5. They are the identical object
   doing the identical job — white controls sitting on a photograph, a dot per
   slide with the active one stretched into a bar. A second copy here would be
   two things to keep in step for no visual difference.

   ⚠️  The two halves are the same HEIGHT because the grid defines it: four
   square tiles plus a gap. The slider half has no height of its own and
   stretches to match, so the row stays level whatever the copy runs to.

   ⚠️  ARABIC — the `letter-spacing` below is scoped to [dir="ltr"].
   -------------------------------------------------------------------------- */

.hy-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
  gap: var(--hy-sp-gap, 16px);
}

/* Which half the carousel takes. Mirrors .hy-imgtext--media-end. */
.hy-split--media-end .hy-split-slider {
  order: 1;
}

/* ---- The carousel half ---- */
.hy-split-slider {
  position: relative;
  overflow: hidden;
  border-radius: var(--hy-sp-radius, 12px);
  background: var(--color-bg-tertiary);
  min-width: 0;
}
.hy-split-slider .swiper,
.hy-split-slider .swiper-wrapper,
.hy-split-slider .swiper-slide {
  height: 100%;
}

.hy-split-slide {
  position: relative;
  display: flex;
  height: 100%;
  min-height: var(--hy-sp-min, 420px);
}

.hy-split-media {
  position: absolute;
  inset: 0;
}
.hy-split-media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.hy-split-noimg {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: var(--color-text-tertiary);
}

/* Top-weighted: the copy sits at the top, so the darkening does too. A flat
   wash over the whole photograph would flatten it. */
.hy-split-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--color-ink) 55%, transparent) 0%,
    color-mix(in srgb, var(--color-ink) 20%, transparent) 48%,
    color-mix(in srgb, var(--color-ink) 5%, transparent) 100%
  );
}

.hy-split-body {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4);
  align-self: flex-start;
  padding: clamp(var(--space-5), 3vw, var(--space-7));
  /* Clear of the controls on the lower edge. */
  padding-block-end: calc(clamp(var(--space-5), 3vw, var(--space-7)) + 44px);
}

.hy-split-eyebrow {
  margin: 0;
  font-family: var(--font-body-ar);
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.4;
  color: #fff;
}
html[lang="en"] .hy-split-eyebrow,
html[dir="ltr"] .hy-split-eyebrow {
  font-family: var(--font-body-en);
}

.hy-split-title {
  margin: 0;
  max-width: 14ch;
  font-family: var(--font-body-ar);
  font-size: clamp(1.75rem, 3.2vw, 2.75rem);
  font-weight: 800;
  line-height: 1.18;
  color: #fff;
}
html[lang="en"] .hy-split-title,
html[dir="ltr"] .hy-split-title {
  font-family: var(--font-body-en);
  letter-spacing: -0.02em;
}

.hy-split-text {
  margin: 0;
  max-width: 40ch;
  font-size: 1rem;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.86);
}
/* A `text` field arrives as HTML and is printed with `| safe`, so the copy is a
   `<p>` inside this element — its own margin does not reach it. */
.hy-split-text > :first-child {
  margin-block-start: 0;
}
.hy-split-text > :last-child {
  margin-block-end: 0;
}

/* ---- The tile grid ---- */
.hy-split-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--hy-sp-gap, 16px);
  min-width: 0;
}

.hy-split-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  /* Square, which is what makes the grid's height predictable and lets the
     slider half match it. */
  aspect-ratio: 1;
  padding: clamp(var(--space-4), 2vw, var(--space-6));
  border-radius: var(--hy-sp-radius, 12px);
  background: var(--color-bg-tertiary);
  text-align: center;
  transition: background var(--t-fast) var(--ease-soft);
}
.hy-split-tile:hover {
  background: var(--color-accent-light);
}

.hy-split-tile-media {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  /* The picture takes the room the copy does not, so a tile with a long title
     gives up image height rather than overflowing. */
  flex: 1 1 auto;
  min-height: 0;
  margin-block-end: var(--space-3);
}
.hy-split-tile-media img {
  max-width: 78%;
  max-height: 100%;
  /* `contain`, not `cover`: these are cut-outs on a flat ground, and `cover`
     would crop the legs off a stool. */
  object-fit: contain;
}

.hy-split-tile-title {
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.35;
  color: var(--color-text-primary);
}

.hy-split-tile-note {
  font-size: 0.9375rem;
  line-height: 1.4;
  color: var(--color-text-secondary);
}

.hy-split-tile-noimg {
  color: var(--color-text-tertiary);
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

@media (max-width: 900px) {
  .hy-split {
    grid-template-columns: 1fr;
  }
  /* The carousel leads: it is the reason to stop scrolling, and a grid of
     offers above it reads as a menu with nothing attached. Both selectors, so
     this outranks the desktop `--media-end` rule at one column — the same pair
     .hy-imgtext uses. */
  .hy-split-slider,
  .hy-split--media-end .hy-split-slider {
    order: -1;
  }
  .hy-split-slide {
    min-height: var(--hy-sp-min-sm, 380px);
  }
}

@media (max-width: 520px) {
  .hy-split-tile {
    /* Taller than square on a phone: at half a 360px screen a square tile
       leaves the picture about 90px. */
    aspect-ratio: 3 / 4;
  }
  .hy-split-tile-title {
    font-size: 0.9375rem;
  }
  .hy-split-tile-note {
    font-size: 0.875rem;
  }
}

/* --------------------------------------------------------------------------
   15.15  COUNTDOWN BANNER  ·  .hy-cd-*
   --------------------------------------------------------------------------
   One horizontal band, four zones, evenly spread:

     ┌────────────────────────────────────────────────────────────────┐
     │  Flash Sale now on!   28:02:23:59   Save on …   (Use Code: X)  │
     └────────────────────────────────────────────────────────────────┘

   ⚠️  THE CLOCK IS FORCED LTR — `.hy-cd-clock { direction: ltr }`.

   A bare `28 : 02 : 23 : 59` is a digital clock, and a digital clock reads
   largest-unit-first left-to-right in Arabic exactly as it does in English —
   nobody writes ٤٥:١٢ for 12:45. Letting the run flow RTL mirrors it to
   `59 : 23 : 02 : 28`, which is not "the Arabic way to write it", it is the
   wrong time. The unit labels are inside each cell, so they follow their own
   number and stay correct either way. Everything OUTSIDE the clock — the zone
   order, the headline, the CTA — flips normally with the document.

   ⚠️  ARABIC — the `letter-spacing` below is scoped to [dir="ltr"].
   -------------------------------------------------------------------------- */

.hy-cd {
  position: relative;
}

.hy-cd-band {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Wraps at ANY width, not only under the breakpoint below. Paired with the
     description's `min-width`, that is what stops the band degrading into four
     one-word lines: the moment the copy cannot hold its floor, the whole row
     breaks into the centred two-row layout instead of crushing one zone. */
  flex-wrap: wrap;
  gap: clamp(var(--space-4), 2vw, var(--space-6));
  width: 100%;
  padding: var(--space-6) clamp(var(--space-5), 4vw, 64px);
  border-radius: var(--hy-cd-radius, 8px);
  /* The band paints from the THEME tokens, which is what makes the section
     colour pickers reach it. It used to paint from four `--hy-cd-*` variables
     the widget printed unconditionally from its own colour settings — so
     ui/section-colors.njk emitted its overrides and nothing read them, and the
     🎨 block looked dead. The whole widget is one strip of one colour with one
     text colour (everything below is `color: inherit` plus opacity), so two
     tokens carry it.
     `--hy-sec-bg` is only ever written by the picker, so the fallback is the
     band's own yellow and switching the pickers off restores it. */
  background: var(--hy-sec-bg, #f0ee92);
  color: var(--color-text-primary, #141414);
}

/* Inset lines up with .hy-container's CONTENT box, not its border box — the
   same computation the hero uses, for the same reason. See 15.5. */
.hy-cd--inset .hy-cd-band {
  width: calc(100% - var(--container-px) * 2);
  max-width: calc(var(--container-max) - var(--container-px) * 2);
  margin-inline: auto;
}
@media (max-width: 640px) {
  .hy-cd--inset .hy-cd-band {
    width: calc(100% - var(--container-px-sm) * 2);
    max-width: calc(var(--container-max) - var(--container-px-sm) * 2);
  }
}

.hy-cd--full .hy-cd-band {
  width: 100%;
  max-width: none;
  margin-inline: 0;
  border-radius: 0;
}

/* ---- Zones ---- */
.hy-cd-headline {
  margin: 0;
  flex-shrink: 0;
  font-family: var(--font-body-ar);
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.4;
  color: inherit;
}
html[lang="en"] .hy-cd-headline,
html[dir="ltr"] .hy-cd-headline {
  font-family: var(--font-body-en);
}

/* The description is the ONLY flexible zone in the band, and it has to be.
   The headline, the clock and the CTA are all `flex-shrink: 0` — none of them
   can give up a pixel without becoming wrong (a clipped clock, a two-line
   button). So the description is what absorbs and releases the slack, and it
   pays for it by wrapping to a second line, which is what the design wants
   anyway.

   `min-width: 0` is what lets it: a flex item's default `min-width: auto`
   floors it at its longest word, and with the copy on one long line that floor
   pushed the CTA onto a row of its own on any viewport under about 1500px. */
/* Same reason as every other `text` container — see 15.5. */
.hy-cd-desc > :first-child {
  margin-block-start: 0;
}
.hy-cd-desc > :last-child {
  margin-block-end: 0;
}

.hy-cd-desc {
  margin: 0;
  /* Grows into whatever the other three leave, down to a 190px floor. Below
     that the band wraps (see `flex-wrap` above) rather than letting the copy
     collapse to one word a line. */
  flex: 1 1 200px;
  min-width: 190px;
  max-width: 34ch;
  font-size: 1rem;
  line-height: 1.5;
  color: inherit;
  opacity: 0.86;
}

/* ---- Clock ----
   `direction: ltr` is load-bearing, not cosmetic — see the section note. */
.hy-cd-clock {
  direction: ltr;
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-shrink: 0;
}

.hy-cd-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}

.hy-cd-num {
  font-family: var(--font-num);
  font-size: clamp(1.75rem, 3.6vw, 2.75rem);
  font-weight: 800;
  line-height: 1;
  /* Tabular figures, or the whole band jitters sideways once a second as the
     seconds digit changes width. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
  color: inherit;
}

.hy-cd-unit {
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.4;
  opacity: 0.66;
  white-space: nowrap;
}
html[lang="en"] .hy-cd-unit,
html[dir="ltr"] .hy-cd-unit {
  text-transform: uppercase;
  letter-spacing: var(--track-micro);
}

.hy-cd-sep {
  font-family: var(--font-num);
  font-size: clamp(1.25rem, 2.4vw, 1.75rem);
  font-weight: 500;
  line-height: 1;
  opacity: 0.42;
  /* Nudged up so the colon centres against the digits rather than sitting on
     their baseline, and lifted clear of the unit labels beneath. */
  align-self: center;
  margin-block-start: -0.15em;
}
.hy-cd-clock--labelled .hy-cd-sep {
  margin-block-start: -0.9em;
}

/* ---- CTA ---- */
.hy-cd-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  height: 56px;
  padding-inline: var(--space-6);
  border-radius: 999px;
  /* Same story as the band. The button pickers reach this through the class
     list in ui/section-colors.njk, which `.hy-cd-cta` is now part of — the
     partial matches buttons by NAME, and a bespoke pill that is in no family
     is invisible to it. Accent is the right default: it is the theme's action
     colour, and #141414 was only ever a hand-typed copy of it. */
  background: var(--color-accent, #141414);
  color: var(--color-text-on-accent, #ffffff);
  font-size: 1rem;
  font-weight: 700;
  white-space: nowrap;
  transition:
    transform var(--t-fast) var(--ease-out),
    opacity var(--t-fast) var(--ease-soft);
}
.hy-cd-cta:hover {
  opacity: 0.88;
  transform: translateY(-1px);
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

/* Four zones side by side need roughly 1300px of band before the description
   is down to two words a line — measured, not guessed: headline ~220 + clock
   ~330 + copy 200 + CTA ~275, plus three gaps, against a container that has
   already given up its own padding. Below that the row drops to the centred
   two-row layout, which is a deliberate shape rather than a wrap accident with
   the CTA stranded on a line of its own.

   This is well above the phone breakpoint on purpose. It is a LAYOUT limit,
   not a device one. */
@media (max-width: 1300px) {
  .hy-cd-band {
    flex-wrap: wrap;
    justify-content: center;
    text-align: center;
    gap: var(--space-4) var(--space-6);
  }
  .hy-cd-desc {
    flex: 0 1 auto;
    min-width: 0;
    max-width: 46ch;
  }
}

@media (max-width: 640px) {
  .hy-cd-band {
    flex-direction: column;
    padding: var(--space-6) var(--space-5);
    gap: var(--space-4);
  }
  .hy-cd-headline {
    font-size: 1rem;
  }
  .hy-cd-desc {
    min-width: 0;
    font-size: 0.9375rem;
  }
  .hy-cd-num {
    font-size: 2rem;
  }
  /* Full width so the tap target is the whole band edge to edge — a centred
     pill on a phone reads as decoration. */
  .hy-cd-cta {
    width: 100%;
    height: 52px;
  }
}

/* --------------------------------------------------------------------------
   15.18  ARTICLE CARD  ·  .hy-post-*
   --------------------------------------------------------------------------
   The shared card behind ui/article-card.njk and its placeholder, used by the
   blog index, a single blog, and an article's "related" row. Those pages lay
   it out with `.hy-blog-grid` (15.27) — this section only styles the card.

   ⚠️  The home-page `post-grid` widget was REMOVED, and with it `.hy-posts-head`,
   `.hy-posts-title` and `.hy-posts-grid`. What is left is only the card.

   `.hy-post` still declares the two-column form it had when post-grid used it,
   and every surviving caller overrides it with `.hy-post--stacked`. That is
   deliberate: the `max-width: 640px` rule at the end reinstates the compact
   row on phones, and it beats `--stacked` on source order. Flattening the base
   would silently restack every blog card on mobile — a change to widgets this
   deletion was not asked to touch.

   The post title is the SANS face while the section heading is the serif. That
   is deliberate: at this size a high-contrast serif set over two lines starts
   competing with the section heading above it, and the eye loses the hierarchy.
   -------------------------------------------------------------------------- */

.hy-post {
  display: grid;
  grid-template-columns: var(--post-img-w, 240px) minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
  min-width: 0;
}
.hy-post--stacked {
  grid-template-columns: 1fr;
}

.hy-post-media {
  position: relative;
  overflow: hidden;
  background: var(--color-bg-tertiary);
  aspect-ratio: var(--post-ratio, 4 / 3);
}
.hy-post-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: transform var(--t-image) var(--ease-out);
}
.hy-post:hover .hy-post-media img {
  transform: scale(1.04);
}
@media (prefers-reduced-motion: reduce) {
  .hy-post:hover .hy-post-media img {
    transform: none;
  }
}

.hy-post-body {
  min-width: 0;
}

.hy-post-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.8125rem;
  color: var(--color-text-secondary);
  margin-bottom: 10px;
}
.hy-post-meta svg {
  flex: none;
  opacity: 0.65;
}

.hy-post-title {
  font-family: var(--font-body-en);
  font-weight: 400;
  font-size: 1.0625rem;
  line-height: 1.5;
  color: var(--color-text-primary);
  margin: 0;
  transition: opacity var(--t-fast) var(--ease-soft);
}
html[dir="rtl"] .hy-post-title {
  font-family: var(--font-body-ar);
  font-size: 1.0625rem;
  line-height: 1.75;
}
.hy-post:hover .hy-post-title {
  opacity: 0.6;
}

.hy-post-excerpt {
  margin: 8px 0 0;
  font-size: 0.9375rem;
  line-height: 1.7;
  color: var(--color-text-secondary);
}

@media (max-width: 640px) {
  .hy-post {
    grid-template-columns: 130px minmax(0, 1fr);
    gap: var(--space-4);
  }
  .hy-post-title {
    font-size: 0.9375rem;
  }
  .hy-post-meta {
    font-size: 0.75rem;
    margin-bottom: 6px;
  }
}

15.19b  HONEYPOT
   --------------------------------------------------------------------------
   The bot trap, hidden without moving anything.

   ⚠️  NOT `left: -9999px`. That is the classic recipe and it is broken in RTL:
   in LTR the box overflows toward the inline-START edge, which browsers do not
   make scrollable, so nothing happens. In Arabic the inline-start edge is the
   RIGHT, so the same offset overflows into the direction that IS scrollable —
   measured on the footer: document width 11,387px on a 1388px viewport, a
   ten-thousand-pixel horizontal scrollbar on every Arabic page carrying a
   form, and a first paint showing an empty band because the viewport starts at
   the wrong end of the document.

   Clipping to a 1px box creates no overflow in either direction. The field
   stays in the DOM where a bot will find and fill it, and `aria-hidden` plus
   `tabindex="-1"` keep it away from people.
   -------------------------------------------------------------------------- */
.hy-hp {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* These four print a `text` field with `| safe`, so the element is a <div>
   holding the field's own <p> — the inner margins have to come off or every
   one of them gains a stray gap above and below its copy. */
.hy-blog-desc > :first-child,
.hy-collp-desc > :first-child,
.hy-shop-desc > :first-child,
.hy-404-text > :first-child {
  margin-block-start: 0;
}
.hy-blog-desc > :last-child,
.hy-collp-desc > :last-child,
.hy-shop-desc > :last-child,
.hy-404-text > :last-child {
  margin-block-end: 0;
}

/* A negative amount, isolated. `-` and `−` are NEUTRAL characters: on their
   own in an Arabic line they drift to the logical end, so "−50,000" renders as
   "50,000−". This is a class and not a `<bdi>` on purpose — main.js replaces
   the whole textContent of [data-cart-savings] on every cart update, which
   would delete a wrapper element but leaves the class untouched. */
.hy-neg {
  direction: ltr;
  unicode-bidi: isolate;
}

/* --------------------------------------------------------------------------
   15.20  FOOTER
   --------------------------------------------------------------------------
   A contact strip, a subscribe column beside the link menus, a utility row
   (language · payment badges · social), and a bottom bar.

   ── The lead column is not one of the link columns ──
   `grid-template-columns: var(--fo-lead) repeat(var(--fo-cols), minmax(0,1fr))`,
   with `--fo-cols` printed by the widget from its rendered block count. The
   previous version used `auto-fit, minmax(240px, 1fr)` and treated the
   subscribe form as just another column: with three link columns beside it the
   field was squeezed to 240px and the heading wrapped after two words.

   ⚠️  NOTHING HERE MAY DECLARE A `--color-*`. This section briefly carried a
   `.hy-footer--light` class that set the five `--color-footer-*` tokens, and a
   class on the footer beats `:root` — so the five footer colours in the Theme
   Editor stopped reaching a light footer entirely. The footer's palette comes
   from the global settings, and the per-footer pickers (printed on the
   element's own ID) override those. The two locals below are DERIVED from
   whichever palette won, so both follow.
   -------------------------------------------------------------------------- */

.hy-footer {
  background: var(--fo-bg, var(--color-footer));
  color: var(--color-footer-text);
}

/* ---- Contact strip ----
   The dividers are `border-inline-start` on every cell but the first, so they
   land between the right pairs in both directions with nothing to flip.
   `--fc-cols` is the number of FILLED cells, printed by the widget: a merchant
   who uses three of the four slots gets three equal cells, not three cells and
   a hole. The reset moves to `:nth-child(2n+1)` at the tablet breakpoint,
   because a cell that has wrapped to a new row must not keep the rule that
   separated it from the cell now above it.
   -------------------------------------------------------------------------- */
.hy-fcontact {
  display: grid;
  grid-template-columns: repeat(var(--fc-cols, 4), minmax(0, 1fr));
  row-gap: var(--space-5);
  padding-block: var(--space-6);
  border-block-end: 1px solid var(--color-footer-border);
}

.hy-fcontact-item {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  min-width: 0;
  padding-inline: var(--space-6);
  border-inline-start: 1px solid var(--color-footer-border);
}
.hy-fcontact-item:first-child {
  padding-inline-start: 0;
  border-inline-start: 0;
}
.hy-fcontact-item:last-child {
  padding-inline-end: 0;
}

.hy-fcontact-icon {
  flex: none;
  display: inline-flex;
  color: var(--color-footer-accent, var(--color-footer-heading));
  margin-block-start: 1px;
}

.hy-fcontact-body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  /* Cross-START, not `stretch`. The two lines carry `dir="auto"`, so a Latin
     phone number becomes an LTR box — and a STRETCHED LTR box resolves
     `text-align: start` to its own left, which parks the number on the wrong
     side of an Arabic cell. Shrink-to-fit boxes sit at the container's
     inline-start instead, which is the logical edge and flips with the
     document. */
  align-items: flex-start;
  gap: 5px;
}

.hy-fcontact-title {
  font-weight: 700;
  font-size: 1rem;
  line-height: 1.3;
  color: var(--color-footer-heading);
}

.hy-fcontact-text {
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--color-footer-text);
}
.hy-fcontact-text--link {
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color var(--t-fast) var(--ease-soft);
}
.hy-fcontact-text--link:hover {
  color: var(--color-footer-heading);
}

/* ---- Column band ---- */
.hy-footer-top {
  display: grid;
  grid-template-columns: var(--fo-lead, minmax(300px, 3.2fr)) repeat(
      var(--fo-cols, 3),
      minmax(0, 1fr)
    );
  gap: var(--space-7) var(--space-6);
  padding-block: var(--space-8);
}

.hy-footer-lead,
.hy-footer-col {
  min-width: 0;
}

.hy-footer-lead-title {
  font-family: var(--font-display-ar);
  font-weight: 700;
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  line-height: 1.35;
  color: var(--color-footer-heading);
  margin: 0 0 var(--space-3);
}
html[lang="en"] .hy-footer-lead-title,
html[dir="ltr"] .hy-footer-lead-title {
  font-family: var(--font-display-en);
  line-height: 1.15;
}

.hy-footer-heading {
  font-weight: 700;
  font-size: 1.0625rem;
  line-height: 1.3;
  color: var(--color-footer-heading);
  margin: 0 0 var(--space-4);
}

.hy-footer-text {
  font-size: 0.9375rem;
  line-height: 1.7;
  margin: 0 0 var(--space-5);
  max-width: 46ch;
}

.hy-footer-links {
  display: flex;
  flex-direction: column;
  gap: 13px;
}
.hy-footer-links a {
  font-size: 0.9375rem;
  color: var(--color-footer-text);
  width: fit-content;
  transition: color var(--t-fast) var(--ease-soft);
}
.hy-footer-links a:hover {
  color: var(--color-footer-heading);
}

/* ---- Subscribe field ----
   The button sits BESIDE the pill rather than inside it, so a real label fits.
   With no label the widget falls back to the arrow disc, which is what a
   narrow column can carry — see the note in the widget.
   -------------------------------------------------------------------------- */
.hy-footer-form {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  max-width: 440px;
}

.hy-footer-field {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  height: 54px;
  padding-inline: 24px;
  border-radius: 999px;
  /* Derived, not a literal: a tint of the footer's own text over its own
     background, so it lands light on a white footer and slightly-lighter-dark
     on a dark one with nothing to configure. */
  background: var(
    --fo-field,
    color-mix(
      in srgb,
      var(--color-footer-text) 12%,
      var(--fo-bg, var(--color-footer))
    )
  );
  transition: box-shadow var(--t-base) var(--ease-soft);
}
.hy-footer-field:focus-within {
  box-shadow: inset 0 0 0 1px var(--color-footer-heading);
}

.hy-footer-field input {
  flex: 1;
  min-width: 0;
  height: 100%;
  border: 0;
  background: transparent;
  outline: none;
  font-family: inherit;
  font-size: 0.9375rem;
  color: var(--color-footer-heading);
}
.hy-footer-field input::placeholder {
  color: var(--color-footer-text-muted);
}
.hy-footer-field input:-webkit-autofill {
  -webkit-text-fill-color: var(--color-footer-heading);
  transition: background-color 100000s;
}

.hy-footer-send {
  flex: none;
  width: 54px;
  height: 54px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 999px;
  background: var(--color-footer-heading);
  color: var(--fo-bg, var(--color-footer));
  cursor: pointer;
  transition: opacity var(--t-fast) var(--ease-soft);
}
.hy-footer-send--label {
  width: auto;
  padding-inline: 32px;
  font-family: var(--font-body-ar);
  font-size: 0.9375rem;
  font-weight: 700;
  line-height: 1;
}
html[lang="en"] .hy-footer-send--label,
html[dir="ltr"] .hy-footer-send--label {
  font-family: var(--font-body-en);
}
.hy-footer-send:hover {
  opacity: 0.82;
}
.hy-footer-send svg {
  transition: transform var(--t-base) var(--ease-out);
}
/* `translateX` is PHYSICAL — it does not flip with `dir`, so RTL needs its own
   sign or the arrow nudges backwards. */
.hy-footer-send:hover svg {
  transform: translateX(2px);
}
html[dir="rtl"] .hy-footer-send:hover svg {
  transform: translateX(-2px);
}

.hy-footer-hint {
  display: block;
  margin-top: var(--space-3);
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--color-footer-text-muted);
}

/* `text` fields arrive as block HTML, so these are <div>s with a <p> inside —
   the outer margins have to come off or the block gains a stray gap. */
.hy-footer-note,
.hy-footer-blurb {
  margin-block-start: var(--space-4);
  font-size: 0.8125rem;
  line-height: 1.7;
  color: var(--color-footer-text-muted);
  max-width: 46ch;
}
.hy-footer-note > :first-child,
.hy-footer-blurb > :first-child {
  margin-block-start: 0;
}
.hy-footer-note > :last-child,
.hy-footer-blurb > :last-child {
  margin-block-end: 0;
}
.hy-footer-note a {
  color: var(--color-footer-text);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.hy-footer-note a:hover {
  color: var(--color-footer-heading);
}

/* ---- Utility row ---- */
.hy-footer-util {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-5) var(--space-6);
  padding-block: 0 var(--space-7);
}

.hy-footer-util-start {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  min-width: 0;
}

/* ---- Social ---- */
.hy-footer-social {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}
.hy-footer-social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--color-footer-heading);
  transition:
    color var(--t-fast) var(--ease-soft),
    background-color var(--t-fast) var(--ease-soft),
    border-color var(--t-fast) var(--ease-soft),
    opacity var(--t-fast) var(--ease-soft);
}
.hy-footer-social--circle a {
  width: 46px;
  height: 46px;
  border-radius: 999px;
  border: 1px solid var(--color-footer-border);
}
.hy-footer-social--circle a:hover {
  background: var(--color-footer-heading);
  border-color: var(--color-footer-heading);
  color: var(--fo-bg, var(--color-footer));
}
.hy-footer-social--plain a:hover {
  opacity: 0.65;
}

/* ---- Language switcher ---- */
.hy-footer-lang {
  position: relative;
}
.hy-footer-lang-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: 0.9375rem;
  color: var(--color-footer-heading);
  cursor: pointer;
  transition: opacity var(--t-fast) var(--ease-soft);
}
.hy-footer-lang-btn:hover {
  opacity: 0.7;
}

.hy-footer-lang-drop {
  position: absolute;
  /* Opens UPWARD: this sits near the bottom of the document, and a downward
     drop would render off the page on a short viewport. */
  inset-block-end: calc(100% + 12px);
  inset-inline-start: 0;
  z-index: 20;
  min-width: 150px;
  padding: var(--space-2) 0;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-md, 0 8px 24px rgba(0, 0, 0, 0.1));
}
.hy-footer-lang-drop button {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 18px;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: 0.8125rem;
  color: var(--color-text-secondary);
  text-align: start;
  cursor: pointer;
}
.hy-footer-lang-drop button:hover:not(:disabled) {
  color: var(--color-text-primary);
}
.hy-footer-lang-drop button:disabled {
  color: var(--color-text-primary);
  cursor: default;
}

/* ---- Bottom bar ---- */
.hy-footer-bottom {
  /* Transparent by default — the hairline is what separates the bar, and a
     baked-in dark wash was wrong the moment the footer went light. The
     `bottom_bar_color` picker is there for a merchant who wants a band. */
  background: var(--fo-bar, transparent);
  border-block-start: 1px solid var(--color-footer-border);
}

.hy-footer-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4) var(--space-6);
  padding-block: var(--space-5);
  font-size: 0.875rem;
  color: var(--color-footer-text-muted);
}

.hy-footer-legal {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
}
.hy-footer-legal a {
  color: var(--color-footer-text);
  transition: color var(--t-fast) var(--ease-soft);
}
.hy-footer-legal a:hover {
  color: var(--color-footer-heading);
}

/* 1023, not 1024. `min-width: 1024px` rules (the shop sidebar, the header's
   desktop row) apply AT 1024 exactly, so an inclusive `max-width: 1024px` here
   put the footer in its tablet layout on a viewport the rest of the theme had
   already switched to desktop. iPad landscape is exactly 1024. */
@media (max-width: 1023px) {
  .hy-footer-top {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .hy-footer-lead {
    grid-column: 1 / -1;
  }
}

@media (max-width: 900px) {
  .hy-fcontact {
    --fc-cols: 2;
  }
  /* A cell that wrapped to a new row must lose the rule that separated it from
     the cell now sitting above it. */
  .hy-fcontact-item:nth-child(2n + 1) {
    padding-inline-start: 0;
    border-inline-start: 0;
  }
  .hy-fcontact-item:nth-child(2n) {
    padding-inline-end: 0;
  }
}

@media (max-width: 640px) {
  .hy-fcontact {
    --fc-cols: 1;
  }
  .hy-fcontact-item {
    padding-inline: 0;
    border-inline-start: 0;
  }
  .hy-footer-top {
    grid-template-columns: minmax(0, 1fr);
    padding-block: var(--space-7);
    gap: var(--space-6);
  }
  .hy-footer-util {
    flex-direction: column;
    align-items: flex-start;
  }
  .hy-footer-bar {
    justify-content: flex-start;
  }
}

/* --------------------------------------------------------------------------
   15.20.1  PAYMENT BADGES
   --------------------------------------------------------------------------
   Drawn marks from ui/payment-icons.njk. Every tile paints a white ground
   whatever the footer tone: brand marks are drawn for white and go muddy on a
   dark one.

   The wordmarks are forced `direction: ltr` — every one of these names is a
   Latin brand, and in an RTL document an unforced "stc pay" reorders to
   "pay stc". `letter-spacing` is safe HERE, and only here, because the content
   is always Latin; on Arabic it breaks letter joining.
   -------------------------------------------------------------------------- */

.hy-footer-pay {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.hy-footer-pay img {
  height: 30px;
  width: auto;
  border-radius: 6px;
}

.hy-pay {
  /* The badge is a mini LTR document. Without this the flex row inside
     ui/payment-icons.njk mirrors with the page and Apple Pay renders as
     "Pay " — the glyph after the word — while mada's chevrons jump behind
     their wordmark. Brand lockups do not mirror. */
  direction: ltr;
  height: 30px;
  min-width: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding-inline: 8px;
  border-radius: 6px;
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.1);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}

.hy-pay-row {
  display: inline-flex;
  align-items: center;
  gap: 3px;
}

/* The one tracking in this file that does NOT ride on --track-micro/--track-nav,
   and so is not zeroed by the RTL guard above. That is correct here: this
   element renders the wordmark half of a payment badge — "Pay", "mada",
   "VISA" — brand names in Latin script whatever language the store is in.
   Routing it through the Arabic guard would drop the tracking from a Latin
   wordmark on an Arabic storefront, which is the opposite of the intent. */
.hy-pay-word {
  /* Always Latin by construction, so `letter-spacing` is safe here — it is
     the one thing that must never reach Arabic, where it breaks letter
     joining. The per-brand tracking rides a custom property rather than a
     second declaration, so this stays the ONLY letter-spacing in the block. */
  direction: ltr;
  letter-spacing: var(--pay-ls, -0.01em);
  font-family: var(--font-body-en);
  font-weight: 800;
  font-size: 0.75rem;
  line-height: 1;
  color: #1a1a1a;
  white-space: nowrap;
}
.hy-pay-word i {
  font-style: normal;
}

.hy-pay--visa .hy-pay-word {
  color: #1a1f71;
  font-style: italic;
  font-size: 0.8125rem;
  --pay-ls: 0.02em;
}
.hy-pay--amex {
  background: #006fcf;
  border-color: #006fcf;
}
.hy-pay--amex .hy-pay-word {
  color: #ffffff;
  font-size: 0.6875rem;
  --pay-ls: 0.05em;
}
.hy-pay--paypal .hy-pay-word b {
  color: #003087;
  font-weight: 800;
}
.hy-pay--paypal .hy-pay-word i {
  color: #009cde;
  font-style: italic;
  font-weight: 800;
}
.hy-pay--stcpay .hy-pay-word {
  color: #4f008c;
}
.hy-pay--stcpay .hy-pay-word i {
  font-weight: 500;
  opacity: 0.85;
}
.hy-pay--tabby .hy-pay-word {
  color: #1f9e83;
}
.hy-pay--tamara .hy-pay-word {
  color: #1f1f1f;
}
.hy-pay--mada .hy-pay-word {
  color: #1a1a1a;
  font-size: 0.6875rem;
}
.hy-pay--applepay {
  color: #000000;
}
.hy-pay--applepay .hy-pay-word {
  color: #000000;
  font-weight: 600;
}
.hy-pay--cod {
  color: #1f7a4d;
}

/* --------------------------------------------------------------------------
   15.21  SHOP / COLLECTION PAGE
   --------------------------------------------------------------------------
   The browsing chrome: breadcrumb, title, a filter/sort bar, removable chips,
   and the grid the `shop-products` widget mounts into.

   Filters live in a drawer on every screen size, not a sidebar on desktop.
   A permanent sidebar costs ~280px of the viewport on the one page where the
   photographs are the product — three columns of clothing beat four columns of
   checkboxes. The drawer also means one implementation instead of two.
   -------------------------------------------------------------------------- */

/* ---- Collection cover ----
   Ratio-locked so the band reserves its height before the image arrives — the
   file carries no intrinsic dimensions in Qumra's payload, so `aspect-ratio` on
   the frame is the only thing standing between this and a layout shift.
   `cover`, not `contain`: a collection cover is a photograph chosen to fill a
   band, and letterboxing it would leave grey bars at the top of the page. */
.hy-shop-cover {
  margin-block-start: var(--space-6);
  overflow: hidden;
  border-radius: var(--radius-media);
  background: var(--color-bg-tertiary);
  aspect-ratio: 21 / 9;
}
.hy-shop-cover img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
@media (max-width: 767px) {
  /* 21:9 across a phone is a letterbox slit — 16:9 keeps the subject readable
     without taking the whole first screen. */
  .hy-shop-cover {
    aspect-ratio: 16 / 9;
  }
}

/* With a cover above it the head no longer needs its own top air. */
.hy-shop-head {
  padding-block: var(--space-7) var(--space-6);
}
.hy-shop-cover + .hy-shop-head {
  padding-block-start: var(--space-6);
}

.hy-crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: 0.8125rem;
  color: var(--color-text-tertiary);
  margin-bottom: var(--space-5);
}
.hy-crumbs a {
  color: var(--color-text-secondary);
}
.hy-crumbs a:hover {
  color: var(--color-text-primary);
}
.hy-crumbs svg {
  opacity: 0.5;
}

.hy-shop-title {
  font-family: var(--font-display-en);
  font-weight: 400;
  font-size: clamp(2rem, 4.4vw, 3.25rem);
  line-height: 1.05;
  color: var(--color-text-primary);
  margin: 0;
}
html[dir="rtl"] .hy-shop-title {
  font-family: var(--font-display-ar);
  font-size: clamp(1.75rem, 3.8vw, 2.75rem);
  line-height: 1.35;
}

.hy-shop-desc {
  margin: var(--space-4) 0 0;
  max-width: 62ch;
  font-size: 1rem;
  line-height: 1.7;
  color: var(--color-text-secondary);
}

/* ---- Collection rail ---- */
.hy-shop-rail {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  padding-block: var(--space-5);
  scrollbar-width: none;
}
.hy-shop-rail::-webkit-scrollbar {
  display: none;
}

.hy-chip {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 38px;
  padding-inline: 18px;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: transparent;
  font-family: inherit;
  font-size: 0.875rem;
  color: var(--color-text-secondary);
  white-space: nowrap;
  cursor: pointer;
  transition:
    border-color var(--t-fast) var(--ease-soft),
    color var(--t-fast) var(--ease-soft);
}
.hy-chip:hover {
  border-color: var(--color-border-hover);
  color: var(--color-text-primary);
}
.hy-chip.is-active {
  border-color: var(--color-text-primary);
  color: var(--color-text-primary);
}
.hy-chip--removable {
  border-style: dashed;
}
.hy-chip--removable svg {
  opacity: 0.6;
}

/* ---- Toolbar ---- */
.hy-shop-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block: var(--space-4);
  border-block: 1px solid var(--color-border);
}

.hy-shop-count {
  font-size: 0.875rem;
  color: var(--color-text-secondary);
}
.hy-shop-count .hy-num {
  color: var(--color-text-primary);
}

.hy-shop-tools {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.hy-shop-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: 0.875rem;
  color: var(--color-text-primary);
  cursor: pointer;
  transition: opacity var(--t-fast) var(--ease-soft);
}
.hy-shop-btn:hover {
  opacity: 0.6;
}
.hy-shop-btn .count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding-inline: 5px;
  border-radius: 999px;
  background: var(--color-ink);
  color: var(--color-text-on-ink);
  font-family: var(--font-num);
  font-size: 0.625rem;
}

/* ---- Sort menu ---- */
.hy-sort {
  position: relative;
}
.hy-sort-menu {
  position: absolute;
  inset-block-start: calc(100% + 12px);
  inset-inline-end: 0;
  z-index: 30;
  min-width: 220px;
  padding: var(--space-2) 0;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
}
.hy-sort-menu button {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 18px;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: 0.875rem;
  color: var(--color-text-secondary);
  text-align: start;
  cursor: pointer;
  transition: color var(--t-fast) var(--ease-soft);
}
.hy-sort-menu button:hover {
  color: var(--color-text-primary);
}
.hy-sort-menu button.is-active {
  color: var(--color-text-primary);
}

/* ---- Body: sidebar + main ----
   `min-width: 0` on the main column is not optional. It holds the product
   grid, and a grid item defaults to `min-width: auto` — so a wide child can
   push the column past its share and crush the sidebar, which is exactly the
   failure the editorial section hit with a slider inside it. */
.hy-shop-body {
  display: grid;
  grid-template-columns: 1fr;
}
.hy-shop-main {
  min-width: 0;
}

.hy-shop-aside {
  display: none;
}

/* Filters off → no <aside> in the DOM, so the sidebar track must go with it or
   the main column inherits a 250px width. See the note in shop-page/widget.njk. */
.hy-shop-body--nofilter {
  grid-template-columns: minmax(0, 1fr) !important;
}

@media (min-width: 1024px) {
  .hy-shop-body {
    grid-template-columns: var(--shop-aside, 250px) minmax(0, 1fr);
    gap: var(--space-7);
    align-items: start;
  }
  .hy-shop-aside {
    display: block;
    min-width: 0;
    position: sticky;
    /* Clears the sticky header so the panel never slides under it. */
    top: calc(var(--header-h) + var(--space-5));
    max-height: calc(100svh - var(--header-h) - var(--space-7));
    overflow-y: auto;
    padding-block: var(--space-4) var(--space-6);
  }
  /* The drawer trigger is for the widths that have no sidebar. */
  .hy-shop-filter-btn {
    display: none;
  }
}

.hy-shop-aside-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--space-5);
}

.hy-shop-clear {
  border: 0;
  background: none;
  padding: 0;
  font-family: inherit;
  font-size: 0.8125rem;
  color: var(--color-text-secondary);
  cursor: pointer;
  border-bottom: 1px solid var(--color-border-strong);
  transition: color var(--t-fast) var(--ease-soft);
}
.hy-shop-clear:hover {
  color: var(--color-text-primary);
}

.hy-filter-count {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  color: var(--color-text-tertiary);
}

/* ---- Column switcher ---- */
.hy-cols-switch {
  display: none;
  align-items: center;
  gap: 4px;
}
@media (min-width: 1024px) {
  .hy-cols-switch {
    display: inline-flex;
  }
}
.hy-cols-switch button {
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: none;
  font-family: var(--font-num);
  font-size: 0.8125rem;
  color: var(--color-text-tertiary);
  cursor: pointer;
  border-bottom: 1px solid transparent;
  transition:
    color var(--t-fast) var(--ease-soft),
    border-color var(--t-fast) var(--ease-soft);
}
.hy-cols-switch button:hover {
  color: var(--color-text-primary);
}
.hy-cols-switch button.is-active {
  color: var(--color-text-primary);
  border-bottom-color: var(--color-text-primary);
}

/* ---- Grid ---- */
.hy-shop-grid {
  display: grid;
  grid-template-columns: repeat(var(--shop-cols-sm, 2), minmax(0, 1fr));
  gap: 2px;
  padding-block: var(--space-6);
  transition: opacity var(--t-base) var(--ease-soft);
}
@media (min-width: 768px) {
  .hy-shop-grid {
    grid-template-columns: repeat(var(--shop-cols-md, 3), minmax(0, 1fr));
  }
}
@media (min-width: 1024px) {
  .hy-shop-grid {
    grid-template-columns: repeat(var(--shop-cols-lg, 3), minmax(0, 1fr));
  }
}
@media (min-width: 1280px) {
  .hy-shop-grid {
    grid-template-columns: repeat(var(--shop-cols, 4), minmax(0, 1fr));
  }
}

.hy-shop-stage {
  position: relative;
  min-width: 0;
}
.hy-shop-stage.is-loading .hy-shop-grid {
  opacity: 0.35;
  pointer-events: none;
}

.hy-shop-spinner {
  position: absolute;
  inset-block-start: 120px;
  inset-inline-start: 50%;
  transform: translateX(-50%);
  z-index: 3;
}

/* ---- Filter drawer body ---- */
.hy-filter-group + .hy-filter-group {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-border);
}

.hy-filter-title {
  font-family: var(--font-body-en);
  font-size: 0.6875rem;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
  margin: 0 0 var(--space-4);
  letter-spacing: var(--track-micro);
}
html[dir="rtl"] .hy-filter-title {
  font-family: var(--font-body-ar);
  font-size: 0.8125rem;
  text-transform: none;
}

/* ---- Options as pills ----
   A checkbox column reads as a form; a wrap of pills reads as a set of tags
   you pick from, which is what a filter is. It also fits four short values on
   one line where a checkbox column would spend four rows on them. The real
   input is still a checkbox — visually hidden, not removed, so the control
   stays keyboard reachable and announces itself as a checkbox. */
.hy-filter-opts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.hy-filter-opt {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 9px 14px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: var(--color-bg-tertiary);
  font-size: 0.875rem;
  line-height: 1;
  color: var(--color-text-secondary);
  cursor: pointer;
  transition:
    border-color var(--t-fast) var(--ease-soft),
    color var(--t-fast) var(--ease-soft),
    background-color var(--t-fast) var(--ease-soft);
}
.hy-filter-opt:hover {
  color: var(--color-text-primary);
}
.hy-filter-opt:has(input:checked) {
  border-color: var(--color-text-primary);
  background: var(--color-surface);
  color: var(--color-text-primary);
}
/* :has() is wide but not universal; the class mirror keeps older engines right. */
.hy-filter-opt.is-on {
  border-color: var(--color-text-primary);
  background: var(--color-surface);
  color: var(--color-text-primary);
}
.hy-filter-opt:focus-within {
  box-shadow: var(--shadow-focus);
}

.hy-filter-opt input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- Group header ---- */
.hy-filter-head {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 0 0 var(--space-4);
  border: 0;
  background: none;
  cursor: pointer;
}
.hy-filter-head svg {
  transition: transform var(--t-base) var(--ease-out);
}
.hy-filter-head[aria-expanded="false"] svg {
  transform: rotate(180deg);
}

.hy-filter-more {
  margin-top: 10px;
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: 0.8125rem;
  color: var(--color-text-secondary);
  cursor: pointer;
  border-bottom: 1px solid var(--color-border-strong);
  transition: color var(--t-fast) var(--ease-soft);
}
.hy-filter-more:hover {
  color: var(--color-text-primary);
}

.hy-filter-price {
  display: flex;
  align-items: center;
  gap: 10px;
}
.hy-filter-price input {
  flex: 1;
  min-width: 0;
  height: 42px;
  padding-inline: 14px;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-input);
  background: var(--color-surface);
  font-family: var(--font-num);
  font-size: 0.875rem;
  color: var(--color-text-primary);
  outline: none;
  transition: border-color var(--t-fast) var(--ease-soft);
}
.hy-filter-price input:focus {
  border-color: var(--color-text-primary);
}
.hy-filter-price span {
  color: var(--color-text-tertiary);
}

@media (max-width: 640px) {
  .hy-shop-head {
    padding-block: var(--space-6) var(--space-5);
  }
  .hy-shop-bar {
    padding-block: var(--space-3);
  }
  .hy-shop-tools {
    gap: var(--space-3);
  }
}

/* --------------------------------------------------------------------------
   15.22  PAGINATION
   -------------------------------------------------------------------------- */

.hy-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px;
  padding-block: var(--space-7) var(--space-8);
}

.hy-pager-btn {
  min-width: 40px;
  height: 40px;
  padding-inline: 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  background: none;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: 0.875rem;
  color: var(--color-text-secondary);
  cursor: pointer;
  transition:
    border-color var(--t-fast) var(--ease-soft),
    color var(--t-fast) var(--ease-soft);
}
.hy-pager-btn:hover:not(:disabled) {
  color: var(--color-text-primary);
  border-color: var(--color-border);
}
.hy-pager-btn.is-active {
  color: var(--color-text-primary);
  border-color: var(--color-text-primary);
}
.hy-pager-btn:disabled {
  opacity: 0.3;
  cursor: default;
}

.hy-pager-gap {
  color: var(--color-text-tertiary);
  padding-inline: 4px;
}

/* ---- Keyword filter (inputType: search_text) ---- */
/* The newsletter's validation line. Deal colour, like every other message in
   this theme that reports something went wrong. `flex-basis: 100%` so it drops
   to its own row under the field and the button rather than squeezing them. */
/* A checkout link that must not be followed. Kept visible and readable — a
   button that disappears reads as a broken page; one that is plainly off, with
   a line under it saying why, reads as an instruction. */
.hy-btn.is-blocked {
  opacity: 0.45;
  cursor: not-allowed;
}
.hy-cart-blocked {
  margin: var(--space-2) 0 0;
  font-size: 0.75rem;
  line-height: 1.5;
  text-align: center;
  color: var(--color-deal);
}

.hy-footer-err {
  flex-basis: 100%;
  margin: var(--space-2) 0 0;
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--color-deal);
}

/* The correction notice under the price pair. Deal colour: it reports that
   what the shopper typed was changed, which is the one thing in a filter panel
   that has to interrupt. */
.hy-filter-note {
  margin: var(--space-2) 0 0;
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--color-deal);
}

.hy-filter-text {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-inline: 14px;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-input);
  background: var(--color-surface);
  transition: border-color var(--t-fast) var(--ease-soft);
}
.hy-filter-text:focus-within {
  border-color: var(--color-text-primary);
}
.hy-filter-text input {
  flex: 1;
  min-width: 0;
  height: 42px;
  border: 0;
  background: transparent;
  outline: none;
  font-family: inherit;
  font-size: 0.875rem;
  color: var(--color-text-primary);
}
.hy-filter-text input::-webkit-search-cancel-button {
  -webkit-appearance: none;
}
.hy-filter-text svg {
  flex: none;
  color: var(--color-text-tertiary);
}

/* --------------------------------------------------------------------------
   15.23  PRODUCT PAGE
   --------------------------------------------------------------------------
   Gallery on one side, the buy column on the other. WHICHEVER COLUMN IS
   SHORTER STICKS, and it selects itself: both carry `position: sticky`, and a
   sticky item gets travel equal to its grid area minus its own height. The
   taller column defines the row, so its travel is zero and its sticky is inert;
   the shorter one has the whole difference to move through.

   That self-selection is the fix for a real bug. Only the buy column used to be
   sticky, on the assumption that the gallery is the long one — five or six
   full-length shots. But the thumb strip scrolls INSIDE the stage precisely so
   the gallery can never outgrow the buy column (see the note on .hy-pdp-thumbs),
   which made the gallery permanently the shorter of the two — so the buy
   column's sticky never had any travel and never once fired. With a long
   description the buy column runs thousands of pixels and the gallery just sat
   at the top of the row with white space under it for the rest of the scroll.

   The stage is capped to the viewport as well: a sticky box taller than the
   screen pins with its lower half permanently out of reach.
   -------------------------------------------------------------------------- */

.hy-pdp-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6);
  padding-block: var(--space-6) var(--space-8);
}
@media (min-width: 900px) {
  .hy-pdp-grid {
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
    gap: var(--space-8);
    align-items: start;
  }
}

/* ---- Gallery: one stage, a thumb strip on its end edge ---- */
.hy-pdp-stage {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2px;
  min-width: 0;
}
@media (min-width: 640px) {
  .hy-pdp-stage {
    grid-template-columns: minmax(0, 1fr) var(--pdp-thumb-w, 96px);
  }
}
@media (min-width: 900px) {
  .hy-pdp-stage {
    position: sticky;
    top: calc(var(--header-h) + var(--space-5));
  }
}

.hy-pdp-main {
  position: relative;
  min-width: 0;
}

/* The strip matches the stage's height and scrolls inside it, so a product with
   nine shots does not make the gallery column taller than the buy column
   beside it — which is what breaks the sticky behaviour. */
.hy-pdp-thumbs {
  display: none;
  flex-direction: column;
  gap: 2px;
  overflow-y: auto;
  max-height: 100%;
  scrollbar-width: none;
}
.hy-pdp-thumbs::-webkit-scrollbar {
  display: none;
}
@media (min-width: 640px) {
  .hy-pdp-thumbs {
    display: flex;
  }
}

.hy-pdp-thumb {
  position: relative;
  flex: none;
  padding: 0;
  border: 1px solid transparent;
  background: var(--color-bg-tertiary);
  aspect-ratio: var(--pdp-ratio, 3 / 4);
  cursor: pointer;
  overflow: hidden;
  transition:
    border-color var(--t-fast) var(--ease-soft),
    opacity var(--t-fast) var(--ease-soft);
  opacity: 0.72;
}
.hy-pdp-thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.hy-pdp-thumb:hover {
  opacity: 1;
}
.hy-pdp-thumb.is-active {
  opacity: 1;
  border-color: var(--color-text-primary);
}

.hy-pdp-shot {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--color-bg-tertiary);
  aspect-ratio: var(--pdp-ratio, 3 / 4);
  cursor: zoom-in;
}
/* The ratio decides the height from the column width, and at 3/4 in a 1.25fr
   column that lands near 900px — taller than the usable height of most laptop
   screens. A sticky box taller than the viewport pins with its bottom below the
   fold and no way to scroll to it, so the height is clamped once the gallery
   starts sticking. The shot's <img> is `object-fit: contain` on the same
   tertiary ground it already uses, so the clamp letterboxes; it never crops. */
@media (min-width: 900px) {
  .hy-pdp-shot {
    max-height: calc(100vh - var(--header-h) - var(--space-5) * 2);
  }
}
.hy-pdp-shot img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.hy-pdp-noimg {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-border-strong);
}

/* ---- Buy column ---- */
.hy-pdp-buy {
  min-width: 0;
}
@media (min-width: 900px) {
  .hy-pdp-buy {
    position: sticky;
    top: calc(var(--header-h) + var(--space-5));
    padding-block: var(--space-4);
  }
}

.hy-pdp-brand {
  display: block;
  font-family: var(--font-body-en);
  font-size: 0.6875rem;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
  margin-bottom: var(--space-3);
  letter-spacing: var(--track-micro);
}
html[dir="rtl"] .hy-pdp-brand {
  font-family: var(--font-body-ar);
  font-size: 0.8125rem;
  text-transform: none;
}

/* ---- Rating summary ----
   ONE element, not five. `background-clip: text` paints the glyph row with a
   hard-stop gradient at `--pct`, so 4.2 out of 5 fills the fifth star a fifth
   of the way — a set of half-star images could only ever land on 4 or 4.5.

   `direction: ltr` on the star row, and only on it: the fill runs from the
   START of the line, so if the row flipped with the page a 4.2 would read as
   0.8. The number and the count beside it keep the page's own direction. */
.hy-pdp-rating {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-block-start: var(--space-3);
  font-size: 0.8125rem;
  color: var(--color-text-secondary);
}
.hy-stars {
  direction: ltr;
  font-size: 0.9375rem;
  letter-spacing: 1px;
  line-height: 1;
  background: linear-gradient(
    90deg,
    var(--color-warning) var(--pct, 0%),
    var(--color-border-strong) var(--pct, 0%)
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.hy-pdp-rating-num {
  font-family: var(--font-num);
  font-weight: 600;
  color: var(--color-text-primary);
}

.hy-pdp-title {
  font-family: var(--font-display-en);
  font-weight: 400;
  /* `--hy-pdp-title-scale` is the Theme Editor's size control, set on the
     section root. It MULTIPLIES the clamp rather than replacing it, so the
     responsive range survives and the RTL clamp below stays proportionally
     smaller — Arabic reads larger than the Latin display face at the same
     nominal size, which is why the two clamps differ in the first place. */
  font-size: calc(clamp(1.75rem, 3.2vw, 2.5rem) * var(--hy-pdp-title-scale, 1));
  line-height: 1.12;
  color: var(--color-text-primary);
  margin: 0 0 var(--space-4);
}
html[dir="rtl"] .hy-pdp-title {
  font-family: var(--font-display-ar);
  font-size: calc(
    clamp(1.5rem, 2.8vw, 2.125rem) * var(--hy-pdp-title-scale, 1)
  );
  line-height: 1.45;
}

.hy-pdp-prices {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}
.hy-pdp-price {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: 1.5rem;
  color: var(--color-text-primary);
}
.hy-pdp-price--sale {
  color: var(--color-deal);
}
.hy-pdp-was {
  font-family: var(--font-num);
  font-size: 1.0625rem;
  color: var(--color-text-tertiary);
  text-decoration: line-through;
}
.hy-pdp-save {
  font-size: 0.8125rem;
  color: var(--color-deal);
}

/* ---- Options ---- */
.hy-pdp-opt + .hy-pdp-opt {
  margin-top: var(--space-5);
}

.hy-pdp-opt-head {
  display: flex;
  align-items: baseline;
  gap: 7px;
  margin-bottom: 10px;
}
.hy-pdp-opt-name {
  font-family: var(--font-body-en);
  font-size: 0.6875rem;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
  letter-spacing: var(--track-micro);
}
html[dir="rtl"] .hy-pdp-opt-name {
  font-family: var(--font-body-ar);
  font-size: 0.8125rem;
  text-transform: none;
}
.hy-pdp-opt-value {
  font-size: 0.875rem;
  color: var(--color-text-primary);
}

.hy-pdp-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.hy-pdp-swatch {
  min-width: 46px;
  height: 42px;
  padding-inline: 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  font-family: inherit;
  font-size: 0.875rem;
  color: var(--color-text-secondary);
  cursor: pointer;
  transition:
    border-color var(--t-fast) var(--ease-soft),
    color var(--t-fast) var(--ease-soft);
}
.hy-pdp-swatch:hover {
  border-color: var(--color-border-hover);
  color: var(--color-text-primary);
}
.hy-pdp-swatch.is-active {
  border-color: var(--color-text-primary);
  color: var(--color-text-primary);
}

/* An option value that carries its own photograph shows it. For colour and
   pattern that is the only honest swatch — a name like "Gray blue" tells a
   shopper far less than the fabric does. */
.hy-pdp-swatch--img {
  width: 62px;
  height: 78px;
  min-width: 0;
  padding: 3px;
}
.hy-pdp-swatch--img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ---- Stock ---- */
.hy-pdp-stockwrap {
  margin-top: var(--space-5);
}

.hy-pdp-bar-track {
  height: 3px;
  margin-top: 10px;
  background: var(--color-border);
  overflow: hidden;
}
.hy-pdp-bar-fill {
  display: block;
  height: 100%;
  background: var(--color-text-primary);
  transition: width var(--t-base) var(--ease-out);
}

/* ---- Quantity + buy ---- */
.hy-pdp-actions {
  display: flex;
  gap: 10px;
  margin-top: var(--space-6);
}

.hy-qty {
  display: inline-flex;
  align-items: center;
  flex: none;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-btn);
}
.hy-qty button {
  width: 44px;
  height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: none;
  color: var(--color-text-primary);
  cursor: pointer;
  transition: opacity var(--t-fast) var(--ease-soft);
}
.hy-qty button:hover:not(:disabled) {
  opacity: 0.55;
}
.hy-qty button:disabled {
  opacity: 0.25;
  cursor: default;
}
.hy-qty span {
  min-width: 34px;
  text-align: center;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: 0.9375rem;
}

.hy-pdp-actions .hy-btn {
  flex: 1;
}

.hy-pdp-meta {
  margin-top: var(--space-5);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  font-size: 0.8125rem;
  color: var(--color-text-tertiary);
}

/* ---- Stock ---- */
.hy-pdp-stock {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.875rem;
}
.hy-pdp-stock i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  display: block;
}
.hy-pdp-stock--in {
  color: var(--color-success);
}
.hy-pdp-stock--low {
  color: var(--color-warning);
}
.hy-pdp-stock--out {
  color: var(--color-deal);
}

/* ---- Panels (accordion) ---- */
.hy-pdp-panels {
  margin-top: var(--space-7);
  border-top: 1px solid var(--color-border);
}
.hy-pdp-panel {
  border-bottom: 1px solid var(--color-border);
}

.hy-pdp-panel-head {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: 18px 0;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: 0.9375rem;
  color: var(--color-text-primary);
  text-align: start;
  cursor: pointer;
}
.hy-pdp-panel-head svg {
  flex: none;
  transition: transform var(--t-base) var(--ease-out);
}
.hy-pdp-panel-head[aria-expanded="true"] svg {
  transform: rotate(180deg);
}

.hy-pdp-panel-body {
  padding-bottom: 20px;
  font-size: 0.9375rem;
  line-height: 1.8;
  color: var(--color-text-secondary);
  /* Merchant-authored HTML, same as `.taqa-prose` — a pasted URL or an
     unbroken product code has no break opportunity and widens the page. */
  overflow-wrap: anywhere;
}
.hy-pdp-panel-body > * + * {
  margin-top: 0.9em;
}
.hy-pdp-panel-body img {
  max-width: 100%;
  height: auto;
}

/* ---- Sticky buy bar ----
   `position: fixed`, so it is out of flow and sits ON the page. Without the
   reservation below it covers the last row of "you may also like" and the top
   of the footer — and on a short product it can land over the add-to-cart
   button it duplicates. The variable is what the page reserves; the bar itself
   is measured, not guessed: 12px padding × 2 + a 44px control = 68px. */
body:has(.hy-pdp-bar) {
  padding-block-end: var(--hy-buybar-h);
}
.hy-pdp-bar {
  position: fixed;
  inset-block-end: 0;
  inset-inline: 0;
  z-index: var(--z-buybar);
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 12px var(--container-px);
  background: var(--color-surface);
  border-block-start: 1px solid var(--color-border);
}
.hy-pdp-bar-info {
  flex: 1;
  min-width: 0;
}
.hy-pdp-bar-title {
  display: block;
  font-size: 0.875rem;
  color: var(--color-text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The primary mobile CTA of the whole theme. `.hy-btn--sm` is 40px, which is
   below the 44px touch minimum — and this is the one control that must never
   be. Overridden here rather than by changing the modifier, which is correct
   at 40px everywhere else it is used. */
.hy-pdp-bar .hy-btn {
  flex: none;
  min-height: 44px;
}

@media (max-width: 640px) {
  .hy-pdp-actions {
    flex-wrap: wrap;
  }
  .hy-pdp-actions .hy-btn {
    flex: 1 1 100%;
  }
  .hy-pdp-bar-info {
    display: none;
  }
  .hy-pdp-bar .hy-btn {
    flex: 1;
  }
}

/* ==========================================================================
   15.24  QUICK VIEW  ·  .hy-qv-*
   --------------------------------------------------------------------------
   The modal is a reduced product page, so it BORROWS the page's own classes
   for everything that carries meaning — .hy-pdp-brand, .hy-pdp-price(-was),
   .hy-pdp-opt, .hy-pdp-swatch, .hy-pdp-stock, .hy-qty, .hy-btn. Only the
   layout around them is quick-view specific. Duplicating those rules here is
   how a modal ends up looking like a different shop by the third redesign.

   The panel it sits in (.hy-qv-panel) is capped at 90dvh and scrolls, so
   nothing in here may assume unlimited height.
   ========================================================================== */

/* ---- the modal shell (owned here, not by the retired .taqa-modal rules) ---- */
.hy-qv-modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  pointer-events: none; /* the overlay behind it takes the clicks */
}
/* The PANEL does not scroll — .hy-qv-pad inside it does. An absolutely
   positioned close button inside a scrolling box scrolls away with the
   content, so on a tall product the X was gone the moment you looked at the
   size chart. Pinning it to a non-scrolling panel keeps it where it was. */
.hy-qv-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 940px;
  /* Two declarations, and the order matters: a browser without `dvh` keeps the
     `vh` line and one with it takes the second. NOT interchangeable on a
     phone — `vh` resolves against the LARGEST viewport (browser chrome
     retracted), so on iOS Safari with the toolbars showing a 90vh panel is
     taller than the visible area and its bottom edge, buy button included,
     sits behind the chrome. `dvh` tracks the viewport that is actually there. */
  max-height: 90vh;
  max-height: 90dvh;
  overflow: hidden;
  background: var(--color-surface);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-xl);
  pointer-events: auto;
}

/* The gallery runs to the panel edge, so in RTL this button sits ON the
   photograph — a bare glyph would disappear into a light dress. It carries its
   own surface and border so it reads against anything underneath. */
.hy-qv-close {
  position: absolute;
  inset-block-start: 10px;
  inset-inline-end: 10px;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-btn); /* sharp, like every other control here */
  background: var(--color-surface);
  color: var(--color-text-primary);
  cursor: pointer;
  transition:
    background var(--t-fast) var(--ease-soft),
    border-color var(--t-fast) var(--ease-soft),
    color var(--t-fast) var(--ease-soft);
}
.hy-qv-close:hover {
  background: var(--color-text-primary);
  border-color: var(--color-text-primary);
  color: var(--color-surface);
}
.hy-qv-close:focus-visible {
  outline: 2px solid var(--color-text-primary);
  outline-offset: 2px;
}

.hy-qv-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  padding-block: var(--space-9, 72px);
}

.hy-qv {
  /* The close button's own width plus its 10px inset from the panel edge.
     One number, declared once: section 16 grows the button on a touch device
     and overrides this alongside it, so the two can never disagree. */
  --hy-qv-close-gutter: 44px;

  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
}
@media (min-width: 768px) {
  .hy-qv {
    grid-template-columns: minmax(0, 0.95fr) minmax(0, 1fr);
    gap: var(--space-6);
  }

  /* ---- Clearance for the close button, and ONLY where it collides ----
     The button is absolute at the panel's top inline-end corner. In two
     columns that corner is over the INFO column's first lines, so the brand
     and the title — and nothing else — step aside for it. Everything below
     starts lower than the button ends and keeps the full width.

     `inset-inline-end` resolves per direction, so this is correct in both:
     LTR puts the button top-right over a right-hand info column, RTL puts it
     top-left over a left-hand one. Same corner, same column, either way.

     Single column needs none of this: the gallery is the first row there, so
     the button sits on the photograph — which is what it was drawn to do, and
     why it carries its own surface and border. */
  .hy-pdp-brand,
  .hy-qv-title {
    padding-inline-end: var(--hy-qv-close-gutter);
  }
}

/* ---- gallery ---- */
.hy-qv-media {
  min-width: 0;
}

.hy-qv-stage {
  position: relative;
  overflow: hidden;
  background: var(--color-bg-tertiary);
  aspect-ratio: 3 / 4;
}
/* On a short viewport the portrait stage would push the buy controls below the
   fold and the modal becomes a scroll exercise. Cap it and let the image crop. */
@media (max-height: 780px) and (min-width: 768px) {
  .hy-qv-stage {
    aspect-ratio: auto;
    height: 58vh;
    height: 58dvh;
  }
}

/* ---- Single column: the cap is not optional here ----
   Below 768px the gallery and the buy controls stack into ONE scroll box, so
   the stage's height comes straight off the space the button has. Measured
   against the 90dvh panel with `add to cart` as the thing that must stay
   visible without scrolling:

     360×640   stage 344px → total 745px vs 576px cap → button 169px under
     390×844   stage 384px → total 785px vs 760px cap → button  25px under
     767×1024  stage 844px → total 1277px vs 922px cap → button 355px under

   767 is the worst case, and it was the ONLY width with no cap at all: the
   rule above is gated on `min-width: 768px`, so it stopped exactly one pixel
   short of the width that needed it most.

   Two limits, not one. `aspect-ratio` alone still scales with width — that is
   what produces the 844px stage — and `max-height` alone would leave a narrow
   phone unchanged. 4/5 handles the width, 42dvh handles the height, and
   whichever binds first wins.

   The image does not distort or crop: `.hy-qv-shot img` is `object-fit:
   contain`, so a stage shorter than its ratio letterboxes against the tertiary
   surface the stage already paints. */
@media (max-width: 767px) {
  .hy-qv-stage {
    aspect-ratio: 4 / 5;
    max-height: 42vh;
    max-height: 42dvh;
  }
}

.hy-qv-shot {
  position: absolute;
  inset: 0;
}
.hy-qv-shot img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

.hy-qv-badge {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  z-index: 2;
  padding: 5px 10px;
  font-family: var(--font-num);
  font-size: 0.6875rem;
  color: var(--color-surface);
  background: var(--color-deal);
  letter-spacing: var(--track-micro);
}

/* Horizontal strip, not the PDP's vertical one: the modal is width-limited and
   a side strip would steal from the garment shot, which is the whole point. */
.hy-qv-thumbs {
  display: flex;
  gap: 2px;
  margin-block-start: 2px;
  overflow-x: auto;
  scrollbar-width: none;
}
.hy-qv-thumbs::-webkit-scrollbar {
  display: none;
}

.hy-qv-thumb {
  flex: none;
  width: 56px;
  padding: 0;
  border: 1px solid transparent;
  background: var(--color-bg-tertiary);
  aspect-ratio: 3 / 4;
  cursor: pointer;
  overflow: hidden;
  opacity: 0.72;
  transition:
    border-color var(--t-fast) var(--ease-soft),
    opacity var(--t-fast) var(--ease-soft);
}
.hy-qv-thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.hy-qv-thumb:hover {
  opacity: 1;
}
.hy-qv-thumb.is-active {
  opacity: 1;
  border-color: var(--color-text-primary);
}

/* ---- info column ---- */
.hy-qv-info {
  min-width: 0;
}

.hy-qv-title {
  display: block;
  font-family: var(--font-display-en);
  font-weight: 400;
  font-size: clamp(1.25rem, 2.2vw, 1.625rem);
  line-height: 1.18;
  color: var(--color-text-primary);
  margin-block-end: var(--space-4);
  transition: opacity var(--t-fast) var(--ease-soft);
}
html[dir="rtl"] .hy-qv-title {
  font-family: var(--font-display-ar);
  font-size: clamp(1.125rem, 2vw, 1.5rem);
  line-height: 1.5;
}
.hy-qv-title:hover {
  opacity: 0.6;
}

/* The modal is tighter than the page — the price block does not need the
   page's bottom air, the unit-price line sits directly under it. */
.hy-qv-prices {
  margin-bottom: var(--space-2);
}
.hy-qv-prices .hy-pdp-price {
  font-size: 1.25rem;
}
.hy-qv-prices .hy-pdp-was {
  font-size: 0.9375rem;
}

.hy-qv-stock {
  margin-block: var(--space-3) var(--space-5);
}

.hy-qv-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-block-start: var(--space-6);
}
.hy-qv-actions .hy-btn {
  flex: 1;
}

.hy-qv-full {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-block-start: var(--space-4);
  font-size: 0.8125rem;
  color: var(--color-text-secondary);
  border-block-end: 1px solid var(--color-border-strong);
  padding-block-end: 2px;
  transition:
    color var(--t-fast) var(--ease-soft),
    border-color var(--t-fast) var(--ease-soft);
}
.hy-qv-full:hover {
  color: var(--color-text-primary);
  border-block-end-color: var(--color-text-primary);
}

@media (max-width: 480px) {
  .hy-qv-actions {
    flex-wrap: wrap;
  }
  .hy-qv-actions .hy-btn {
    flex: 1 1 100%;
  }
}

/* ---- Single column: the buy row is pinned, not scrolled to ----
   Capping the stage above is not enough on a small phone. Measured on
   360×640: the info column alone is ~376px (title 70 · price 34 · stock 56 ·
   two option groups 90 · the wrapped buy row 140 · full-product link 46),
   plus 77px of thumbs and 32px of padding — 485px of the 576px a 90dvh panel
   has. Whatever is left cannot hold a product photograph AND the button, at
   any aspect ratio. Shrinking the image further only makes it useless.

   So the image is allowed to need a scroll and the BUTTON is not: it stays on
   the bottom edge of the scroll box the whole way down. The acceptance rule is
   that the shopper never hunts for add-to-cart, not that everything fits.

   `display: contents` is what makes it work. `position: sticky` travels only
   inside its containing block, and .hy-qv-actions sits at the END of
   .hy-qv-info — so anchored there it would unstick after ~46px, the height of
   the one link below it. Dissolving the info wrapper on mobile only promotes
   its children to rows of .hy-qv itself, and the row can then travel the full
   height of the modal. Nothing moves or reorders: a single-column grid stacks
   them in source order exactly as the wrapper did.

   `gap: 0` because that grid gap would otherwise land between every promoted
   child; the info elements carry their own margins already, and the one gap
   that WAS doing work — under the gallery — is restored on .hy-qv-media. */
@media (max-width: 767px) {
  .hy-qv {
    gap: 0;
  }
  .hy-qv-info {
    display: contents;
  }
  .hy-qv-media {
    margin-block-end: var(--space-5);
  }
  .hy-qv-actions {
    position: sticky;
    inset-block-end: 0;
    z-index: 2;
    margin-block-start: var(--space-5);
    padding-block: var(--space-3);
    /* opaque, or the photograph scrolls through the button */
    background: var(--color-surface);
    /* a hairline of shadow so the row reads as sitting above the content it
       covers — the one place this theme allows a shadow, because there is
       nothing else that can say "this is pinned" */
    box-shadow: 0 -10px 18px -14px rgba(20, 17, 15, 0.45);
  }
}

/* --------------------------------------------------------------------------
   Unit-price subtitle — shared by the PDP and the quick view.
   Only rendered while qty > 1, where the headline figure is a line total and
   would otherwise read as a price change.
   `direction: ltr` on the row keeps "1,000ج.م × 2" in that order in Arabic:
   the numerals and the × are neutral characters, so RTL reordering would
   otherwise float the multiplier to the wrong side of the amount.
   -------------------------------------------------------------------------- */
.hy-pdp-unit,
.hy-qv-unit {
  display: flex;
  align-items: center;
  gap: 5px;
  direction: ltr;
  margin: 0 0 var(--space-4);
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: 0.8125rem;
  color: var(--color-text-tertiary);
}
html[dir="rtl"] .hy-pdp-unit,
html[dir="rtl"] .hy-qv-unit {
  justify-content: flex-end;
}
.hy-pdp-unit {
  margin-block-start: calc(var(--space-5) * -1 + 2px);
}

/* Padding for the quick-view panel's inner wrapper (loading, error and the
   injected product markup all sit inside it). Owned here rather than by a
   Tailwind utility so it is present on the very first paint. */
.hy-qv-pad {
  flex: 1;
  min-height: 0; /* a flex child will not shrink below content without this */
  overflow-y: auto;
  /* Symmetric. The close button is `position: absolute` — it is out of flow
     and overlaps whatever is beneath it, so nothing here needs to move aside
     for it. This used to carry an extra inline-end gutter, which reserved a
     column-tall strip of empty space down the WHOLE panel to clear a 34px
     button in one corner: the gallery, the swatches and the buy row all lost
     that width for a collision only the first line of text could ever have.
     The clearance now sits on that first line instead — see .hy-qv-title. */
  padding: var(--space-6);
}
@media (max-width: 640px) {
  .hy-qv-pad {
    padding: var(--space-4);
  }
}

/* --------------------------------------------------------------------------
   Buy-form feedback — product page and quick view
   -------------------------------------------------------------------------- */

/* Why an add did not happen, printed under the button that refused it. The
   toast carries the same sentence but slides away; this one stays until the
   shopper fixes the thing it names. */
.hy-form-error {
  margin: var(--space-3) 0 0;
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--color-deal);
}

/* The option head asks for a choice while none is made. Nothing is
   pre-selected on purpose, so this is the normal opening state, not an error —
   it stays quiet until the shopper tries to add without answering. */
.hy-pdp-opt-value.is-empty {
  color: var(--color-text-tertiary);
}

/* A size that cannot be bought has to look unbuyable. Struck through rather
   than merely faded: fade alone reads as "not selected" next to the values
   that are genuinely just unselected. */
.hy-pdp-swatch.is-unavailable,
.hy-pdp-swatch.is-unavailable:hover {
  cursor: not-allowed;
  opacity: 0.4;
  color: var(--color-text-tertiary);
  border-color: var(--color-border);
  background-image: linear-gradient(
    to bottom left,
    transparent calc(50% - 0.5px),
    var(--color-border-strong) calc(50% - 0.5px),
    var(--color-border-strong) calc(50% + 0.5px),
    transparent calc(50% + 0.5px)
  );
}
/* On an image swatch the line would be lost against the photograph, so that
   variant gets the wash instead. */
.hy-pdp-swatch--img.is-unavailable {
  background-image: none;
  opacity: 0.3;
}

/* ==========================================================================
   15.25  CART  ·  .hy-citem-* · .hy-drawer-* · .hy-cart-* · .hy-sum-*
   --------------------------------------------------------------------------
   One row definition serves the header drawer AND the cart page — the macro in
   ui/cart.njk and its JS twin HayatCartItem.build() both emit exactly this.
   The only difference is `.hy-citem--compact`, which the drawer adds.

   The shell around the drawer (.taqa-drawer / .taqa-overlay) is shared with the
   filters drawer and the search sheet and is NOT redefined here.
   ========================================================================== */

/* ---- the row --------------------------------------------------------- */

.hy-citem {
  display: grid;
  grid-template-columns: 88px minmax(0, 1fr);
  gap: var(--space-4);
  padding-block: var(--space-5);
  border-block-end: 1px solid var(--color-border);
  transition: opacity var(--t-base) var(--ease-soft);
}
.hy-citem--compact {
  grid-template-columns: 68px minmax(0, 1fr);
  gap: var(--space-3);
  padding-block: var(--space-4);
}
.hy-citem:last-child {
  border-block-end: 0;
}
.hy-citem.is-removing {
  opacity: 0.4;
  pointer-events: none;
}

/* Portrait, matching the product card. A garment cropped square loses the
   drape, which is the thing being re-confirmed at this step. */
.hy-citem-media {
  position: relative;
  display: block;
  aspect-ratio: 2 / 3;
  overflow: hidden;
  background: var(--color-bg-tertiary);
}
.hy-citem-media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.hy-citem-noimg {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-border-strong);
}

.hy-citem-body {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.hy-citem-head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

.hy-citem-name {
  flex: 1;
  min-width: 0;
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--color-text-primary);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  transition: opacity var(--t-fast) var(--ease-soft);
}
.hy-citem-name:hover {
  opacity: 0.6;
}
.hy-citem--compact .hy-citem-name {
  font-size: 0.875rem;
}

.hy-citem-remove {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin-block-start: -6px;
  margin-inline-end: -8px;
  border: 0;
  background: none;
  color: var(--color-text-tertiary);
  cursor: pointer;
  transition: color var(--t-fast) var(--ease-soft);
}
.hy-citem-remove:hover {
  color: var(--color-deal);
}

/* which size and colour this line actually is */
.hy-citem-opts {
  display: flex;
  flex-wrap: wrap;
  gap: 4px var(--space-3);
  margin-block-start: 5px;
}
.hy-citem-opt {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.75rem;
  color: var(--color-text-tertiary);
}
.hy-citem-opt b {
  font-weight: 400;
  color: var(--color-text-secondary);
}
.hy-citem-dot {
  width: 11px;
  height: 11px;
  display: inline-block;
  border: 1px solid var(--color-border-strong);
}

/* sold out, or a variant product added without one — read before `+` is
   pressed again, so it sits above the stepper rather than beside it */
.hy-citem-note {
  margin: 6px 0 0;
  font-size: 0.75rem;
  line-height: 1.6;
  color: var(--color-deal);
}

/* Follows the content rather than being pushed to the bottom of the row.
   `margin-block-start: auto` stretched the body to the image height and left a
   hole under any line with no options to show. */
.hy-citem-foot {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-3);
  padding-block-start: var(--space-3);
}

.hy-citem-money {
  text-align: end;
  min-width: 0;
}

.hy-citem-unit {
  display: block;
  direction: ltr; /* "1,000ج.م × 2" keeps that order in Arabic */
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  color: var(--color-text-tertiary);
}
html[dir="rtl"] .hy-citem-unit {
  text-align: left;
}
.hy-citem-total {
  display: block;
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: 0.9375rem;
  color: var(--color-text-primary);
  transition: opacity var(--t-fast) var(--ease-soft);
}
.hy-citem-total.is-pending {
  opacity: 0.45;
}
.hy-citem-was {
  display: block;
  font-family: var(--font-num);
  font-size: 0.75rem;
  color: var(--color-text-tertiary);
  text-decoration: line-through;
}

.hy-citem-confirm {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block-start: var(--space-3);
  padding: var(--space-3);
  background: var(--color-bg-tertiary);
  font-size: 0.8125rem;
  color: var(--color-text-primary);
}
.hy-citem-confirm-acts {
  display: flex;
  gap: 8px;
}

/* On a finger, the destructive control on a cart line and the drawer's close
   button are the two smallest targets in the theme. The icon inside stays the
   same size — only the box that accepts the tap grows. */
@media (pointer: coarse) {
  .hy-citem-remove {
    width: 44px;
    height: 44px;
    margin-block-start: -11px;
    margin-inline-end: -13px;
  }
  .hy-drawer-close {
    width: 44px;
    height: 44px;
    margin-inline-end: -12px;
  }
  /* The footer icons are 18px of artwork with nothing around them. The icon
       keeps its size; the box that accepts the tap grows around it, and the
       negative margin keeps the row visually where it was. */
  .hy-footer-social {
    gap: var(--space-2);
    margin-inline-start: -13px;
  }
  .hy-footer-social a {
    width: 44px;
    height: 44px;
    align-items: center;
    justify-content: center;
  }
}

/* the stepper, one size down from the product page's */
.hy-qty--sm button {
  width: 32px;
  height: 34px;
}
.hy-qty--sm span {
  min-width: 26px;
  font-size: 0.8125rem;
}
.hy-qty--sm span .hy-spinner {
  display: inline-block;
  vertical-align: -1px;
}

.hy-cart-list {
  display: flex;
  flex-direction: column;
}

/* ---- summary rows (drawer footer AND cart page box) ------------------- */

.hy-sum {
  display: flex;
  flex-direction: column;
}
.hy-sum-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding-block: 7px;
  font-size: 0.875rem;
  color: var(--color-text-secondary);
}
.hy-sum-row > span:last-child {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  color: var(--color-text-primary);
}
.hy-sum-row--save > span:last-child {
  color: var(--color-deal);
}
.hy-sum-muted {
  font-family: var(--font-body-en) !important;
  font-size: 0.75rem;
  color: var(--color-text-tertiary) !important;
}
html[dir="rtl"] .hy-sum-muted {
  font-family: var(--font-body-ar) !important;
}
.hy-sum-row--total {
  margin-block-start: 6px;
  padding-block-start: var(--space-3);
  border-block-start: 1px solid var(--color-border);
  font-size: 1rem;
  color: var(--color-text-primary);
}
.hy-sum-row--total > span:last-child {
  font-size: 1.125rem;
}

/* ---- the drawer contents --------------------------------------------- */

.hy-drawer-head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-block-end: 1px solid var(--color-border);
}
.hy-drawer-title {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  font-size: 0.75rem;
  text-transform: uppercase;
  color: var(--color-text-primary);
  letter-spacing: var(--track-micro);
}
html[dir="rtl"] .hy-drawer-title {
  font-size: 0.875rem;
  text-transform: none;
}
.hy-drawer-count {
  font-family: var(--font-num);
  font-size: 0.6875rem;
  color: var(--color-text-tertiary);
}
.hy-drawer-close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  margin-inline-end: -6px;
  border: 0;
  background: none;
  color: var(--color-text-primary);
  cursor: pointer;
  transition: opacity var(--t-fast) var(--ease-soft);
}
.hy-drawer-close:hover {
  opacity: 0.55;
}

.hy-drawer-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-inline: var(--space-5);
  scrollbar-width: thin;
}

.hy-drawer-foot {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-block-start: 1px solid var(--color-border);
  background: var(--color-surface);
}
.hy-drawer-note {
  margin: 0;
  font-size: 0.75rem;
  color: var(--color-text-tertiary);
  text-align: center;
}
.hy-drawer-empty {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding-inline: var(--space-5);
}

/* ---- the cart page ---------------------------------------------------- */

.hy-cart {
  padding-block-end: var(--section-py);
}

.hy-cart-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding-block: var(--space-6) var(--space-5);
  border-block-end: 1px solid var(--color-text-primary);
}
.hy-cart-title {
  margin: 0;
  font-family: var(--font-display-en);
  font-weight: 400;
  font-size: clamp(1.75rem, 3.4vw, 2.5rem);
  line-height: 1.1;
  color: var(--color-text-primary);
}
html[dir="rtl"] .hy-cart-title {
  font-family: var(--font-display-ar);
  font-size: clamp(1.5rem, 3vw, 2.125rem);
  line-height: 1.45;
}
/* NOT .hy-cart-count — that name is already taken by the header's bag badge,
   which is absolutely positioned. Reusing it dropped this line into the top-left
   corner of the page. */
.hy-cart-qty {
  font-family: var(--font-num);
  font-size: 0.8125rem;
  color: var(--color-text-tertiary);
}

.hy-cart-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-6);
  padding-block-start: var(--space-2);
}
@media (min-width: 900px) {
  .hy-cart-grid {
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: var(--space-8);
    align-items: start;
  }
}
.hy-cart-items {
  min-width: 0;
}

.hy-cart-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding-block-start: var(--space-5);
  border-block-start: 1px solid var(--color-border);
}
.hy-cart-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.8125rem;
  color: var(--color-text-secondary);
  transition: color var(--t-fast) var(--ease-soft);
}
.hy-cart-back:hover {
  color: var(--color-text-primary);
}
.hy-cart-clear {
  border: 0;
  background: none;
  padding: 0;
  font-size: 0.75rem;
  color: var(--color-text-tertiary);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  transition: color var(--t-fast) var(--ease-soft);
}
.hy-cart-clear:hover {
  color: var(--color-deal);
}

/* The summary follows the shopper down a long list — the total is the number
   the decision hangs on, and scrolling back up to find it is friction at the
   worst possible moment. */
.hy-cart-aside {
  min-width: 0;
}
@media (min-width: 900px) {
  .hy-cart-aside {
    position: sticky;
    top: calc(var(--header-h) + var(--space-5));
  }
}
.hy-cart-box {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-5);
  background: var(--color-bg-secondary);
}
.hy-cart-box-title {
  font-size: 0.6875rem;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
  letter-spacing: var(--track-micro);
}
html[dir="rtl"] .hy-cart-box-title {
  font-size: 0.8125rem;
  text-transform: none;
}

.hy-cart-ship {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.hy-cart-ship-text {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.8125rem;
  color: var(--color-text-primary);
}
.hy-cart-ship-text bdi {
  font-family: var(--font-num);
}
.hy-cart-ship-text--won {
  color: var(--color-success);
}
.hy-cart-bar {
  display: block;
  height: 2px;
  background: var(--color-border);
  overflow: hidden;
}
.hy-cart-bar > span {
  display: block;
  height: 100%;
  background: var(--color-text-primary);
  transition: width var(--t-base) var(--ease-out);
}

.hy-cart-trust {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0;
  padding: var(--space-4) 0 0;
  list-style: none;
  border-block-start: 1px solid var(--color-border);
}
.hy-cart-trust-item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.75rem;
  color: var(--color-text-secondary);
}
.hy-cart-trust-item svg {
  flex: none;
  color: var(--color-text-tertiary);
}

.hy-cart-note {
  margin: 0;
  font-size: 0.6875rem;
  line-height: 1.7;
  color: var(--color-text-tertiary);
  text-align: center;
}

/* ---- empty ---- */
.hy-cart-empty {
  padding-block: var(--space-9, 72px);
  gap: var(--space-4);
}
.hy-cart-empty-icon {
  display: inline-flex;
  color: var(--color-border-strong);
}
.hy-cart-empty-icon--lg {
  margin-block-end: var(--space-2);
}
.hy-cart-empty-text {
  margin: 0;
  max-width: 44ch;
  font-size: 0.875rem;
  line-height: 1.8;
  color: var(--color-text-secondary);
}
.hy-cart-title--empty {
  font-size: clamp(1.375rem, 2.6vw, 1.875rem);
}

@media (max-width: 480px) {
  .hy-citem-foot {
    flex-direction: column;
    /* `start`, not `stretch`. A column flex container stretches its children
       across the cross axis by default — which is the WIDTH here — so the
       quantity stepper's border ran the full width of the row even though the
       control itself is `inline-flex` and sized to its three buttons. The box
       hugged nothing and read as an empty input field.
       Nothing in this column needs the full width: the money block below it is
       `text-align: start` at this size, so hugging is right for both. */
    align-items: start;
    gap: var(--space-2);
  }
  .hy-citem-money {
    text-align: start;
  }
  html[dir="rtl"] .hy-citem-unit {
    text-align: right;
  }
}

/* ==========================================================================
   15.26  COLLECTIONS PAGE  ·  .hy-collp-*
   --------------------------------------------------------------------------
   Container only. The tile itself is `.hy-coll-tile` from 15.11 — the same one
   the homepage carousel puts on a rail — so a collection is the same object
   wherever a shopper meets it, and a change to the tile lands in both places.
   ========================================================================== */

.hy-collp {
  padding-block: var(--space-7) var(--section-py);
}
.hy-collp--well {
  background: var(--color-bg-secondary);
}

.hy-collp-head {
  max-width: 62ch;
  margin-block-end: var(--space-7);
}
.hy-collp-eyebrow {
  display: block;
  font-family: var(--font-body-en);
  font-size: 0.6875rem;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
  margin-block-end: var(--space-3);
  letter-spacing: var(--track-micro);
}
html[dir="rtl"] .hy-collp-eyebrow {
  font-family: var(--font-body-ar);
  font-size: 0.8125rem;
  text-transform: none;
}

.hy-collp-title {
  margin: 0;
  font-family: var(--font-display-en);
  font-weight: 400;
  font-size: clamp(1.875rem, 4vw, 3rem);
  line-height: 1.08;
  color: var(--color-text-primary);
}
html[dir="rtl"] .hy-collp-title {
  font-family: var(--font-display-ar);
  font-size: clamp(1.625rem, 3.4vw, 2.5rem);
  line-height: 1.4;
}

.hy-collp-desc {
  margin: var(--space-4) 0 0;
  font-size: 0.9375rem;
  line-height: 1.9;
  color: var(--color-text-secondary);
}
.hy-collp-count {
  display: inline-block;
  margin-block-start: var(--space-4);
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: 0.8125rem;
  color: var(--color-text-tertiary);
}

/* 2 up on a phone by default — a single column of tall tiles turns a ten-
   collection store into a very long scroll for no extra clarity. */
.hy-collp-grid {
  display: grid;
  grid-template-columns: repeat(var(--collp-cols-m, 2), minmax(0, 1fr));
  gap: 2px;
}
@media (min-width: 768px) {
  .hy-collp-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (min-width: 1024px) {
  .hy-collp-grid {
    grid-template-columns: repeat(var(--collp-cols, 3), minmax(0, 1fr));
  }
}

/* the pager is .hy-pager (15.22); on <a> there is no :disabled, so a link that
   would be disabled is simply not rendered */
.hy-collp .hy-pager-btn {
  text-decoration: none;
}

.hy-collp-empty {
  padding-block: var(--space-9, 72px);
  gap: var(--space-4);
}
.hy-collp-empty-icon {
  display: inline-flex;
  color: var(--color-border-strong);
}
.hy-collp-empty-text {
  margin: 0;
  max-width: 44ch;
  font-size: 0.875rem;
  line-height: 1.8;
  color: var(--color-text-secondary);
}

/* ==========================================================================
   15.27  BLOG  ·  .hy-blogpage-* · .hy-blog-* · .hy-article-*
   --------------------------------------------------------------------------
   Three pages, one card: /blogs, /blogs/<handle> and the related row on an
   article all render ui/article-card.njk, which is the `.hy-post-*` object
   from 15.18. Nothing about the card is redefined here — only the containers.
   ========================================================================== */

.hy-blogpage {
  padding-block: var(--space-5) var(--section-py);
}
.hy-blogpage--well {
  /* `--color-bg-secondary` is PAPER (#FFFFFF) — "cards sit ON the paper, not
     above it", see the token block at the top of this file. Painting the well
     with it drew white on white, so the "بيج فاتح / warm off-white" option in
     the Theme Editor did nothing at all. The warm well is `--color-bg-tertiary`
     (#F7F6F4) — the same token every other section's `well` option uses. */
  background: var(--color-bg-tertiary);
}

.hy-blog-head {
  max-width: 62ch;
  margin-block: var(--space-6) var(--space-7);
}
.hy-blog-head--row {
  max-width: none;
  margin-block-end: var(--space-6);
}

.hy-blog-eyebrow {
  display: block;
  font-family: var(--font-body-en);
  font-size: 0.6875rem;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
  margin-block-end: var(--space-3);
  letter-spacing: var(--track-micro);
}
html[dir="rtl"] .hy-blog-eyebrow {
  font-family: var(--font-body-ar);
  font-size: 0.8125rem;
  text-transform: none;
}

.hy-blog-title {
  margin: 0;
  font-family: var(--font-display-en);
  font-weight: 400;
  font-size: clamp(1.875rem, 4vw, 3rem);
  line-height: 1.08;
  color: var(--color-text-primary);
}
html[dir="rtl"] .hy-blog-title {
  font-family: var(--font-display-ar);
  font-size: clamp(1.625rem, 3.4vw, 2.5rem);
  line-height: 1.4;
}
.hy-blog-title--sm {
  font-size: clamp(1.25rem, 2.4vw, 1.75rem);
}
html[dir="rtl"] .hy-blog-title--sm {
  font-size: clamp(1.125rem, 2.2vw, 1.5rem);
}

.hy-blog-desc {
  margin: var(--space-4) 0 0;
  font-size: 0.9375rem;
  line-height: 1.9;
  color: var(--color-text-secondary);
}
.hy-blog-count {
  display: inline-block;
  margin-block-start: var(--space-4);
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: 0.8125rem;
  color: var(--color-text-tertiary);
}

.hy-blog-hero {
  aspect-ratio: 21 / 9;
  overflow: hidden;
  background: var(--color-bg-tertiary);
  margin-block-start: var(--space-5);
}
.hy-blog-hero img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

.hy-blog-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-7) var(--space-5);
}
@media (min-width: 640px) {
  .hy-blog-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (min-width: 1024px) {
  .hy-blog-grid {
    grid-template-columns: repeat(var(--blog-cols, 3), minmax(0, 1fr));
  }
}

/* The lead article is the same card at a larger size — not a second design.
   Below the breakpoint it is simply the first cell, like every other. */
@media (min-width: 1024px) {
  .hy-blog-grid--lead > :first-child {
    grid-column: 1 / -1;
  }
  .hy-blog-grid--lead > :first-child .hy-post-media {
    aspect-ratio: 21 / 9;
  }
  .hy-blog-grid--lead > :first-child .hy-post-title {
    font-size: 1.5rem;
  }
  html[dir="rtl"] .hy-blog-grid--lead > :first-child .hy-post-title {
    font-size: 1.375rem;
  }
}

.hy-blog-empty {
  padding-block: var(--space-9, 72px);
  gap: var(--space-4);
}
.hy-blog-empty-icon {
  display: inline-flex;
  color: var(--color-border-strong);
}
.hy-blog-empty-text {
  margin: 0;
  max-width: 44ch;
  font-size: 0.875rem;
  line-height: 1.8;
  color: var(--color-text-secondary);
}

/* Placeholder for a card with no cover — the post grid never needed one
   because its images are required; a blog often has none. */
.hy-post-noimg {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-border-strong);
}

/* ---- the article itself ---------------------------------------------- */

.hy-article {
  padding-block: var(--space-5) var(--section-py);
}

.hy-article-head {
  margin-block: var(--space-6) var(--space-6);
}

.hy-article-title {
  margin: 0;
  font-family: var(--font-display-en);
  font-weight: 400;
  font-size: clamp(1.75rem, 3.8vw, 2.75rem);
  line-height: 1.14;
  color: var(--color-text-primary);
}
html[dir="rtl"] .hy-article-title {
  font-family: var(--font-display-ar);
  font-size: clamp(1.5rem, 3.2vw, 2.25rem);
  line-height: 1.45;
}

.hy-article-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-block-start: var(--space-4);
  font-family: var(--font-num);
  font-size: 0.75rem;
  color: var(--color-text-tertiary);
}
.hy-article-meta svg {
  flex: none;
  opacity: 0.6;
}

.hy-article-hero {
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--color-bg-tertiary);
  margin-block-end: var(--space-7);
}
.hy-article-hero img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* `.taqa-prose` still styles the authored HTML — headings, lists, quotes. It is
   generic typography, shared with the CMS page, and renaming it would touch
   merchant content that already exists. */
.hy-article-body {
  max-width: none;
}
.hy-article-body img {
  margin-block: 1.8em;
}

.hy-article-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-block-start: var(--space-7);
}
.hy-article-tag {
  padding: 5px 12px;
  border: 1px solid var(--color-border);
  font-size: 0.75rem;
  color: var(--color-text-secondary);
}

.hy-article-share {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-block-start: var(--space-7);
  padding-block: var(--space-5);
  border-block: 1px solid var(--color-border);
}

.hy-article-back {
  margin-block-start: var(--space-6);
}

.hy-article-related {
  margin-block-start: var(--section-py);
  padding-block: var(--section-py);
  background: var(--color-bg-secondary);
}

/* ==========================================================================
   15.28  STATIC PAGE  ·  .hy-pg-*        AND        404  ·  .hy-404-*
   --------------------------------------------------------------------------
   Both are text-first pages, so both use the narrow measure. `.taqa-prose`
   still carries the authored-HTML typography for the body — it is generic and
   shared with the article, and renaming it would touch merchant content that
   already exists.
   ========================================================================== */

.hy-pg {
  padding-block: var(--space-5) var(--section-py);
}
.hy-pg--well {
  background: var(--color-bg-secondary);
}

.hy-pg-head {
  margin-block: var(--space-6) var(--space-7);
  padding-block-end: var(--space-5);
  border-block-end: 1px solid var(--color-border);
}
.hy-pg-eyebrow {
  display: block;
  font-family: var(--font-body-en);
  font-size: 0.6875rem;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
  margin-block-end: var(--space-3);
  letter-spacing: var(--track-micro);
}
html[dir="rtl"] .hy-pg-eyebrow {
  font-family: var(--font-body-ar);
  font-size: 0.8125rem;
  text-transform: none;
}

.hy-pg-title {
  margin: 0;
  font-family: var(--font-display-en);
  font-weight: 400;
  font-size: clamp(1.75rem, 3.8vw, 2.75rem);
  line-height: 1.1;
  color: var(--color-text-primary);
}
html[dir="rtl"] .hy-pg-title {
  font-family: var(--font-display-ar);
  font-size: clamp(1.5rem, 3.2vw, 2.25rem);
  line-height: 1.42;
}
.hy-pg-updated {
  margin: var(--space-3) 0 0;
  font-family: var(--font-num);
  font-size: 0.75rem;
  color: var(--color-text-tertiary);
}

.hy-pg-body {
  max-width: none;
}

.hy-pg-empty {
  padding-block: var(--space-8);
  gap: var(--space-3);
}
.hy-pg-empty-icon {
  display: inline-flex;
  color: var(--color-border-strong);
}
.hy-pg-empty-text {
  margin: 0;
  max-width: 46ch;
  font-size: 0.875rem;
  line-height: 1.8;
  color: var(--color-text-secondary);
}

/* ---- 404 -------------------------------------------------------------- */

.hy-404 {
  display: flex;
  align-items: center;
  min-height: 66vh;
  padding-block: var(--section-py);
}
.hy-404-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  max-width: 52ch;
  margin-inline: auto;
  text-align: center;
}

/* The number is the wallpaper, not the message — it sits behind the sentence
   that actually helps, in the faintest ink on the page. */
.hy-404-num {
  font-family: var(--font-display-en);
  font-variant-numeric: tabular-nums;
  font-size: clamp(4.5rem, 16vw, 10rem);
  font-weight: 400;
  line-height: 0.86;
  color: var(--color-border);
  user-select: none;
}

.hy-404-eyebrow {
  font-family: var(--font-body-en);
  font-size: 0.6875rem;
  text-transform: uppercase;
  color: var(--color-text-tertiary);
  letter-spacing: var(--track-micro);
}
html[dir="rtl"] .hy-404-eyebrow {
  font-family: var(--font-body-ar);
  font-size: 0.8125rem;
  text-transform: none;
}

.hy-404-title {
  margin: 0;
  font-family: var(--font-display-en);
  font-weight: 400;
  font-size: clamp(1.5rem, 3.2vw, 2.25rem);
  line-height: 1.14;
  color: var(--color-text-primary);
}
html[dir="rtl"] .hy-404-title {
  font-family: var(--font-display-ar);
  font-size: clamp(1.375rem, 2.8vw, 1.875rem);
  line-height: 1.45;
}
.hy-404-text {
  margin: 0;
  max-width: 44ch;
  font-size: 0.9375rem;
  line-height: 1.9;
  color: var(--color-text-secondary);
}

.hy-404-search {
  display: flex;
  align-items: center;
  width: 100%;
  max-width: 380px;
  margin-block-start: var(--space-2);
  border-block-end: 1px solid var(--color-text-primary);
}
.hy-404-search input {
  flex: 1;
  min-width: 0;
  height: 44px;
  border: 0;
  background: none;
  font-size: 0.9375rem;
  color: var(--color-text-primary);
}
.hy-404-search input::placeholder {
  color: var(--color-text-tertiary);
}
.hy-404-search input:focus {
  outline: none;
}
.hy-404-search button {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 44px;
  border: 0;
  background: none;
  color: var(--color-text-primary);
  cursor: pointer;
  transition: opacity var(--t-fast) var(--ease-soft);
}
.hy-404-search button:hover {
  opacity: 0.55;
}

.hy-404-acts {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-3);
  margin-block-start: var(--space-3);
}

.hy-404-links {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  margin-block-start: var(--space-6);
  padding-block-start: var(--space-6);
  border-block-start: 1px solid var(--color-border);
  width: 100%;
}
.hy-404-links-label {
  font-size: 0.75rem;
  color: var(--color-text-tertiary);
}
.hy-404-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
}
.hy-404-chip {
  padding: 6px 14px;
  border: 1px solid var(--color-border);
  font-size: 0.8125rem;
  color: var(--color-text-secondary);
  transition:
    border-color var(--t-fast) var(--ease-soft),
    color var(--t-fast) var(--ease-soft);
}
.hy-404-chip:hover {
  border-color: var(--color-text-primary);
  color: var(--color-text-primary);
}

/* ==========================================================================
   15.29  PLACEHOLDERS  ·  .hy-ph-* and the --ph card modifiers
   --------------------------------------------------------------------------
   What a section shows before the merchant has picked anything (ui/placeholder.njk).

   The rule these follow: occupy the REAL card's space, in the real layout, but
   never look like stock. A merchant who cannot tell a placeholder from a
   product will ship one. So: flat artwork in one muted colour family, no
   photograph, and dimmed type — recognisably a blank waiting to be filled.
   ========================================================================== */

.hy-ph-media {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  background: var(--color-bg-tertiary);
  overflow: hidden;
}
/* Sized by HEIGHT, not width. A frame can be portrait (2:3, 3:4), and a
   width-based size left a small piece marooned in a tall empty box. Height
   keeps it filling whatever shape the merchant picked, and the max-width stops
   it stretching edge to edge in a square one. */
.hy-ph-art {
  height: 64%;
  width: auto;
  max-width: 76%;
  display: block;
}
/* Two tones off the theme's own neutrals, so the artwork sits inside the
   palette instead of introducing a colour of its own — present enough to read
   as a piece of furniture, flat enough to never be mistaken for a photograph. */
.hy-ph-fill {
  fill: var(--color-border-strong);
  opacity: 0.7;
}
.hy-ph-tint {
  fill: var(--color-text-tertiary);
  opacity: 0.4;
}

/* ---- product ---- */
.hy-pcard--ph {
  cursor: default;
}
.hy-pcard--ph .hy-pcard-media {
  cursor: default;
}
.hy-pcard--ph .hy-pcard-name,
.hy-pcard--ph .hy-price {
  color: var(--color-text-tertiary);
}
/* The info block always sits BELOW the artwork here. The tray variant hides it
   behind a hover that a placeholder has no reason to reward. */
.hy-pcard--ph .hy-pcard-info {
  position: static;
  opacity: 1;
  padding: 14px 0 0;
  background: none;
}

/* ---- collection ---- */
.hy-coll-tile--ph {
  cursor: default;
}
.hy-coll-tile--ph .hy-ph-media {
  position: absolute;
  inset: 0;
}
.hy-coll-tile--ph .hy-coll-name {
  color: var(--color-text-tertiary);
}
/* The real tile darkens its photograph so white type reads; there is no
   photograph here, so the scrims would only muddy the artwork. */
.hy-coll-tile--ph::before,
.hy-coll-tile--ph::after {
  display: none;
}

/* ---- article ---- */
.hy-post--ph {
  cursor: default;
}
.hy-post--ph .hy-post-title,
.hy-post--ph .hy-post-excerpt,
.hy-post--ph .hy-post-meta {
  color: var(--color-text-tertiary);
}
.hy-post--ph:hover .hy-post-title {
  opacity: 1;
}

/* Nothing about a placeholder should respond to a pointer — it is scenery, and
   a hover that lifts or zooms invites the click it then refuses. */
.hy-pcard--ph:hover .hy-ph-art,
.hy-coll-tile--ph:hover .hy-ph-art,
.hy-post--ph:hover .hy-ph-art {
  transform: none;
}

/* One quiet line under a placeholder row, telling the merchant what to do.
   Hidden from the shopper's eye by nothing — a store with no products at all
   has bigger problems, and the sentence is honest either way. */
.hy-ph-note {
  margin: var(--space-5) 0 0;
  font-size: 0.75rem;
  color: var(--color-text-tertiary);
  text-align: center;
}

/* --------------------------------------------------------------------------
   15.29.1  EMPTY-SECTION SKELETON  ·  .hy-ph-box / .hy-ph-line / .hy-ph-hint
   --------------------------------------------------------------------------
   What a section shows the moment it is added and before anything is filled in.

   A preset carries only a `category` and a `name` — Qumra has no way to seed
   default blocks — so a block-driven section arrives EMPTY. Rendering nothing
   in that state leaves the merchant staring at a gap with no way to tell
   whether the section is broken, still loading, or simply theirs to fill.

   So every section draws its own real layout at its real size, in grey. The
   shape teaches what the section is; the hint says what to do. All of it is
   `data-hy-placeholder` and `aria-hidden`, so no script can mistake it for
   content and no screen reader reads it out.

   DASHED, not solid: several sections use a solid grey block as a real surface
   (a product frame, a media well), and a solid skeleton would be
   indistinguishable from a photograph that failed to load.
   -------------------------------------------------------------------------- */

.hy-ph-box {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-card);
  background: var(--color-bg-tertiary);
  color: var(--color-text-tertiary);
}

/* No opacity knock-back: at 0.55 over the well these lines were invisible in
   the very state they exist for — a card the merchant has just added. The token
   is already a soft warm grey; it needs its full value to read. */
.hy-ph-line {
  display: block;
  flex-shrink: 0;
  height: var(--ph-line-h, 12px);
  width: var(--ph-line-w, 100%);
  border-radius: 999px;
  background: var(--color-border-strong);
}

.hy-ph-line--lg {
  --ph-line-h: 22px;
}
.hy-ph-line--sm {
  --ph-line-h: 9px;
}

.hy-ph-stack {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
}

/* The one piece of real text in an empty section. It is a hint to the person
   editing, not copy for a shopper — kept quiet on purpose. */
/* The offer skeleton keeps the card's real two-column split, so the shape says
   "copy on one side, a product on the other" before anything is filled in. */
.hy-offer.hy-ph-box {
  display: grid;
  align-items: center;
}
.hy-offer.hy-ph-box .hy-offer-body {
  gap: 14px;
}

.hy-ph-hint {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border: 1px dashed var(--color-border-strong);
  border-radius: 999px;
  background: var(--color-surface);
  color: var(--color-text-tertiary);
  font-size: 0.8125rem;
  font-weight: 500;
  white-space: nowrap;
}

.hy-ph-hint svg {
  flex-shrink: 0;
}

/* A centred hint for the sections whose skeleton is one big area. */
.hy-ph-center {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
}
/* ==========================================================================
   16. TOUCH TARGETS  ·  @media (pointer: coarse)
   --------------------------------------------------------------------------
   QA rule: every tappable control is ≥ 44×44 on phone and tablet.

   Scoped to `pointer: coarse` and NOT to a width, on purpose. The rule is
   about the input device, not the viewport — a 1024px tablet is a finger and
   a 1024px laptop is a mouse, and a width query cannot tell them apart. It
   also means none of the desktop design moves: these controls keep their
   drawn size wherever there is a cursor.

   Same technique the cart/footer block in 15.25 already uses — the ARTWORK
   never changes size, only the box that accepts the tap grows around it, with
   a negative margin so the row stays visually where it was drawn. Growing the
   icon instead would be a different design at a different size.

   `min-*`, not `width`/`height`: several of these are flex children whose box
   is already sized by their content, and a hard `height` would fight it.
   ========================================================================== */
@media (pointer: coarse) {
  /* ---- iOS auto-zoom ----
     Safari on iOS zooms the whole page when a focused input's font-size is
     under 16px, and it does NOT zoom back out on blur — the shopper is left on
     a magnified, sideways-scrolling page and has to pinch their way back. It
     is not a preference and there is no meta-tag fix that does not also break
     pinch-zoom for everyone.

     16px here, and only here. These five fields are drawn at 14–15px because
     that is the size the design calls for beside their labels; on a cursor
     they keep it. `1rem` rather than `16px` so a root font-size change still
     scales them together.

     ⚠️  A field added anywhere in this theme has to be listed here as well —
     the same standing rule as the authored-HTML surfaces in 15.23. */
  .hy-search-input,
  .hy-footer-field input,
  .hy-filter-price input,
  .hy-filter-text input,
  .hy-404-search input {
    font-size: 1rem;
  }

  /* Header row — search, wishlist, cart. Drawn at 38px. */
  .hy-icon-btn,
  .hy-cart-btn {
    min-width: 44px;
    min-height: 44px;
  }

  /* The announcement bar's dismiss. Drawn at 26px so it does not compete with
     the message; the tap box grows around the same glyph. */
  .hy-topbar-close {
    width: 44px;
    height: 44px;
  }

  /* Hero and testimonial arrows sit ON the artwork, so they are drawn small
     to stay out of the photograph's way. The tap box grows; the negative
     margin keeps the arrow optically where it was against the image edge. */
  .hy-hero-arrow,
  .hy-quotes-arrow,
  .hy-coll-arrow {
    min-width: 44px;
    min-height: 44px;
  }

  /* Quick view's close button — 34px, and the only way out of a modal. */
  .hy-qv-close {
    width: 44px;
    height: 44px;
  }
  /* Coupled to the line above: the clearance that keeps the title out from
     under that button is derived from its width, so it grows with it. */
  .hy-qv {
    --hy-qv-close-gutter: 54px;
  }

  /* The product card's quick-view eye drops to 36px on small screens (15.7).
     It is a secondary affordance, but it is still a tap target. */
  .hy-pcard-eye {
    width: 44px;
    height: 44px;
  }

  /* Card add-to-cart. `.hy-btn--sm` is 40px and correct on a cursor. */
  .hy-pcard-atc .hy-btn {
    min-height: 44px;
  }
}
