/* =========================================================
   Arch motif — reused for photo frames, card masks, blockquotes
   ========================================================= */
.arch-frame {
  --arch-radius: 50%;
  position: relative;
  border-radius: var(--arch-radius) var(--arch-radius) 8px 8px / 30% 30% 8px 8px;
  overflow: hidden;
  padding: 8px;
  background: var(--accent-gold);
}

.arch-frame__inner {
  border-radius: var(--arch-radius) var(--arch-radius) 4px 4px / 30% 30% 4px 4px;
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--bg-surface);
}

/* Hero main title — bold + accent-gold underline highlight (Section 4.1
   allows an "optional --accent-gold underline motif" on H1). */
.hero-title {
  display: block;
  position: relative;
  font-weight: 800;
  color: var(--primary);
  font-size: 68px;
  line-height: 1.08;
}
@media (max-width: 767px) {
  .hero-title {
    font-size: 40px;
  }
}
[dir="rtl"] .hero-title {
  font-size: 54px;
}
@media (max-width: 767px) {
  [dir="rtl"] .hero-title {
    font-size: 36px;
  }
}
/* No [dir="rtl"] override needed here (and one used to exist, incorrectly)
   -- inset-inline-start already auto-flips to the physical right edge in
   RTL on its own. Explicitly setting inset-inline-end instead would target
   the LEFT edge in RTL (inline-end = the edge reading finishes at, which is
   left in RTL), undoing the correct native flip and stranding the
   underline back on the left, away from the now right-aligned title text. */
.hero-title::after {
  content: '';
  position: absolute;
  bottom: -8px;
  inset-inline-start: 2px;
  width: 72px;
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--accent-gold), var(--accent-turquoise-light));
}
.hero-title__prefix {
  font-weight: 400;
}

/* Interior page titles (page-header component) — same bold/underline
   treatment as .hero-title but smaller, since these sit under a breadcrumb
   rather than carrying a full hero. */
.page-title {
  display: block;
  position: relative;
  font-weight: 800;
  color: var(--primary);
  font-size: 32px;
  line-height: 1.15;
}
@media (max-width: 767px) {
  .page-title { font-size: 24px; }
}
[dir="rtl"] .page-title { font-size: 27px; }
@media (max-width: 767px) {
  [dir="rtl"] .page-title { font-size: 21px; }
}
/* Same note as .hero-title::after above -- inset-inline-start auto-flips
   correctly on its own; no [dir="rtl"] override needed (or wanted). */
.page-title::after {
  content: '';
  position: absolute;
  bottom: -8px;
  inset-inline-start: 2px;
  width: 56px;
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--accent-gold), var(--accent-turquoise-light));
}

/* Interior page-header — center-aligned (unlike Home's left-aligned hero).
   Uses physical left/transform (not inset-inline-start) so the same rule
   centers correctly regardless of LTR/RTL — pure geometric centering
   doesn't need to flip with direction the way start/end-aligned content does. */
.page-header-content {
  text-align: center;
}
/* Temporarily hidden (not removed) -- scoped to the interior page-header
   component specifically, so homepage section eyebrows ("Latest Lectures",
   "Upcoming Tours & Events", etc.), which reuse the same .section-eyebrow
   class in a different context, are unaffected. */
.page-header-content .section-eyebrow {
  display: none;
}
.page-header-content .page-title::after,
[dir="rtl"] .page-header-content .page-title::after {
  inset-inline-start: auto;
  inset-inline-end: auto;
  left: 50%;
  transform: translateX(-50%);
}

/* Hero stats row (lecture/book counts, under the CTA buttons) */
.hero-stats__num {
  font-size: 26px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--primary);
}
:root[data-theme="dark"] .hero-stats__num {
  color: var(--accent-turquoise-light);
}
.hero-stats__divider {
  width: 1px;
  height: 32px;
  background: var(--border);
}

/* Hero photo cluster (Home hero, right side) — 1 to 6 arch-framed images.
   Column counts come from CSS custom properties set inline per render
   (see app.js renderHeroGallery), so the same grid rule handles every
   count from 1 to 6 without per-count markup. */
.hero-gallery {
  display: grid;
  grid-template-columns: repeat(var(--hg-cols-mobile, 2), 1fr);
  gap: 5px;
}
@media (min-width: 1024px) {
  .hero-gallery {
    grid-template-columns: repeat(var(--hg-cols-desktop, 2), 1fr);
    gap: 6px;
  }
}
/* iPhone-Photos-style square tile — no arch framing, just a clean rounded
   square that scales/lifts slightly on hover for a modern gallery feel. */
.hero-gallery__item {
  aspect-ratio: 1 / 1;
  border-radius: 16px;
  overflow: hidden;
  border: 1px solid var(--border);
  transition: transform 250ms ease, box-shadow 250ms ease;
}
.hero-gallery__item:hover {
  transform: scale(1.04);
  box-shadow: 0 14px 28px rgba(var(--shadow-color), 0.16);
  z-index: 1;
}
.hero-gallery__item svg,
.hero-gallery__item img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
}

.arch-mask {
  /* url(#arch-clip) — an objectBoundingBox clipPath defined once in index.html —
     scales correctly at any size, unlike clip-path: path() which uses fixed,
     unscaled px coordinates and would clip incorrectly at anything other than
     the exact box it was authored for. */
  clip-path: url(#arch-clip);
  aspect-ratio: 4 / 5;
  width: 100%;
  background-size: cover;
  background-position: center;
}
.arch-mask img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
}

/* =========================================================
   Buttons — Section 9.3
   ========================================================= */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 48px;
  padding-inline: 24px;
  border-radius: var(--radius-btn);
  font-weight: 500;
  font-size: 15px;
  letter-spacing: 0.2px;
  border: 1.5px solid transparent;
  transition: background-color 200ms ease, border-color 200ms ease, color 200ms ease, transform 150ms ease;
  white-space: nowrap;
}

.btn:active {
  transform: scale(0.98);
}

.btn-primary {
  background: var(--primary);
  color: var(--bg-surface);
}
.btn-primary:hover {
  background: var(--primary-deep);
  border-color: var(--accent-gold);
}

.btn-secondary {
  background: transparent;
  color: var(--primary);
  border-color: var(--primary);
}
:root[data-theme="dark"] .btn-secondary {
  color: var(--text-main);
}
.btn-secondary:hover {
  background: rgba(29, 78, 137, 0.08);
  border-color: var(--accent-gold);
}

.btn-gold {
  background: linear-gradient(135deg, var(--accent-gold), #E4BE6A);
  color: #26221B;
  position: relative;
  overflow: hidden;
}
.btn-gold:hover {
  background: linear-gradient(135deg, #D9AE5F, var(--accent-gold));
}
.btn-gold::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(120deg, transparent 20%, rgba(255,255,255,0.55) 50%, transparent 80%);
  transform: translateX(-120%);
}
.btn-gold:hover::after {
  animation: shimmer-sweep 1.5s ease;
}
/* Section 7: on the hero specifically, the shimmer also auto-fires every 8s
   (not just on hover) so the primary CTA reads as alive at rest. */
.btn-gold.motion-shimmer-periodic::after {
  animation: shimmer-sweep-periodic 8s ease infinite;
}

.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  color: var(--text-main);
  transition: background-color 200ms ease, transform 100ms ease;
}
.btn-icon:hover {
  background: var(--bg-raised);
}
/* :hover alone never fires on a touch tap (and is unreliable/"sticky" on
   the mobile browsers that do simulate it) -- :active is what actually
   gives an installed-PWA/mobile visitor instant "yes, that registered"
   feedback the moment they press down. See nav.js's touchstart listener
   for why this needs a companion JS line to even fire on iOS. */
.btn-icon:active {
  background: var(--bg-raised);
  transform: scale(0.92);
}
.btn-icon--outline {
  border: 1px solid var(--border);
}
.btn-icon--outline:hover {
  border-color: var(--accent-gold);
}
.btn-icon.is-offline-saved {
  color: var(--secondary);
  border-color: var(--secondary);
}
/* "Notify me when live" button (Live page, push-notifications.js) once the
   visitor has notifications turned on -- filled in the accent color, same
   idea as .btn-icon.is-offline-saved above and the page's own live-dot/
   pulse-glow color, scoped to this one button so it doesn't affect
   .is-active's other, unrelated meanings elsewhere (nav links, lang chip). */
[data-action="toggle-push"].is-active {
  background: rgba(47, 164, 169, 0.14);
  color: var(--secondary);
  border-color: var(--secondary);
}
[data-action="toggle-push"] .toggle-push-icon {
  display: inline-flex;
}
[data-action="toggle-push"] .toggle-push-icon svg {
  width: 18px;
  height: 18px;
}
/* "Live notifications are enabled" confirmation (Live page, live.js's
   renderNotifyToggle() + push-notifications.js's setBellState()) -- a
   persistent, always-visible pill next to the button itself, not just a
   one-off toast, so a visitor who left and came back can still see at a
   glance that this is already on. Same accent color as the active button
   above, just as a small rounded badge instead of filling the button. */
.toggle-push-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(47, 164, 169, 0.14);
  color: var(--secondary);
  font-size: 13px;
  font-weight: 600;
}
.toggle-push-status svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}
.offline-progress-text {
  font-size: 10px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* Removing has no byte-progress to show (unlike Save's percentage text
   above), so a plain spinner is the loading state -- see offline-lectures.js
   handleToggle()'s remove branch. */
.offline-spinner {
  width: 14px;
  height: 14px;
  border: 2px solid currentColor;
  border-inline-end-color: transparent;
  border-radius: 50%;
  opacity: 0.6;
  animation: offline-spin 700ms linear infinite;
}
@keyframes offline-spin {
  to { transform: rotate(360deg); }
}

/* Larger page-level counterpart to .offline-spinner above, same spin
   animation -- used while a page's own list data is still loading (e.g. the
   Offline Library reading its saved lectures out of IndexedDB), not tied to
   the offline-save buttons specifically despite living in this section. */
.page-spinner {
  display: inline-block;
  width: 32px;
  height: 32px;
  border: 3px solid var(--border);
  border-inline-start-color: var(--primary);
  border-radius: 50%;
  animation: offline-spin 700ms linear infinite;
}

/* Book detail page's PDF reader (see features/pdf-viewer.js) -- pages
   render at their own real page-color (near-white paper), regardless of
   site theme, same as any other document viewer; the surrounding card
   just needs enough contrast in dark mode for that white paper to read as
   a distinct sheet rather than bleeding into the page background. */
.pdf-viewer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding: 20px 12px;
  border-radius: var(--radius-card);
  background: var(--bg-raised);
}
.pdf-viewer__page {
  width: 100%;
  max-width: 720px;
  line-height: 0;
  background: #fff;
  border-radius: 4px;
  overflow: hidden;
  box-shadow: 0 2px 12px rgba(var(--shadow-color), 0.18);
  display: flex;
  align-items: center;
  justify-content: center;
}
.pdf-viewer__page canvas {
  display: block;
  max-width: 100%;
  height: auto;
}
.pdf-viewer__status {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 48px 24px;
}
/* Sits inside an as-yet-unrendered .pdf-viewer__page (see pdf-viewer.js) --
   fixed, theme-independent colors rather than the usual var(--border)/
   var(--primary) pair, since this page's background is always white "paper"
   regardless of site theme; the dark-mode versions of those tokens can be
   too pale to read against that white. */
.pdf-viewer__page-spinner {
  display: inline-block;
  width: 28px;
  height: 28px;
  border: 3px solid #e5e0d5;
  border-inline-start-color: #1D4E89;
  border-radius: 50%;
  animation: offline-spin 700ms linear infinite;
}

/* Count badge on the header's "My Downloads" icon -- see
   assets/js/features/offline-lectures.js updateDownloadsBadge(). */
.header-downloads-badge {
  position: absolute;
  top: -4px;
  inset-inline-end: -4px;
  min-width: 16px;
  height: 16px;
  padding-inline: 4px;
  border-radius: 999px;
  background: var(--secondary);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.header-downloads-badge--mobile {
  position: static;
  margin-inline-start: auto;
}

/* Header icon's little "landed" bump when a flying icon (below) arrives. */
@keyframes header-downloads-bump {
  0%, 100% { transform: scale(1); }
  40% { transform: scale(1.3); }
}
#header-downloads-link.is-bump {
  animation: header-downloads-bump 450ms ease;
}

/* "Save Offline" -> header icon fly animation -- a full clone of the
   lecture card that travels from its own position to the header's
   downloads icon, shrinking away as it goes. Purely decorative
   (pointer-events:none, data-action stripped off its buttons so it's
   inert) and removed from the DOM once its Web Animations API animation
   finishes (see animateSaveToDownloads()). transform-origin stays at the
   default center so the scale-down reads as "flying into" the icon rather
   than collapsing toward a corner. */
.offline-fly-card {
  z-index: 200;
  pointer-events: none;
  box-shadow: 0 20px 45px rgba(var(--shadow-color), 0.28);
}
.btn-icon.is-muted {
  color: var(--text-muted);
  opacity: 0.55;
}

/* =========================================================
   Header — Section 9.1
   ========================================================= */
.site-header {
  /* Fixed (not sticky) so it stays pinned at the top on every device --
     see the --header-height comment on body in base.css for why sticky was
     dropped. body's padding-top reserves the space this removes from
     normal flow. */
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  background: var(--bg-surface);
  border-bottom: 1px solid var(--border);
  border-top: 3px solid transparent;
  border-image: linear-gradient(90deg, var(--accent-gold), var(--accent-turquoise-light), var(--accent-gold)) 1;
  transition: background-color 300ms ease, box-shadow 200ms ease;
}
.site-header.is-scrolled {
  box-shadow: 0 4px 20px rgba(var(--shadow-color), 0.08);
  backdrop-filter: saturate(150%) blur(6px);
}

.nav-link {
  font-size: 15px;
  font-weight: 500;
  color: var(--text-main);
  padding-block: 8px;
  position: relative;
}
.nav-link::after {
  content: '';
  position: absolute;
  inset-inline-start: 0;
  bottom: 0;
  height: 2px;
  width: 0;
  background: var(--accent-gold);
  transition: width 200ms ease;
}
.nav-link:hover::after,
.nav-link.is-active::after {
  width: 100%;
}
.nav-link.is-active {
  color: var(--primary-deep);
}
:root[data-theme="dark"] .nav-link.is-active {
  color: var(--secondary);
}

.lang-chip {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: 999px;
  overflow: hidden;
  font-size: 13px;
  font-weight: 600;
}
.lang-chip button {
  padding: 6px 14px;
  color: var(--text-muted);
  transition: background-color 200ms ease, color 200ms ease;
}
.lang-chip button:active {
  background: var(--bg-raised);
}
.lang-chip button.is-active {
  background: var(--primary);
  color: var(--bg-surface);
}
/* On mobile there's no room to show "current language, highlighted" next
   to an unhighlighted alternative -- just show the one button that switches
   to the other language, since that's the only actionable choice anyway. */
@media (max-width: 639px) {
  .lang-chip button.is-active {
    display: none;
  }
}

.live-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(47, 164, 169, 0.14);
  color: var(--secondary);
  font-size: 13px;
  font-weight: 600;
  transition: background-color 150ms ease, transform 100ms ease;
}
/* Header badge only (see header.js/live-poll.js) -- now a real link to the
   Live page, so it needs the same tap/press feedback as every other nav
   control (see nav.js's touchstart listener for why iOS needs that). The
   Live page's own "LIVE" badge in its player panel stays a plain, non-link
   <span> and is unaffected since it never gets this class combined with a
   surrounding <a>. */
a.live-pill:hover, a.live-pill:active {
  background: rgba(47, 164, 169, 0.28);
}
a.live-pill:active {
  transform: scale(0.95);
}
.live-dot {
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--secondary);
  animation: pulse-glow 2s infinite;
}

.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: var(--bg-surface);
  transform: translateX(100%);
  transition: transform 300ms ease;
  overflow-y: auto;
}
[dir="rtl"] .mobile-menu {
  transform: translateX(-100%);
}
.mobile-menu.is-open {
  transform: translateX(0);
}
.mobile-menu__row {
  display: block;
  padding: 18px 24px;
  font-size: 20px;
  border-bottom: 1px solid var(--border);
  min-height: 48px;
  transition: background-color 100ms ease;
}
/* Covers both plain link rows and .mobile-menu__toggle (the accordion
   buttons below share this same class) -- instant press feedback on tap,
   see nav.js's touchstart listener for why iOS needs that companion line. */
.mobile-menu__row:active {
  background: var(--bg-raised);
}
.mobile-menu__row--child {
  padding-inline-start: 44px;
  font-size: 18px;
}
.mobile-menu__row--grandchild {
  padding-inline-start: 64px;
  font-size: 14px;
  color: var(--text-muted);
  opacity: 0.85;
}

@media (max-width: 767px) {
  .site-header__shaykh-name { font-size: 12px; }
}

/* Mobile accordion for items with children -- collapsed by default, only
   auto-expanded (via the .is-open class rendered server-side) when the
   current page is inside it. max-height (not display/height) is what's
   transitioned since "auto" heights can't be animated directly; the cap is
   just generously larger than any real submenu ever gets. */
.mobile-menu__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  background: none;
  /* border:0 alone would also wipe out .mobile-menu__row's border-bottom
     divider (same element, same shorthand-clobbers-longhand issue as the
     font-size one below) -- reset all sides, then explicitly put the
     bottom one back so the row divider always shows regardless of which
     other classes end up combined with this one. */
  border: 0;
  border-bottom: 1px solid var(--border);
  /* font-family only, deliberately not the "font" shorthand -- that resets
     font-size too, which would silently override .mobile-menu__row's 20px
     (buttons don't inherit font-size from the page by default the way a/p
     do, so this still needs SOME reset, just not one that wipes the size). */
  font-family: inherit;
  font-weight: inherit;
  color: inherit;
  text-align: inherit;
  cursor: pointer;
}
.mobile-submenu-caret {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  transition: transform 200ms ease;
}
.mobile-submenu-group.is-open > .mobile-menu__toggle .mobile-submenu-caret {
  transform: rotate(180deg);
}
.mobile-submenu-panel {
  max-height: 0;
  overflow: hidden;
  transition: max-height 250ms ease;
}
.mobile-submenu-group.is-open > .mobile-submenu-panel {
  max-height: 1000px;
}

/* Header nav dropdown (admin-manageable parent/child menu items) -- opens
   on mouse hover or keyboard focus (:focus-within covers Tab navigation),
   no click/JS involved. .dropdown-menu is a DOM descendant of
   .nav-item-dropdown so hovering it keeps the ancestor's :hover alive even
   though it's positioned outside its visual box; the ::before pseudo-
   element below bridges the small visual gap between trigger and panel so
   moving the mouse diagonally across it doesn't flicker the menu closed. */
.nav-item-dropdown {
  position: relative;
  display: inline-flex;
  align-items: center;
}
.dropdown-toggle {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
}
.dropdown-caret {
  width: 12px;
  height: 12px;
  transform: rotate(90deg);
  transition: transform 200ms ease;
}
.nav-item-dropdown:hover .dropdown-caret,
.nav-item-dropdown:focus-within .dropdown-caret {
  transform: rotate(-90deg);
}
.dropdown-menu {
  position: absolute;
  top: 100%;
  inset-inline-start: 0;
  margin-top: 10px;
  min-width: 220px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: 0 12px 32px rgba(var(--shadow-color), 0.14);
  padding: 8px;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 180ms ease, transform 180ms ease, visibility 180ms;
  z-index: 55;
}
.dropdown-menu::before {
  content: '';
  position: absolute;
  top: -10px;
  inset-inline: 0;
  height: 10px;
}
.nav-item-dropdown:hover .dropdown-menu,
.nav-item-dropdown:focus-within .dropdown-menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.dropdown-menu__link {
  display: block;
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 500;
  color: var(--text-main);
  transition: background-color 150ms ease;
}
.dropdown-menu__link:hover,
.dropdown-menu__link.is-active {
  background: var(--bg-raised);
  color: var(--primary-deep);
}

/* Second-level flyout (category -> subcategories), e.g. inside an
   auto_categories-populated "Lectures" dropdown. Same hover/focus-within
   mechanics as .dropdown-menu itself, one level deeper -- the category
   link doubles as the hover/focus target, no separate toggle button. */
.dropdown-menu__item-wrap {
  position: relative;
}
.dropdown-menu__link--has-sub {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.submenu-caret {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  color: var(--text-muted);
}
[dir="rtl"] .submenu-caret {
  transform: scaleX(-1);
}
.dropdown-submenu {
  position: absolute;
  top: 0;
  inset-inline-start: 100%;
  margin-inline-start: 8px;
  min-width: 200px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: 0 12px 32px rgba(var(--shadow-color), 0.14);
  padding: 8px;
  opacity: 0;
  visibility: hidden;
  transform: translateX(-6px);
  transition: opacity 180ms ease, transform 180ms ease, visibility 180ms;
  z-index: 56;
}
.dropdown-submenu::before {
  content: '';
  position: absolute;
  top: 0;
  inset-inline-start: -8px;
  width: 8px;
  height: 100%;
}
[dir="rtl"] .dropdown-submenu {
  transform: translateX(6px);
}
.dropdown-menu__item-wrap:hover .dropdown-submenu,
.dropdown-menu__item-wrap:focus-within .dropdown-submenu {
  opacity: 1;
  visibility: visible;
  transform: translateX(0);
}

/* =========================================================
   Sections / bands
   ========================================================= */
.band-raised {
  background: var(--bg-raised);
}

.pattern-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 0;
}
.pattern-layer svg {
  position: absolute;
  inset: -10%;
  width: 120%;
  height: 120%;
  color: var(--pattern-tint);
  opacity: var(--pattern-opacity);
}

.section-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--accent-gold);
}
.section-eyebrow::before,
.section-eyebrow::after {
  content: '';
  width: 20px;
  height: 1px;
  background: var(--accent-gold);
}

/* Tile divider — Section 6 */
.tile-divider {
  height: 20px;
  width: 100%;
  background-repeat: repeat-x;
  background-size: 40px 20px;
  opacity: 0.6;
}

/* Tile-band divider (patterns.js tileBand()) — a real-opacity gold + turquoise
   ornament band used between major sections, not the near-invisible girih
   texture used behind text. */
.tile-band {
  width: 100%;
  height: 22px;
  display: block;
}
.tile-band svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* Large soft turquoise dome backdrop (patterns.js domeGlow()) */
.dome-glow-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 0;
}
.dome-glow-layer svg {
  position: absolute;
  top: -10%;
  inset-inline-start: 50%;
  transform: translateX(-50%);
  width: 140%;
  max-width: 900px;
  height: auto;
}

/* =========================================================
   Cards — Lecture / Book
   ========================================================= */
.card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  transition: transform 250ms ease, box-shadow 250ms ease, border-color 250ms ease;
}
.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 16px 32px rgba(var(--shadow-color), 0.12);
  border-color: var(--accent-gold);
}

.lecture-card__thumb {
  display: block;
  aspect-ratio: 16/7;
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  overflow: hidden;
  position: relative;
}
@media (max-width: 767px) {
  .lecture-card__thumb {
    aspect-ratio: 16/4.5;
  }
}
.lecture-hero__thumb {
  width: 160px;
  aspect-ratio: 16/10;
  border-radius: var(--radius-btn);
  overflow: hidden;
  flex-shrink: 0;
}
.lecture-hero__thumb svg {
  width: 100%;
  height: 100%;
  display: block;
}
@media (max-width: 639px) {
  .lecture-hero__thumb {
    width: 100%;
    max-width: 240px;
  }
  /* Play alone on its own full-width line, Download + Share together on
     the line below -- three same-size items in one row left Play too
     cramped next to two icon buttons at this width. */
  .lecture-hero__actions {
    flex-wrap: wrap;
    justify-content: center;
    width: 100%;
  }
  .lecture-hero__actions .btn-primary {
    flex: 1 1 100%;
  }
}
.lecture-card__category-badge {
  position: absolute;
  top: 10px;
  inset-inline-start: 10px;
  max-width: calc(100% - 20px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  background: rgba(13, 26, 44, 0.55);
  border-color: transparent;
  color: #F2EDE1;
  backdrop-filter: blur(2px);
}

/* Bottom-left, mirroring .duration-badge which sits bottom-right -- capped
   narrower than the category badge above so a long series name can't run
   under the duration pill on the opposite corner. */
.lecture-card__series-badge {
  position: absolute;
  bottom: 10px;
  inset-inline-start: 10px;
  max-width: 55%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  background: rgba(13, 26, 44, 0.55);
  border-color: transparent;
  color: #F2EDE1;
  backdrop-filter: blur(2px);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-muted);
  background: var(--bg-raised);
  border: 1px solid var(--border);
}

.chip-removable {
  border-color: var(--accent-gold);
  color: var(--text-main);
}

.chip svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
}

.chip-remove-btn {
  display: inline-flex;
  margin-inline-start: 2px;
}
.chip-remove-btn svg {
  width: 11px;
  height: 11px;
}

/* =========================================================
   Cards — Events (torn-calendar date block)
   ========================================================= */
.event-card {
  display: flex;
  overflow: hidden;
}
.event-card__date {
  flex: 0 0 84px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 16px 8px;
  background: var(--primary);
  color: var(--bg-surface);
  border-radius: calc(var(--radius-card) - 1px) 0 0 calc(var(--radius-card) - 1px);
}
.event-card__date--past {
  background: var(--bg-raised);
  color: var(--text-muted);
}
.event-card__date-month {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.event-card__date-day {
  font-size: 28px;
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
/* A same-month range ("8–12") is wider than a single day -- shrink to fit
   the fixed 84px-wide tile instead of overflowing or wrapping. */
.event-card__date-day.is-range {
  font-size: 19px;
}
.event-card__date-year {
  font-size: 11px;
  opacity: 0.85;
}
.event-card__body {
  flex: 1;
  min-width: 0;
  padding: 18px 20px;
}
.event-card__tour-chip {
  color: var(--accent-gold);
  border-color: var(--accent-gold);
}
/* One color per event type so the type is recognizable at a glance without
   reading the label -- picked to stay distinct from the gold tour-name chip
   above. Colors reuse existing theme tokens, so they auto-adjust for dark mode. */
.event-card__type-badge--tour {
  color: var(--accent-terracotta);
  border-color: var(--accent-terracotta);
}
.event-card__type-badge--majlis {
  color: var(--primary);
  border-color: var(--primary);
}
.event-card__type-badge--workshop {
  color: var(--secondary);
  border-color: var(--secondary);
}
.event-card__type-badge--lecture {
  color: var(--accent-turquoise-deep);
  border-color: var(--accent-turquoise-deep);
}
.event-card__type-badge--other {
  color: var(--text-muted);
  border-color: var(--text-muted);
}
.event-card__actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  flex-wrap: wrap;
}
.event-card__actions .btn-primary {
  min-height: 36px;
  padding-inline: 14px;
  font-size: 13px;
}
.event-card__past-note {
  margin-top: 14px;
  font-size: 13px;
  color: var(--text-muted);
  font-style: italic;
}
/* Date badge stays on the left (row layout) at every width, mobile
   included -- it used to stack on top as a horizontal bar below 480px,
   which is what's being deliberately removed here. */

/* =========================================================
   Filter bar — Section 9.6
   ========================================================= */
.filter-bar {
  padding: 22px;
  border-radius: var(--radius-card);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  box-shadow: 0 8px 24px rgba(var(--shadow-color), 0.06);
}
.filter-control {
  min-height: 48px;
  padding: 0 14px;
  border-radius: var(--radius-btn);
  border: 1px solid var(--border);
  background: var(--bg-raised);
  color: var(--text-main);
  font-size: 14px;
  min-width: 150px;
  flex: 1 1 150px;
}
@media (max-width: 640px) {
  .filter-control {
    flex: 1 1 100%;
  }
}
.filter-search {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 48px;
  padding-inline: 14px;
  border-radius: var(--radius-btn);
  border: 1px solid var(--border);
  background: var(--bg-raised);
  color: var(--text-muted);
  flex: 2 1 220px;
}
.filter-search svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}
.filter-search input {
  border: none;
  background: none;
  outline: none;
  width: 100%;
  min-height: 46px;
  color: var(--text-main);
  font-size: 15px;
}
.filter-search:focus-within {
  border-color: var(--accent-gold);
  background: var(--bg-surface);
}
/* The wrapper's own :focus-within border/background above is already the
   focus indicator -- suppress the global :focus-visible outline+glow on
   the input itself so it doesn't also draw its own mismatched ring inside
   the pill. */
.filter-search input:focus-visible {
  outline: none;
  box-shadow: none;
}

/* Icon-prefixed native <select> — same pill treatment as .filter-search,
   with a custom chevron since the native select arrow is hidden. */
.filter-select {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 48px;
  padding-inline: 14px;
  border-radius: var(--radius-btn);
  border: 1px solid var(--border);
  background: var(--bg-raised);
  color: var(--text-muted);
  flex: 1 1 170px;
  cursor: pointer;
  transition: border-color 200ms ease, background-color 200ms ease;
}
.filter-select:hover,
.filter-select:focus-within {
  border-color: var(--accent-gold);
  background: var(--bg-surface);
}
.filter-select select:focus-visible {
  outline: none;
  box-shadow: none;
}
.filter-select svg:first-child {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}
.filter-select select {
  appearance: none;
  -webkit-appearance: none;
  border: none;
  background: none;
  outline: none;
  width: 100%;
  min-height: 46px;
  color: var(--text-main);
  font-size: 14px;
  cursor: pointer;
}
.filter-select__chevron {
  display: inline-flex;
  flex-shrink: 0;
  width: 14px;
  height: 14px;
  color: var(--text-muted);
  transform: rotate(90deg);
}
@media (max-width: 640px) {
  .filter-select {
    flex: 1 1 100%;
  }
}
/* Keep category/place/year/clear on a single row on desktop instead of
   wrapping. #filter-controls-row (id specificity) reliably overrides
   Tailwind's .flex-wrap utility class regardless of stylesheet order.
   Each select gets a smaller fixed-ish basis (not the 170px mobile
   minimum) so all three plus Clear comfortably share one row. */
@media (min-width: 900px) {
  #filter-controls-row {
    flex-wrap: nowrap;
  }
  #filter-controls-row .filter-search {
    flex: 1 1 160px;
    min-width: 0;
  }
  #filter-controls-row .filter-select {
    flex: 1 1 120px;
    min-width: 0;
  }
  #filter-controls-row .filter-select select {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  #filter-controls-row #filter-clear {
    flex-shrink: 0;
  }
}

/* Mobile: filters collapsed by default behind a toggle button so the
   results grid isn't pushed below the fold by four stacked full-width
   controls. Desktop/tablet keep the bar always open (no toggle shown). */
.filter-toggle-btn {
  display: none;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 44px;
  padding-inline: 4px;
  background: none;
  border: none;
  color: var(--text-main);
  font-size: 15px;
  font-weight: 600;
}
.filter-toggle-btn svg:first-child {
  width: 18px;
  height: 18px;
  color: var(--text-muted);
}
.filter-toggle-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding-inline: 5px;
  border-radius: 999px;
  background: var(--secondary);
  color: var(--bg-surface);
  font-size: 12px;
  font-weight: 700;
}
.filter-toggle-chevron {
  display: inline-flex;
  margin-inline-start: auto;
  width: 14px;
  height: 14px;
  color: var(--text-muted);
  transform: rotate(90deg);
  transition: transform 200ms ease;
}
.filter-toggle-btn[aria-expanded="true"] .filter-toggle-chevron {
  transform: rotate(-90deg);
}
@media (max-width: 767px) {
  .filter-bar {
    padding: 14px 16px;
  }
  .filter-toggle-btn {
    display: flex;
    min-height: 32px;
  }
  #filter-collapsible {
    display: none;
    margin-top: 14px;
  }
  #filter-collapsible.is-open {
    display: block;
  }
  /* iOS Safari auto-zooms the page on focus if an input's font-size is
     under 16px -- keep these at 16px on mobile only (desktop keeps its
     smaller, denser sizing) so tapping a filter field doesn't zoom in. */
  .filter-search input,
  .filter-select select {
    font-size: 16px;
  }
}

/* Live page schedule table — Section 10.2 */
.schedule-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}
.schedule-table th {
  text-align: start;
  padding: 10px 12px;
  color: var(--text-muted);
  font-weight: 600;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  border-bottom: 2px solid var(--accent-gold);
}
.schedule-table td {
  padding: 12px;
  border-bottom: 1px solid var(--border);
}
.schedule-table tr:last-child td {
  border-bottom: none;
}

.duration-badge {
  position: absolute;
  inset-inline-end: 10px;
  bottom: 10px;
  padding: 3px 8px;
  border-radius: 6px;
  font-size: 12px;
  font-weight: 600;
  background: rgba(13, 26, 44, 0.72);
  color: #F2EDE1;
  backdrop-filter: blur(2px);
}

/* Same look, but flowed inline in a chips row (e.g. lecture list-view) instead
   of overlaid on a thumbnail -- neutralize the corner-positioning. Also a
   touch lighter than the overlay version, since here it sits on the page's
   own background rather than needing to stand out against a photo. */
.duration-badge--inline {
  position: static;
  background: rgba(13, 26, 44, 0.62);
}

/* line-height here (not inline on each element) so [dir="rtl"] h1-h4 rules
   in rtl.css -- which give Noto Nastaliq Urdu the taller line-height its
   tall ascenders/descenders need -- can win by specificity. An inline
   line-height on the element would always beat those, clipping Urdu titles
   top/bottom against this box's overflow:hidden. */
.line-clamp-2 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.35;
}
.line-clamp-1 {
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.3;
}

/* About page biography — bolded names/institutions read in the primary blue.
   !important because pasted-from-Word/Docs content (a real thing admins do
   in the Quill editor, see quill-fields.js) commonly carries its own
   inline color/background-color on every <p>/<span> -- e.g. literal
   "color: rgb(0, 0, 0)" -- which otherwise wins over any stylesheet rule
   regardless of selector specificity and stays flat black even in dark
   mode. This container's text must always follow the theme, full stop --
   admin-pasted colors are discarded rather than honored. */
.biography-text, .biography-text * {
  color: var(--text-main) !important;
  background-color: transparent !important;
}
.biography-text strong {
  color: var(--primary) !important;
}

/* Both biography fields now come from a WYSIWYG editor (Quill, admin side)
   whose output is real <p>/<ul>/<ol>/<h2-3>/<a> tags -- but this site's own
   Tailwind preflight reset (tailwind.min.css) zeroes every one of those
   elements' default margins/list-style globally, and Quill's own spacing
   rules (.ql-editor p etc., in quill.snow.css) only ship to the admin page,
   never here. Without these, the biography text would render as one
   unbroken run with no paragraph gaps and no bullets. */
.biography-text p { margin-bottom: 1.25rem; }
.biography-text p:last-child { margin-bottom: 0; }
.biography-text h2, .biography-text h3, .biography-text h4 { margin: 1.5rem 0 0.75rem; font-weight: 700; }
.biography-text ul, .biography-text ol { margin: 0 0 1.25rem; padding-inline-start: 1.5rem; }
.biography-text ul { list-style: disc; }
.biography-text ol { list-style: decimal; }
.biography-text a { color: var(--primary) !important; text-decoration: underline; }

/* Bayanaat listing — grid/list view toggle */
.lecture-results-grid {
  display: grid;
  gap: 24px;
  /* auto-fill (not auto-fit) so cards keep their natural width when a
     filtered result set is smaller than a full row, instead of the
     existing cards stretching to fill the leftover space. */
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
.lecture-results-grid.is-list-view {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.view-toggle {
  display: inline-flex;
  gap: 4px;
  padding: 4px;
  border-radius: var(--radius-btn);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  flex-shrink: 0;
}
.view-toggle__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 8px;
  color: var(--text-muted);
  transition: background-color 200ms ease, color 200ms ease;
}
.view-toggle__btn svg {
  width: 18px;
  height: 18px;
}
.view-toggle__btn.is-active {
  background: var(--bg-surface);
  color: var(--primary);
  box-shadow: 0 2px 6px rgba(var(--shadow-color), 0.1);
}
:root[data-theme="dark"] .view-toggle__btn.is-active {
  color: var(--accent-turquoise-light);
}

/* Lecture list-view row */
.lecture-list-row {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px;
}
.lecture-list-row__main {
  display: flex;
  align-items: center;
  gap: 16px;
  flex: 1;
  min-width: 0;
}
.lecture-list-row__thumb {
  display: block;
  position: relative;
  width: 130px;
  aspect-ratio: 16/10;
  border-radius: var(--radius-btn);
  overflow: hidden;
  flex-shrink: 0;
}
.lecture-list-row__thumb svg {
  width: 100%;
  height: 100%;
  display: block;
}
.lecture-list-row__body {
  flex: 1;
  min-width: 0;
}
.lecture-list-row__actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.lecture-list-row__play-label {
  display: none;
}
@media (min-width: 640px) {
  .lecture-list-row__play {
    width: auto;
    padding-inline: 16px;
    border-radius: var(--radius-btn);
    gap: 8px;
  }
  .lecture-list-row__play-label {
    display: inline;
    font-size: 14px;
    font-weight: 600;
    white-space: nowrap;
  }
}
/* Below 640px there's no room for thumb + text + a column of 3 icon
   buttons side by side without crushing the title -- stack the row
   into [thumb+text] on top and a full-width horizontal action bar
   below instead (same pattern as the grid card's action row), and
   give Play its label back since the bar now has the full row width. */
@media (max-width: 640px) {
  .lecture-list-row {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }
  .lecture-list-row__thumb {
    display: none;
  }
  .lecture-list-row__actions {
    gap: 8px;
  }
  .lecture-list-row__play {
    flex: 1;
    width: auto;
    padding-inline: 16px;
    border-radius: var(--radius-btn);
    gap: 8px;
  }
  .lecture-list-row__play-label {
    display: inline;
    font-size: 14px;
    font-weight: 600;
  }
}

.book-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.book-card__cover {
  display: block;
  position: relative;
  aspect-ratio: 3 / 3.7;
  overflow: hidden;
}
.book-card__category-badge {
  position: absolute;
  top: 10px;
  inset-inline-start: 10px;
  max-width: calc(100% - 60px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  background: rgba(13, 26, 44, 0.55);
  border-color: transparent;
  color: #F2EDE1;
  backdrop-filter: blur(2px);
}
.book-card__lang-badge {
  position: absolute;
  top: 10px;
  inset-inline-end: 10px;
  background: rgba(13, 26, 44, 0.55);
  border-color: transparent;
  color: #F2EDE1;
  backdrop-filter: blur(2px);
}
.book-card__cover svg,
.book-card__cover img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  transition: transform 300ms ease;
}
.book-card:hover .book-card__cover svg,
.book-card:hover .book-card__cover img {
  transform: scale(1.05);
}
.book-card__body {
  padding: 12px 14px 14px;
  flex: 1;
  display: flex;
  flex-direction: column;
}

/* Book-detail "Related Books" -- grid on desktop, same swipeable
   horizontal scroll-snap row as the Home page's Recent Books section on
   mobile, so a handful of related titles don't stack into a long vertical
   scroll on a phone. */
.related-books-grid {
  display: grid;
  gap: 32px;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
@media (max-width: 767px) {
  .related-books-grid {
    display: flex;
    gap: 16px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    padding-bottom: 8px;
  }
  .related-books-grid > * {
    width: 240px;
    flex-shrink: 0;
    scroll-snap-align: start;
  }
}

/* =========================================================
   Quote block — Section 9.9
   ========================================================= */
.quote-card {
  position: relative;
  padding: 48px 40px;
  border-radius: var(--radius-card);
  background: var(--bg-surface);
  border: 1px solid var(--border);
  text-align: center;
}
.quote-card::before,
.quote-card::after {
  content: '';
  position: absolute;
  width: 28px;
  height: 28px;
  border: 1.5px solid var(--accent-gold);
}
.quote-card::before {
  top: 16px;
  inset-inline-start: 16px;
  border-inline-end: none;
  border-bottom: none;
}
.quote-card::after {
  bottom: 16px;
  inset-inline-end: 16px;
  border-inline-start: none;
  border-top: none;
}

/* =========================================================
   Shajrah teaser cards
   ========================================================= */
.silsila-card {
  padding: 28px 20px;
  border-radius: var(--radius-card);
  border: 1px solid var(--border);
  background: var(--bg-surface);
  text-align: center;
  transition: transform 250ms ease, box-shadow 250ms ease;
}
.silsila-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 16px 32px rgba(var(--shadow-color), 0.12);
}
.silsila-card__motif {
  width: 56px;
  height: 56px;
  padding: 12px;
  margin-inline: auto;
  border-radius: 999px;
  color: var(--accent-turquoise-deep);
  background: radial-gradient(circle, var(--accent-turquoise-light) 0%, transparent 72%);
  opacity: 1;
}
:root[data-theme="dark"] .silsila-card__motif {
  color: var(--accent-turquoise-light);
  background: radial-gradient(circle, rgba(127, 224, 234, 0.28) 0%, transparent 72%);
}
.silsila-card:hover .silsila-card__motif {
  color: var(--accent-gold);
}

/* Shajrah Taiba tabs (shajrah.html) — 4 silsilas on one page */
.shajrah-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 6px;
  border-radius: 999px;
  background: var(--bg-raised);
  border: 1px solid var(--border);
}
@media (max-width: 640px) {
  .shajrah-tabs {
    border-radius: var(--radius-card);
  }
}
.shajrah-tab {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  justify-content: center;
  min-height: 48px;
  padding-inline: 18px;
  border-radius: 999px;
  color: var(--text-muted);
  font-weight: 600;
  font-size: 14px;
  transition: background-color 200ms ease, color 200ms ease;
}
.shajrah-tab:hover {
  color: var(--text-main);
}
.shajrah-tab.is-active {
  background: var(--bg-surface);
  color: var(--primary);
  box-shadow: 0 4px 12px rgba(var(--shadow-color), 0.1);
}
:root[data-theme="dark"] .shajrah-tab.is-active {
  color: var(--accent-turquoise-light);
}
.shajrah-tab__icon {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
}
.shajrah-tab__icon svg {
  width: 100%;
  height: 100%;
}

/* =========================================================
   Shajrah chain — Section 9.8
   ========================================================= */
.shajrah-chain {
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
  max-width: 640px;
  margin-inline: auto;
}
.shajrah-chain::before {
  content: '';
  position: absolute;
  inset-inline-start: 19px;
  top: 8px;
  bottom: 8px;
  width: 2px;
  background: linear-gradient(var(--accent-gold), var(--accent-turquoise-light));
}
[dir="rtl"] .shajrah-chain::before {
  inset-inline-start: auto;
  inset-inline-end: 19px;
}
.shajrah-node {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding-block: 10px;
}
.shajrah-node__connector {
  position: relative;
  z-index: 1;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: var(--bg-surface);
  border: 2px solid var(--accent-gold);
  color: var(--accent-gold);
  font-weight: 700;
  font-size: 15px;
}
.shajrah-node__connector svg {
  width: 60%;
  height: 60%;
}
.shajrah-node--prophet .shajrah-node__connector {
  border-width: 3px;
  background: linear-gradient(135deg, var(--accent-gold), var(--accent-turquoise-light));
  color: var(--bg-surface);
  box-shadow: 0 0 0 4px rgba(199, 149, 44, 0.15);
}
.shajrah-node__card {
  flex: 1;
  padding: 12px 16px;
  border-radius: var(--radius-card);
  background: var(--bg-surface);
  border: 1px solid var(--border);
}
.shajrah-node--prophet .shajrah-node__card {
  border-color: var(--accent-gold);
  background: var(--bg-raised);
}
.shajrah-node__name-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: 12px;
  row-gap: 6px;
}
.shajrah-node__primary {
  font-size: 17px;
  font-weight: 600;
  line-height: 1.4;
  color: var(--text-main);
}
.shajrah-node--prophet .shajrah-node__primary {
  font-size: 19px;
  font-weight: 800;
  color: var(--primary);
}
/* Place badge — flag + city, country the figure is traditionally
   associated with. The badge itself carries dir="ltr" (flag + Latin/Urdu
   text always reads the same way internally), but that same dir="ltr"
   also hijacks margin-inline-start's resolution to always mean "physical
   left/push right" -- regardless of the page's real direction. So on an
   Urdu/RTL page margin-inline-start:auto still pushed it right instead of
   flipping to the left with everything else. Fixed with an explicit
   physical-property override keyed off the page's real [dir="rtl"]
   (unaffected by the badge's own local dir override) so English keeps the
   badge pinned right and Urdu pins it left -- in both cases whether it
   shares the line with a short name or wraps onto its own line. */
.shajrah-node__place {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
  white-space: nowrap;
  flex-shrink: 0;
  margin-inline-start: auto;
}
[dir="rtl"] .shajrah-node__place {
  margin-inline-start: 0;
  margin-right: auto;
}
.shajrah-node__place-flag {
  font-size: 14px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
}
.shajrah-node__place-flag-img {
  display: block;
  width: 16px;
  height: 12px;
  border-radius: 2px;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08);
  object-fit: cover;
}
.shajrah-node--continuation {
  padding-block: 6px;
}
.shajrah-node--continuation .shajrah-node__connector {
  opacity: 0.5;
}
.shajrah-node__card--continuation {
  display: flex;
  align-items: center;
  min-height: 40px;
  font-style: italic;
  font-size: 14px;
  color: var(--text-muted);
  background: none;
  border: none;
  padding: 0;
}

/* =========================================================
   Global Sticky Audio Player — Section 9.5
   ========================================================= */
.player-bar {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 70;
  background: var(--bg-surface);
  border-top: 2px solid var(--accent-gold);
  box-shadow: 0 -8px 24px rgba(var(--shadow-color), 0.1);
  transform: translateY(100%);
  transition: transform 300ms ease;
}
.player-bar.is-visible {
  transform: translateY(0);
}
/* On mobile the row is tight (artwork + title + play + ⋯ already compete
   for space), so Close moves out of it entirely -- a small floating badge
   straddling the bar's own top edge instead, solid background so it reads
   clearly against whatever's behind it. .player-bar's own position:fixed
   is what this anchors to. inset-inline-end (not right) so it mirrors to
   the left in RTL/Urdu like every other edge-anchored element on this site. */
@media (max-width: 767px) {
  .player-bar__close {
    position: absolute;
    /* -4px (not 0) so the badge clears the row's own icon buttons below it
       (More sits right underneath, at the same edge) instead of nearly
       touching them -- straddling the bar's top edge alone left only ~3px
       of gap. */
    top: -4px;
    inset-inline-end: 16px;
    transform: translateY(-50%);
    width: 30px;
    height: 30px;
    background: var(--bg-surface);
    border: 1px solid var(--border);
    box-shadow: 0 4px 12px rgba(var(--shadow-color), 0.18);
    z-index: 2;
    /* Floats above wherever .player-bar's own top edge currently is
       (top offset + this badge's own -50% translate) -- when the bar is
       hidden, that edge sits exactly at the viewport's bottom (its own
       translateY(100%) is only 100% of ITS OWN height, unrelated to this
       child's extra offset), so the badge would otherwise still poke into
       view on its own. Hidden explicitly instead of relying on the
       parent's hide-transform to coincidentally cover it too. */
    opacity: 0;
    pointer-events: none;
    transition: opacity 200ms ease;
  }
  .player-bar.is-visible .player-bar__close {
    opacity: 1;
    pointer-events: auto;
  }
  .player-bar__close svg {
    width: 16px;
    height: 16px;
  }
  /* Sits directly above .mobile-bottom-nav instead of underneath/overlapping
     it -- same --bottom-nav-height custom property that bar uses for its
     own height, so the two stay in sync by construction. Scoped to
     .is-visible specifically (not .player-bar alone) -- the hide state's
     translateY(100%) is only ever 100% of the bar's OWN height, so shifting
     its resting "bottom" up while ALSO hidden would leave that same 60px
     gap visible year-round instead of fully off-screen. */
  .player-bar.is-visible {
    bottom: var(--bottom-nav-height, 60px);
  }
  /* Without this, a long lecture title (the only flexible sibling in the
     row) squeezes these two fixed-size circular icon buttons narrower than
     their own 40x40 -- flex-shrink defaults to 1 on every flex child, and
     an inline width alone doesn't opt out of that. */
  #player-playpause-mobile,
  #player-more {
    flex-shrink: 0;
  }
}

/* =========================================================
   Mobile bottom nav — fixed app-style tab bar (Home/Live/Audios/
   Offline Library/More), mobile only. See assets/js/features/bottom-nav.js.
   ========================================================= */
.mobile-bottom-nav {
  display: none;
}
@media (max-width: 767px) {
  .mobile-bottom-nav {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    height: var(--bottom-nav-height, 60px);
    z-index: 55;
    background: var(--bg-surface);
    border-top: 1px solid var(--border);
    box-shadow: 0 -4px 16px rgba(var(--shadow-color), 0.08);
    /* iOS Safari in particular repaints position:fixed elements a frame or
       two late during active touch-scroll momentum instead of keeping them
       glued to the viewport, so the bar visibly lags/rides up with the
       page until scrolling settles. Promoting it to its own compositor
       layer (same trick as any "fixed nav jumps on scroll" fix) keeps it
       glued the whole time instead of only once scrolling stops. */
    transform: translateZ(0);
    -webkit-transform: translateZ(0);
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
  }
}
.mobile-bottom-nav__item {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-width: 0;
  padding: 6px 2px 8px;
  border: none;
  background: none;
  color: var(--text-muted);
  font-family: inherit;
  transition: transform 100ms ease;
}
/* This bar is the primary nav surface on mobile/installed-PWA, exactly
   where "did my tap register?" was reported -- instant press feedback, see
   nav.js's touchstart listener for why iOS needs that companion line. */
.mobile-bottom-nav__item:active {
  transform: scale(0.9);
  opacity: 0.7;
}
.mobile-bottom-nav__item svg {
  width: 21px;
  height: 21px;
}
.mobile-bottom-nav__item .mobile-bottom-nav__label {
  font-size: 10.5px;
  font-weight: 500;
  line-height: 1.2;
  text-align: center;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mobile-bottom-nav__item.is-active {
  color: var(--primary);
}
:root[data-theme="dark"] .mobile-bottom-nav__item.is-active {
  color: var(--accent-turquoise-light);
}
.mobile-bottom-nav__badge {
  position: absolute;
  top: 2px;
  inset-inline-start: 50%;
  margin-inline-start: 2px;
  min-width: 15px;
  height: 15px;
  padding-inline: 3px;
  border-radius: 999px;
  background: var(--secondary);
  color: var(--bg-surface);
  font-size: 9.5px;
  font-weight: 700;
  line-height: 15px;
  text-align: center;
}
/* A soft blue streak sweeps left-to-right over the gold top border every
   3.5s -- noticeable but not shouty: a light touch, not a big glow. */
.player-bar::after {
  content: '';
  position: absolute;
  top: -1.5px;
  left: 0;
  width: 110px;
  height: 2px;
  border-radius: 999px;
  background: linear-gradient(90deg, transparent, rgba(46, 111, 217, 0.7) 35%, rgba(159, 197, 247, 0.95) 50%, rgba(46, 111, 217, 0.7) 65%, transparent);
  box-shadow: 0 0 8px 1px rgba(46, 111, 217, 0.4);
  transform: translateX(-110px);
  animation: player-bar-flash 3.5s linear infinite;
  pointer-events: none;
}

.player-seek {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 4px;
  border-radius: 999px;
  background: var(--border);
  cursor: pointer;
}
.player-seek::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 999px;
  background: var(--secondary);
  border: 2px solid var(--bg-surface);
  box-shadow: 0 0 0 1px var(--secondary);
  margin-top: -5px;
}
.player-seek::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 999px;
  background: linear-gradient(to right, var(--secondary) var(--seek-pct, 0%), var(--border) var(--seek-pct, 0%));
}
[dir="rtl"] .player-seek::-webkit-slider-runnable-track {
  background: linear-gradient(to left, var(--secondary) var(--seek-pct, 0%), var(--border) var(--seek-pct, 0%));
}

.eq-bars {
  display: inline-flex;
  align-items: flex-end;
  gap: 2px;
  height: 14px;
}
.eq-bars span {
  width: 3px;
  background: var(--secondary);
  border-radius: 2px;
  animation: eq-bounce 900ms ease-in-out infinite;
}
.eq-bars span:nth-child(1) { animation-delay: 0ms; height: 40%; }
.eq-bars span:nth-child(2) { animation-delay: 150ms; height: 100%; }
.eq-bars span:nth-child(3) { animation-delay: 300ms; height: 60%; }
.eq-bars span:nth-child(4) { animation-delay: 450ms; height: 85%; }

.player-artwork {
  width: 48px;
  height: 48px;
  border-radius: 10px 10px 3px 3px / 24% 24% 3px 3px;
  overflow: hidden;
  border: 1px solid var(--border);
  flex-shrink: 0;
}

/* Fixed width on tablet/desktop (room to breathe next to the transport
   controls); on mobile those controls collapse to just one play button,
   so this flexes to fill whatever's left instead of forcing the bar to
   overflow past the close button. */
.player-title-block {
  min-width: 0;
  width: 220px;
  flex-shrink: 0;
}
@media (max-width: 767px) {
  .player-title-block {
    width: auto;
    flex: 1 1 auto;
  }
}

/* =========================================================
   Footer — Section 9.2
   ========================================================= */
.site-footer {
  position: relative;
  background: linear-gradient(160deg, var(--primary-deep) 0%, var(--accent-turquoise-deep) 130%);
  color: #F2EDE1;
  padding-top: 56px;
}
/* .font-display's own color:var(--text-main) rule (base.css) always beats
   inherited color, no matter how specific the ancestor's own rule is, since
   a direct match always outranks pure inheritance -- scope an explicit
   override here so the brand name / column headings stay legible on the
   footer's dark background instead of falling back to near-black text. */
.site-footer .font-display {
  color: #F2EDE1;
}
:root[data-theme="dark"] .site-footer {
  background: var(--bg-raised);
  border-top: 1px solid var(--border);
}
.site-footer a:hover {
  color: var(--accent-gold);
}
.footer-skyline {
  position: absolute;
  top: -1px;
  inset-inline: 0;
  width: 100%;
  height: 32px;
  color: var(--primary-deep);
  transform: translateY(-99%);
}
:root[data-theme="dark"] .footer-skyline {
  color: var(--bg-raised);
}

.whatsapp-cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  border-radius: var(--radius-btn);
  background: #25D366;
  color: #0D1A2C;
  font-weight: 600;
  font-size: 14px;
}

/* =========================================================
   Toasts
   ========================================================= */
.toast {
  position: fixed;
  bottom: 96px;
  inset-inline-end: 20px;
  z-index: 80;
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--bg-surface);
  border: 1px solid var(--accent-gold);
  border-radius: 10px;
  padding: 12px 12px 12px 16px;
  box-shadow: 0 8px 24px rgba(var(--shadow-color), 0.14);
  font-size: 14px;
  max-width: 280px;
  transform: translateY(12px);
  opacity: 0;
  transition: transform 250ms ease, opacity 250ms ease;
}
.toast__message {
  flex: 1;
  min-width: 0;
  overflow-wrap: break-word;
}
.toast__close {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--text-muted);
  opacity: 0.7;
  cursor: pointer;
}
.toast__close:hover {
  opacity: 1;
  background: var(--bg-raised);
}
.toast__close svg {
  width: 14px;
  height: 14px;
}
.toast.is-visible {
  transform: translateY(0);
  opacity: 1;
}
.toast.is-error {
  border-color: #DC2626;
  border-width: 2px;
  /* The base .toast transition (transform/opacity) would fight the
     slide-in-then-shake keyframe animation below over the same transform
     property -- turning it off here lets the animation fully own it. */
  transition: none;
}
.toast.is-error.is-visible {
  animation: toast-shake-in 500ms ease;
}
@keyframes toast-shake-in {
  0% { opacity: 0; transform: translateY(12px); }
  25% { opacity: 1; transform: translateY(0); }
  40% { transform: translateX(-8px); }
  55% { transform: translateX(8px); }
  70% { transform: translateX(-5px); }
  85% { transform: translateX(5px); }
  100% { transform: translateX(0); }
}
@media (max-width: 767px) {
  .toast {
    inset-inline: 16px;
    bottom: 84px;
    max-width: none;
  }
}

/* =========================================================
   PWA update banner -- see assets/js/sw-register.js
   ========================================================= */
.pwa-update-banner {
  /* Deliberately NOT position:fixed -- .site-header now owns the fixed
     top:0 spot itself, and body's padding-top (see base.css) already
     reserves exactly its height. Sitting in normal flow as body's first
     child means this banner renders right below the header and pushes
     everything after it down naturally, no offset math needed. */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 10px 20px;
  background: var(--primary);
  color: #fff;
  font-size: 14px;
  text-align: center;
  opacity: 0;
  transition: opacity 250ms ease;
}
.pwa-update-banner.is-visible {
  opacity: 1;
}
.pwa-update-banner__btn {
  min-height: 32px;
  padding-block: 4px;
  padding-inline: 16px;
  font-size: 13px;
  background: #fff;
  color: var(--primary);
}

/* Same normal-flow reasoning as .pwa-update-banner (renders right below
   the fixed .site-header, in the space body's padding-top reserves for
   it) -- accent-gold instead of primary-blue so it reads as an optional
   suggestion rather than the update banner's "you should act on this"
   tone. */
.pwa-install-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 10px 40px 10px 20px;
  position: relative;
  background: var(--accent-gold);
  color: #fff;
  font-size: 14px;
  text-align: center;
  opacity: 0;
  transition: opacity 250ms ease;
}
.pwa-install-banner.is-visible {
  opacity: 1;
}
.pwa-install-banner__btn {
  min-height: 32px;
  padding-block: 4px;
  padding-inline: 16px;
  font-size: 13px;
  background: #fff;
  color: var(--accent-gold);
}
.pwa-install-banner__close {
  position: absolute;
  inset-inline-end: 10px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.2);
  color: #fff;
  cursor: pointer;
}
.pwa-install-banner__close svg {
  width: 14px;
  height: 14px;
}

/* Same normal-flow reasoning as .pwa-update-banner/.pwa-install-banner
   above -- quieter still (small text + a thin progress bar, no button),
   since this is just status, nothing for the visitor to act on. See
   assets/js/sw-register.js's updatePrecacheBanner(). */
.pwa-precache-banner {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 20px;
  background: var(--bg-raised);
  border-bottom: 1px solid var(--border);
  opacity: 0;
  transition: opacity 250ms ease;
}
.pwa-precache-banner.is-visible {
  opacity: 1;
}
.pwa-precache-banner__label {
  font-size: 12.5px;
  color: var(--text-muted);
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.pwa-precache-banner__track {
  height: 3px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
}
.pwa-precache-banner__fill {
  height: 100%;
  width: 0%;
  background: var(--secondary);
  border-radius: 999px;
  transition: width 200ms ease;
}

/* =========================================================
   Share popover
   ========================================================= */
.share-popover {
  position: absolute;
  z-index: 90;
  min-width: 180px;
  padding: 6px;
  border-radius: 12px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  box-shadow: 0 12px 32px rgba(var(--shadow-color), 0.18);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.share-popover__heading {
  padding: 6px 10px 4px;
  font-size: 11px;
  font-weight: 700;
  line-height: 1.3; /* see .share-popover__item -- same [dir="rtl"] body line-height:2.1 inheritance issue */
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.share-popover__item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  border: none;
  border-radius: 8px;
  background: none;
  color: var(--text-main);
  font-size: 14px;
  line-height: 1.3; /* pinned so [dir="rtl"] body's line-height:2.1 can't inflate this span's box and throw off centering against the fixed-size icon next to it */
  font-family: inherit;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
}
.share-popover__item:hover {
  background: var(--bg-raised);
}
.share-popover__item svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

/* =========================================================
   Action menu — the "more actions" (⋯) popover after Play/Save
   Offline/Share on lecture cards + the detail page (see action-menu.js).
   Same look/mechanics as .share-popover above, kept as its own class since
   this one is a general-purpose, growing action list rather than a fixed
   set of share targets.
   ========================================================= */
.action-menu {
  position: absolute;
  z-index: 90;
  min-width: 160px;
  padding: 6px;
  border-radius: 12px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  box-shadow: 0 12px 32px rgba(var(--shadow-color), 0.18);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.action-menu__item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  border: none;
  border-radius: 8px;
  background: none;
  color: var(--text-main);
  font-size: 14px;
  line-height: 1.3;
  font-family: inherit;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
}
.action-menu__item:hover {
  background: var(--bg-raised);
}
.action-menu__item svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

/* =========================================================
   "Notify me when live" proactive modal — see notify-prompt.js.
   Real backdrop+dialog (unlike install-prompt.js's slide-in banner) --
   z-index above the mobile menu (60)/action-menu+share-popover (90)/toast
   (80), comfortably under .offline-fly-card's transient 200 which never
   overlaps this flow anyway.
   ========================================================= */
.notify-modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 150;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(0, 0, 0, 0.5);
  opacity: 0;
  transition: opacity 250ms ease;
}
.notify-modal-backdrop.is-visible {
  opacity: 1;
}
.notify-modal {
  position: relative;
  width: 100%;
  max-width: 380px;
  padding: 32px 28px 28px;
  border-radius: var(--radius-card);
  background: var(--bg-surface);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
  text-align: center;
  transform: translateY(12px) scale(0.97);
  transition: transform 250ms ease;
}
.notify-modal-backdrop.is-visible .notify-modal {
  transform: translateY(0) scale(1);
}
.notify-modal__close {
  position: absolute;
  inset-block-start: 12px;
  inset-inline-end: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}
.notify-modal__close:hover {
  background: var(--bg-raised);
}
.notify-modal__close svg {
  width: 16px;
  height: 16px;
}
.notify-modal__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  margin-bottom: 16px;
  border-radius: 999px;
  background: rgba(47, 164, 169, 0.14);
  color: var(--secondary);
}
.notify-modal__icon svg {
  width: 28px;
  height: 28px;
}
.notify-modal__title {
  margin-bottom: 8px;
  font-size: 19px;
  font-weight: 700;
}
.notify-modal__body {
  margin-bottom: 22px;
  color: var(--text-muted);
  font-size: 14px;
  line-height: 1.6;
}
.notify-modal__actions {
  display: flex;
  gap: 10px;
}
.notify-modal__actions .btn {
  flex: 1 1 0;
  min-height: 44px;
  padding-inline: 12px;
  font-size: 14px;
}
