/* ==========================================================================
   Tajawal - self-hosted (no Google Fonts CDN call, keeps the CSP locked to
   'self'). Covers both Arabic and Latin text with one family everywhere.
   ========================================================================== */

@font-face {
  font-family: 'Tajawal';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/tajawal/tajawal-arabic-400-normal.woff2') format('woff2');
  unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF, U+FB50-FDFF, U+FE70-FEFF;
}
@font-face {
  font-family: 'Tajawal';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/tajawal/tajawal-arabic-500-normal.woff2') format('woff2');
  unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF, U+FB50-FDFF, U+FE70-FEFF;
}
@font-face {
  font-family: 'Tajawal';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/fonts/tajawal/tajawal-arabic-700-normal.woff2') format('woff2');
  unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF, U+FB50-FDFF, U+FE70-FEFF;
}
@font-face {
  font-family: 'Tajawal';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/tajawal/tajawal-latin-400-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F;
}
@font-face {
  font-family: 'Tajawal';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/tajawal/tajawal-latin-500-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F;
}
@font-face {
  font-family: 'Tajawal';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/fonts/tajawal/tajawal-latin-700-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F;
}

/* ==========================================================================
   Tarteel Admin - design tokens & shared shell styles
   Brand colors sourced from the current app (maincolors.text):
     green  rgb(37, 97, 66)   -> primary
     green  rgb(53, 90, 50)   -> primary, darker shade
     gold   rgb(202, 170, 120) -> accent
   ========================================================================== */

:root {
  --brand-primary: #256142;
  --brand-primary-dark: #1d4d34;
  --brand-primary-darker: #163b28;
  --brand-gold: #caaa78;
  --brand-gold-dark: #a98a5c;

  --bg: #f3f6f4;
  --surface: #ffffff;
  --surface-alt: #eef2f0;
  --text: #1f2a24;
  --text-muted: #5b6b62;
  --border: #e1e7e3;
  --shadow: 0 2px 10px rgba(20, 40, 30, 0.06);

  /* Announcement bell/popup "sticky note" styling - see the
     Notifications bell section below. */
  --sticky-yellow-bg: #fff8e1;
  --sticky-yellow-border: #f0dfa0;
  --sticky-yellow-text: #6b5900;

  --sidebar-bg: var(--brand-primary-darker);
  --sidebar-text: #d9e8df;
  --sidebar-text-muted: #9db5a8;
  --sidebar-active-bg: rgba(255, 255, 255, 0.12);

  --topbar-height: 4rem;
  --sidebar-width: 17rem;
  --sidebar-width-collapsed: 4.75rem;

  color-scheme: light;
}

:root[data-bs-theme='dark'] {
  --bg: #101613;
  --surface: #1a2320;
  --surface-alt: #202a26;
  --text: #e8efeb;
  --text-muted: #9fb3a8;
  --border: #2a3630;
  --shadow: 0 2px 14px rgba(0, 0, 0, 0.35);

  --sticky-yellow-bg: #3a3320;
  --sticky-yellow-border: #5c4f2a;
  --sticky-yellow-text: #f0dfa0;

  --sidebar-bg: #0d1a14;
  --sidebar-text: #d9e8df;
  --sidebar-text-muted: #85a294;
  --sidebar-active-bg: rgba(255, 255, 255, 0.08);

  color-scheme: dark;
}

/* Remap Bootstrap's primary to the brand green so btn-primary, links,
   focus rings etc. all follow the brand without per-component overrides. */
:root,
:root[data-bs-theme='dark'] {
  --bs-primary: var(--brand-primary);
  --bs-primary-rgb: 37, 97, 66;
  --bs-link-color: var(--brand-primary);
  --bs-link-hover-color: var(--brand-primary-dark);
  --bs-body-bg: var(--bg);
  --bs-body-color: var(--text);
  --bs-body-font-family: 'Tajawal', 'Segoe UI', Tahoma, Arial, sans-serif;
  --bs-font-sans-serif: 'Tajawal', 'Segoe UI', Tahoma, Arial, sans-serif;
}

/* Every button pill-shaped (same rounding as the "طالب متميز" distinguished
   badge) instead of Bootstrap's default slightly-rounded rectangle -
   .btn-sm/.btn-lg and every color variant all read this one token, so this
   single override covers the whole app, icon-only table-action buttons
   included. */
.btn {
  --bs-btn-border-radius: 999px;
}

/* Bootstrap 5.3's .btn-primary hardcodes its own --bs-btn-* tokens instead of
   reading --bs-primary, so the global remap above doesn't reach it on its own. */
.btn-primary {
  --bs-btn-bg: var(--brand-primary);
  --bs-btn-border-color: var(--brand-primary);
  --bs-btn-hover-bg: var(--brand-primary-dark);
  --bs-btn-hover-border-color: var(--brand-primary-dark);
  --bs-btn-active-bg: var(--brand-primary-darker);
  --bs-btn-active-border-color: var(--brand-primary-darker);
  --bs-btn-disabled-bg: var(--brand-primary);
  --bs-btn-disabled-border-color: var(--brand-primary);
  --bs-btn-focus-shadow-rgb: 37, 97, 66;
}

/* Same hardcoded-token issue as .btn-primary above, for the outline
   variant (white background, colored border/text at rest; filled on
   hover/active) - used all over the app (print/email/save buttons on
   staff, students, site-content, events, quranCenters, ...). Without
   this override it stays Bootstrap's default blue instead of the brand
   green every solid button already uses. */
.btn-outline-primary {
  --bs-btn-color: var(--brand-primary);
  --bs-btn-border-color: var(--brand-primary);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: var(--brand-primary);
  --bs-btn-hover-border-color: var(--brand-primary);
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: var(--brand-primary-dark);
  --bs-btn-active-border-color: var(--brand-primary-dark);
  --bs-btn-disabled-color: var(--brand-primary);
  --bs-btn-disabled-border-color: var(--brand-primary);
  --bs-btn-focus-shadow-rgb: 37, 97, 66;
}

/* Bootstrap 5.3's form-check/form-switch toggles also hardcode their own
   checked/focus colors (#0d6efd) instead of reading --bs-primary, so the
   remap above doesn't reach them either - override explicitly so every
   toggle switch in the app (Active, has_discount, etc.) turns brand green
   with a white knob instead of Bootstrap blue. */
.form-check-input:checked {
  background-color: var(--brand-primary);
  border-color: var(--brand-primary);
}

.form-check-input:focus {
  border-color: var(--brand-primary);
  box-shadow: 0 0 0 0.25rem rgba(37, 97, 66, 0.25);
}

.form-switch .form-check-input:focus {
  --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23256142'/%3e%3c/svg%3e");
}

html,
body {
  height: 100%;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: 'Tajawal', 'Segoe UI', Tahoma, Arial, sans-serif;
}

a {
  color: var(--brand-primary);
  text-decoration: none;
}

a:hover,
a:focus {
  text-decoration: none;
}

.text-muted {
  color: var(--text-muted) !important;
}

/* ---------------------------------------------------------------------- *
 * App shell: topbar + sidebar + content
 * ---------------------------------------------------------------------- */

.app-shell {
  display: flex;
  min-height: 100vh;
}

.sidebar {
  width: var(--sidebar-width);
  flex-shrink: 0;
  background: var(--sidebar-bg);
  color: var(--sidebar-text);
  display: flex;
  flex-direction: column;
  position: fixed;
  inset-block: 0;
  inset-inline-start: 0;
  z-index: 1030;
  transition: transform 0.2s ease;
}

/* Same overlay technique as .auth-features-panel::before (see the auth
   pages further down) - the brand pattern sits on its own layer at 50%
   opacity behind the sidebar's actual content, rather than making the nav
   links/logo/user block themselves translucent. */
.sidebar::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: url('/img/bg-pattern-dark.png');
  background-repeat: repeat;
  opacity: 0.5;
  pointer-events: none;
}

.sidebar > * {
  position: relative;
  z-index: 1;
}

.sidebar .brand {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.sidebar .brand img {
  display: block;
  width: 100%;
  height: auto;
  box-sizing: border-box;
  object-fit: contain;
  background: #fff;
  padding: 0.6rem 0.85rem;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
}

/* Mobile-only "X" to dismiss the off-canvas sidebar (see sidebar.js and the
   max-width:991.98px block below) - the d-lg-none utility class on the
   button itself hides it again once the sidebar stops being an overlay. */
.sidebar-close-btn {
  position: absolute;
  top: 0.6rem;
  inset-inline-end: 0.6rem;
  z-index: 2;
  width: 1.75rem;
  height: 1.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
}

.sidebar-close-btn:hover {
  background: rgba(0, 0, 0, 0.75);
}

.sidebar .brand span {
  font-weight: 700;
  font-size: 1.05rem;
  color: #fff;
}

.sidebar-nav {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0.75rem 0.75rem 1.5rem;
}

.sidebar-nav .nav-link {
  color: var(--sidebar-text);
  border-radius: 0.6rem;
  padding: 0.65rem 0.9rem;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-size: 0.95rem;
  margin-bottom: 0.15rem;
}

.sidebar-nav .nav-link i {
  font-size: 1.1rem;
  width: 1.4rem;
  text-align: center;
  color: var(--sidebar-text-muted);
  flex-shrink: 0;
}

.sidebar-nav .nav-link .link-text {
  white-space: nowrap;
  overflow: hidden;
}

.sidebar-nav .nav-link:hover {
  background: var(--sidebar-active-bg);
  color: #fff;
}

.sidebar-nav .nav-link.active {
  background: var(--brand-gold);
  color: #26311f;
  font-weight: 600;
}

.sidebar-nav .nav-link.active i {
  color: #26311f;
}

.sidebar-nav .nav-section-title {
  color: var(--sidebar-text-muted);
  text-transform: uppercase;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  padding: 1rem 0.9rem 0.35rem;
}

/* Collapsible sidebar groups (Quran Centers / Staff / Students) - clicking
   the parent link toggles a bulleted list of that section's sub-pages
   instead of navigating directly, mirroring the legacy app's dropdown
   menus. Uses Bootstrap's own .collapse (bootstrap.bundle.min.js is
   already loaded), so no extra JS is needed - it toggles aria-expanded on
   the trigger automatically, which the caret rotation below reads. */
.sidebar-toggle {
  cursor: pointer;
}

.sidebar-caret {
  margin-inline-start: auto;
  font-size: 0.8rem !important;
  color: var(--sidebar-text-muted);
  transition: transform 0.2s ease;
  flex-shrink: 0;
}

.sidebar-toggle[aria-expanded="true"] .sidebar-caret {
  transform: rotate(180deg);
}

.sidebar-toggle.active .sidebar-caret {
  color: inherit;
}

.sidebar-submenu {
  list-style: disc;
  margin: 0.1rem 0 0.4rem;
  padding-inline-start: 2.7rem;
  padding-inline-end: 0.5rem;
}

.sidebar-submenu li {
  margin-bottom: 0.1rem;
}

.sidebar-submenu li::marker {
  color: var(--sidebar-text-muted);
}

/* The selected bullet's own dot changes color instead of getting the same
   full-row gold highlight the top-level nav-links use - the override below
   cancels that inherited .active background/color/weight for sub-links, and
   :has() lets the <li> recolor its ::marker (a pseudo-element can't be
   targeted on the link itself) when the sub-link inside it is active. */
.sidebar-submenu li:has(.sidebar-sublink.active)::marker {
  color: var(--brand-gold);
}

.sidebar-nav .nav-link.sidebar-sublink {
  padding: 0.4rem 0.6rem;
  font-size: 0.85rem;
  margin-bottom: 0;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.sidebar-nav .nav-link.sidebar-sublink.active {
  background: transparent;
  color: var(--sidebar-text);
  font-weight: 400;
}

.sidebar-nav .nav-link.sidebar-sublink.active:hover {
  background: var(--sidebar-active-bg);
  color: #fff;
}

/* Small "opens the legacy app" indicator on sub-items that don't have an
   equivalent page in the redesign yet (see sidebar.ejs comment above the
   Quran Centers / Staff / Students groups). */
.sidebar-sublink-external {
  margin-inline-start: auto;
  font-size: 0.7rem !important;
  color: var(--sidebar-text-muted);
  opacity: 0.8;
}

.sidebar-nav .nav-link.sidebar-sublink.active .sidebar-sublink-external {
  color: inherit;
}

.sidebar-user {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  width: 100%;
  min-height: 5rem; /* matches .site-footer's total height (80px), so the
    sidebar's bottom user block and the page footer line up as the same
    thickness across the bottom of the app shell */
  padding: 0.75rem 0.9rem;
  border: none;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  background: transparent;
  color: var(--sidebar-text);
  text-decoration: none;
  text-align: start;
  font: inherit;
  cursor: pointer;
  flex-shrink: 0;
}

.sidebar-user:hover {
  background: var(--sidebar-active-bg);
  color: #fff;
}

.sidebar-user.dropdown-toggle::after {
  display: none;
}

/* A direct child of .sidebar, right after .sidebar-nav (so it still renders
   right below the website link, the nav's last item) - .sidebar-nav has
   flex: 1 and scrolls its own links internally when they overflow, which
   always leaves this row pinned flush to the sidebar's actual bottom edge,
   never scrolled away and never pushed out past it, regardless of how many
   nav links there are or how short the viewport is. */
.sidebar-user-dropdown {
  flex-shrink: 0;
}

/* The sidebar itself is always the dark palette (in both the light and dark
   app themes - see --sidebar-bg above), but Bootstrap's dropdown-menu
   follows the global light/dark theme via [data-bs-theme] on <html>. In
   light mode that meant a plain white popup floating off a dark green
   sidebar - styled here to match the sidebar instead, so the menu reads as
   part of it rather than a separate, unrelated box. */
.sidebar-user-dropdown .dropdown-menu {
  width: calc(var(--sidebar-width) - 1.8rem);
  max-width: calc(var(--sidebar-width) - 1.8rem);
  background: var(--sidebar-bg);
  border-color: rgba(255, 255, 255, 0.15);
}

.sidebar-user-dropdown .dropdown-item {
  color: var(--sidebar-text);
}

.sidebar-user-dropdown .dropdown-item:hover,
.sidebar-user-dropdown .dropdown-item:focus {
  background: var(--sidebar-active-bg);
  color: #fff;
}

.sidebar-user-dropdown .dropdown-item.text-danger:hover,
.sidebar-user-dropdown .dropdown-item.text-danger:focus {
  background: rgba(220, 53, 69, 0.15);
  color: #dc3545;
}

.sidebar-user-dropdown .dropdown-divider {
  border-color: rgba(255, 255, 255, 0.12);
}

.sidebar-user-avatar {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  border: 1px solid rgba(202, 170, 120, 0.5);
  background: #f1e6d3;
  color: var(--brand-primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.2rem;
  overflow: hidden;
  flex-shrink: 0;
}

.sidebar-user-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.sidebar-user-info {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  line-height: 1.3;
}

.sidebar-user-name {
  font-size: 0.95rem;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sidebar-user-email {
  font-size: 0.7rem;
  color: var(--sidebar-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.main-column {
  flex: 1;
  min-width: 0;
  margin-inline-start: var(--sidebar-width);
  display: flex;
  flex-direction: column;
}

.topbar {
  height: var(--topbar-height);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 1rem;
  padding-inline: 1.25rem;
  position: sticky;
  top: 0;
  z-index: 1020;
}

.topbar .flex-grow {
  flex: 1;
}

/* Every list/profile page passes its heading in as `title` - rendered above
   the page's own content (see partials/page-title.ejs, included from
   layout.ejs just before `body`) as a centered, filled brand-green pill, so
   it reads as that page's actual title rather than plain text buried in
   the topbar. */
.page-title-wrap {
  display: flex;
  justify-content: center;
  margin-bottom: 1.25rem;
}

.page-title-badge {
  background: transparent;
  border: none;
  color: var(--brand-primary);
  font-size: 1.725rem;
  font-weight: 600;
  padding: 0.35rem 1.5rem;
  border-radius: 2rem;
  line-height: 1.3;
}

/* On a phone, the longer page titles ("تعديل بيانات المستخدم" and
   similar edit-page titles) sit right at the edge of wrapping to a
   second line at the desktop size above - scaled down a bit here so
   every title, including the longest ones, reliably stays on one line. */
@media (max-width: 575.98px) {
  .page-title-badge {
    font-size: 1.3rem;
    padding: 0.3rem 1rem;
  }
}

.topbar-icon-btn {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 0.6rem;
  border: 1px solid rgba(202, 170, 120, 0.5);
  background: #f1e6d3;
  color: var(--brand-primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.05rem;
}

.topbar-icon-btn:hover {
  background: var(--brand-gold);
  color: #26311f;
}

.lang-toggle-btn.dropdown-toggle::after {
  display: none;
}

.topbar-avatar-btn {
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(202, 170, 120, 0.5);
  background: #f1e6d3;
  color: var(--brand-primary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.35rem;
  overflow: hidden;
  flex-shrink: 0;
}

.topbar-avatar-btn:hover {
  background: var(--brand-gold);
  color: #26311f;
}

.topbar-avatar-btn.dropdown-toggle::after {
  display: none;
}

.topbar-avatar-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---------------------------------------------------------------------- *
 * Profile page
 * ---------------------------------------------------------------------- */

.profile-cover {
  height: 5.5rem;
  background: linear-gradient(135deg, var(--brand-primary), var(--brand-primary-dark));
}

.profile-avatar-wrap {
  margin-top: -3.25rem;
}

.profile-avatar {
  width: 6.5rem;
  height: 6.5rem;
  border-radius: 50%;
  object-fit: cover;
  border: 4px solid var(--surface);
  box-shadow: var(--shadow);
  background: var(--surface);
}

.profile-avatar-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 3rem;
  color: var(--text-muted);
  background: var(--surface-alt);
}

.profile-role-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  background: var(--brand-gold);
  color: #26311f;
  font-weight: 600;
  font-size: 0.8rem;
  padding: 0.35rem 0.8rem;
  border-radius: 999px;
}

.profile-info-row {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--border);
}

.profile-info-row:last-child {
  border-bottom: none;
}

.profile-info-icon {
  width: 2.4rem;
  height: 2.4rem;
  border-radius: 0.7rem;
  background: var(--surface-alt);
  color: var(--brand-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  flex-shrink: 0;
}

/* ---------------------------------------------------------------------- *
 * Edit-page profile header (students / staff edit forms): photo + title
 * + photo actions combined into a single card header instead of a
 * separate photo card.
 * ---------------------------------------------------------------------- */

.edit-profile-header {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1.25rem;
  min-height: 9.25rem;
  padding-bottom: 1.5rem;
  padding-right: 8rem;
  margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--border);
}

.edit-photo-corner {
  position: absolute;
  top: 1.25rem;
  right: 1.25rem;
  width: 96px;
  height: 112px;
  flex-shrink: 0;
  border-radius: 0.5rem;
  overflow: hidden;
  border: 3px solid var(--brand-primary);
  box-shadow: var(--shadow);
  background: var(--surface-alt);
  display: flex;
  align-items: center;
  justify-content: center;
}

.edit-photo-corner img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.edit-photo-corner i {
  font-size: 3rem;
  color: var(--text-muted);
}

@media (max-width: 420px) {
  .edit-photo-corner {
    width: 72px;
    height: 84px;
    top: 1rem;
    right: 1rem;
  }

  .edit-profile-header {
    min-height: 7.25rem;
    padding-right: 6rem;
  }
}

.edit-profile-info {
  flex: 1 1 auto;
  min-width: 10rem;
}

.edit-profile-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.edit-voice-player {
  height: 2.25rem;
  max-width: 220px;
  vertical-align: middle;
}

.voice-play-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3.25rem;
  height: 3.25rem;
  border-radius: 50%;
  border: none;
  background: var(--brand-primary);
  color: #fff;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  transition: background-color 0.15s ease, transform 0.1s ease;
}

.voice-play-btn i {
  margin-inline-start: 2px;
}

.voice-play-btn.is-playing i {
  margin-inline-start: 0;
}

.voice-play-btn:hover {
  background: var(--brand-primary-dark);
}

.voice-play-btn:active {
  transform: scale(0.94);
}

.voice-play-btn.is-playing {
  background: var(--brand-gold-dark);
}

.distinguished-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
  background: linear-gradient(135deg, var(--brand-gold), var(--brand-gold-dark));
  color: #fff;
  font-size: 0.8rem;
  font-weight: 600;
}

.distinguished-star {
  display: inline-block;
  animation: distinguished-star-roll 2.4s linear infinite;
}

@keyframes distinguished-star-roll {
  0% {
    transform: rotate(0deg);
    color: #fff;
  }
  50% {
    color: #fff2b8;
  }
  100% {
    transform: rotate(360deg);
    color: #fff;
  }
}

@media (prefers-reduced-motion: reduce) {
  .distinguished-star {
    animation: none;
  }
}

.topbar-avatar-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.lang-flag-circle {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  background: #fff;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);
}

.lang-flag-circle svg {
  display: block;
  width: 20px;
  height: 20px;
}

.lang-menu {
  min-width: 9.5rem;
  padding: 0.4rem;
}

.lang-menu-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  border-radius: 0.5rem;
  padding: 0.5rem 0.65rem;
}

.lang-menu-item:hover {
  background: rgba(202, 170, 120, 0.16);
}

.lang-menu-item.active,
.dropdown-item.lang-menu-item.active,
.dropdown-menu .lang-menu-item.active {
  background: rgba(202, 170, 120, 0.28);
  color: var(--text) !important;
  font-weight: 600;
}

.notif-item:hover {
  background: rgba(202, 170, 120, 0.16);
}

.content {
  /* Previously flex: 1, which stretched this to absorb the leftover column
     height so the footer stayed pinned to the bottom of the viewport on
     short pages. Removed by request - the footer now just sits right after
     the page content ends, wherever that is, instead of being pushed down
     to the bottom of the screen. */
  padding: 1.5rem;
  background-color: var(--bg);
  background-image: url('/img/auth-bg-admin.png');
  background-repeat: repeat;
}

/* Dark mode swaps the light leaf pattern for a dark-green tiled version and
   brightens headings that sit directly on it (not inside a .card, which
   already has its own solid dark surface + light text via --text). */
:root[data-bs-theme='dark'] .content {
  background-color: #355a32;
  background-image: url('/img/bg-pattern-dark.png');
}

:root[data-bs-theme='dark'] .content h4,
:root[data-bs-theme='dark'] .content h5,
:root[data-bs-theme='dark'] .content h6 {
  color: var(--brand-gold);
}

.sidebar-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  z-index: 1025;
}

@media (max-width: 991.98px) {
  .sidebar {
    transform: translateX(100%);
  }
  html[dir='ltr'] .sidebar {
    transform: translateX(-100%);
  }
  .sidebar.open {
    transform: translateX(0);
  }
  .main-column {
    margin-inline-start: 0;
  }
  .sidebar-backdrop.open {
    display: block;
  }
}

/* ---------------------------------------------------------------------- *
 * Desktop sidebar collapse (icons-only mode) - toggled from the topbar
 * ---------------------------------------------------------------------- */

@media (min-width: 992px) {
  html.sidebar-collapsed .sidebar {
    width: var(--sidebar-width-collapsed);
  }

  html.sidebar-collapsed .main-column {
    margin-inline-start: var(--sidebar-width-collapsed);
  }

  html.sidebar-collapsed .sidebar-nav .nav-link .link-text,
  html.sidebar-collapsed .sidebar-nav .nav-section-title {
    display: none;
  }

  html.sidebar-collapsed .sidebar-nav .nav-link {
    justify-content: center;
    padding-inline: 0.6rem;
  }

  html.sidebar-collapsed .sidebar-nav .nav-link i {
    width: auto;
  }

  /* Icon-only mode has no room for sub-item text, so collapse the whole
     bulleted submenu away rather than leaving an orphaned bullet list or a
     caret that no longer does anything useful. */
  html.sidebar-collapsed .sidebar-nav-group .collapse {
    display: none !important;
  }

  html.sidebar-collapsed .sidebar-caret {
    display: none;
  }

  html.sidebar-collapsed .sidebar-user-info {
    display: none;
  }

  html.sidebar-collapsed .sidebar-user {
    justify-content: center;
    padding-inline: 0.6rem;
  }
}

/* ---------------------------------------------------------------------- *
 * Cards & dashboard widgets
 * ---------------------------------------------------------------------- */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
}

/* Admin dashboard only (views/dashboard/index.ejs wraps itself in
   .dashboard-page): cards take the same light green as the Upcoming Events
   rows, which is Bootstrap's list-group background = --bs-body-bg = --bg.
   Tables inside go transparent so they don't show as a white box on top. */
.dashboard-page .card {
  background: var(--bg);
}

.dashboard-page .card .table {
  --bs-table-bg: transparent;
}

.stat-card {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1.25rem;
  border-radius: 0.9rem;
}

.stat-card .stat-icon {
  width: 3rem;
  height: 3rem;
  border-radius: 0.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.4rem;
  color: #fff;
  background: var(--brand-primary);
  flex-shrink: 0;
}

.stat-card .stat-icon.gold {
  background: var(--brand-gold-dark);
}

.stat-card .stat-value {
  font-size: 1.6rem;
  font-weight: 700;
  line-height: 1;
}

.stat-card .stat-label {
  color: var(--text-muted);
  font-size: 0.85rem;
}

/* Dashboard's top stat cards double as quick links (Students/Staff/Quran
   Centers) - counter and navigation combined into one clickable card
   instead of two separate ones. */
a.stat-card {
  color: var(--text);
  text-decoration: none;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}

a.stat-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow);
  color: var(--text);
}

.stat-card .stat-chevron {
  margin-inline-start: auto;
  color: var(--text-muted);
  font-size: 1rem;
}

/* Dashboard weather + prayer-times status bar (top of the page) - a
   full-width bar docked flush under the topbar, matching the public
   site's (tarteelbh.com) own date/weather + prayer-times strip: a slim
   top row with the date/clock/weather, then a second row of prayer-time
   pills below it, spanning the whole content width instead of floating
   as a small centered card.
   The wrap's negative margin cancels out .content's own padding (see
   theme.css's `.content { padding: ... }`) so the bar's background/border
   reaches the topbar above and both edges of the content column, the
   same "docked" look as the reference site - it has to be kept in sync
   with .content's padding at each breakpoint (see the 575.98px override
   below, matching .content's own mobile padding override). */
.dashboard-status-bar-wrap {
  margin: -1.5rem -1.5rem 1.5rem;
}

.dashboard-status-bar {
  width: 100%;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow);
  animation: statusBarIn 0.5s ease-out;
}

.dashboard-status-bar-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.4rem 1rem;
  padding: 0.5rem 1.5rem;
}

.dashboard-status-bar-row-prayers {
  border-top: 1px solid var(--border);
  padding-block: 0.35rem;
}

.status-datetime {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  flex-shrink: 0;
  font-size: 0.8rem;
  color: var(--text-muted);
  white-space: nowrap;
}

.status-datetime > i {
  color: var(--brand-primary);
}

.status-datetime-sep {
  opacity: 0.5;
}

.status-clock {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.status-weather {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-shrink: 0;
}

.status-weather > i {
  font-size: 1.15rem;
  color: var(--brand-primary);
  flex-shrink: 0;
  animation: weatherIconPulse 3s ease-in-out infinite;
}

/* Sun icon (clear weather) spins slowly like rays turning, instead of just
   pulsing like the other condition icons - the pulse still runs alongside
   it so it doesn't look static compared to its siblings. */
.status-weather > i.bi-sun-fill {
  animation: weatherIconPulse 3s ease-in-out infinite, sunSpin 10s linear infinite;
  transform-origin: center;
}

.status-weather-temp {
  font-weight: 700;
  font-size: 0.9rem;
}

.status-humidity {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
}

.status-humidity i {
  display: inline-block;
  color: #4a9fd8;
  animation: humidityDrop 2.2s ease-in-out infinite;
}

.status-divider {
  width: 1px;
  align-self: stretch;
  background: var(--border);
}

.status-prayers {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
}

.status-prayer-item {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  background: var(--surface-alt);
  font-size: 0.75rem;
  white-space: nowrap;
  transition: background-color 0.15s ease, color 0.15s ease, transform 0.15s ease;
}

.status-prayer-item:hover {
  transform: translateY(-1px);
}

.status-prayer-item.is-next {
  background: var(--brand-primary);
  color: #fff;
  font-weight: 600;
  animation: nextPrayerPulse 2s ease-in-out infinite;
}

.status-prayer-name {
  opacity: 0.8;
}

.status-prayer-time {
  font-weight: 600;
}

.status-prayer-period {
  font-weight: 400;
  opacity: 0.75;
  font-size: 0.85em;
  margin-inline-start: 0.15em;
}

/* On a narrow phone (portrait), .dashboard-status-bar-row-top's flex-wrap
   naturally wraps .status-datetime/.status-weather onto separate lines -
   but exactly where it wraps depends on their rendered widths, and the
   clock ticking (":45" -> ":46", or an hour/day/date rollover) constantly
   nudges .status-datetime's width by a pixel or two. Right at a wrap
   boundary, that's enough to flip whether .status-weather shares the
   datetime's line or starts a new one, which visibly shows as the weather
   icon/temperature jumping up and down every second. Forcing a column
   layout below removes the ambiguous wrap point entirely - each section
   always gets its own line, so nothing can shift as the clock updates.
   (.status-prayers is unaffected - it's always its own row now, see
   .dashboard-status-bar-row-prayers above.) */
@media (max-width: 575.98px) {
  /* Matches .content's own mobile padding override so the docked bar's
     edges still line up with the topbar/content column exactly. */
  .dashboard-status-bar-wrap {
    margin: -0.85rem -0.85rem 1.25rem;
  }

  .dashboard-status-bar-row-top {
    flex-direction: column;
  }

  .status-datetime,
  .status-weather {
    justify-content: center;
  }

  /* Day + Hijri date + Gregorian date + time no longer reliably fits one
     line at phone width now that the Hijri segment is in the mix. Each
     section already gets its own row at this breakpoint (see above), so
     letting just this row wrap internally is safe - it can't reintroduce
     the datetime/weather wrap-boundary jitter described above, since
     .status-weather is a separate row regardless of how tall this one is. */
  .status-datetime {
    white-space: normal;
    flex-wrap: wrap;
    row-gap: 0.15rem;
  }
}

@keyframes statusBarIn {
  from {
    opacity: 0;
    transform: translateY(-6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes weatherIconPulse {
  0%, 100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.15);
  }
}

@keyframes sunSpin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

/* A little drop that stretches down and bounces back, like a bead of
   water forming and falling - loops to suggest humidity in the air. */
@keyframes humidityDrop {
  0%, 100% {
    transform: translateY(0) scaleY(1);
  }
  30% {
    transform: translateY(1px) scaleY(1.1);
  }
  50% {
    transform: translateY(3px) scaleY(0.9);
  }
  70% {
    transform: translateY(0) scaleY(1.05);
  }
}

/* Ring glow around the highlighted "next prayer" pill - uses the same
   brand-primary green as its background (#256142 = rgb(37, 97, 66)). */
@keyframes nextPrayerPulse {
  0%, 100% {
    box-shadow: 0 0 0 0 rgba(37, 97, 66, 0.5);
  }
  50% {
    box-shadow: 0 0 0 5px rgba(37, 97, 66, 0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .dashboard-status-bar,
  .status-weather > i,
  .status-humidity i,
  .status-prayer-item.is-next {
    animation: none;
  }
}

/* Clickable rows (e.g. dashboard's inactive-users list linking into the
   full /users table) and the brief highlight when landing on one via
   a #user-row-<id> link. */
.row-link {
  cursor: pointer;
}

.table tbody tr {
  scroll-margin-top: calc(var(--topbar-height) + 1rem);
}

/* Table background: a tinted header row so columns are easy to scan, plain
   surface rows underneath, and a subtle hover tint for feedback. Applies
   to every data table in the app (Users, Students, the dashboard's
   inactive-users card) so they all stay visually consistent. */
.table {
  --bs-table-bg: var(--surface);
  --bs-table-color: var(--text);
}

.table thead th {
  background: var(--surface-alt);
  color: var(--text-muted);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  border-bottom: 1px solid var(--border);
}

.table tbody tr:hover {
  --bs-table-bg: var(--surface-alt);
}

.row-highlighted,
.row-highlighted > td {
  background-color: rgba(202, 170, 120, 0.65) !important;
}

.row-highlighted > td:first-child {
  box-shadow: inset 3px 0 0 var(--brand-gold-dark);
}

.quick-link-card {
  border-radius: 0.9rem;
  padding: 1.1rem;
  display: flex;
  align-items: center;
  gap: 0.85rem;
  text-decoration: none;
  color: var(--text);
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}

.quick-link-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow);
  color: var(--text);
}

.quick-link-card .icon {
  width: 2.6rem;
  height: 2.6rem;
  border-radius: 0.65rem;
  background: var(--surface-alt);
  color: var(--brand-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.2rem;
  flex-shrink: 0;
}

/* "Being migrated to the new system" cards on the Students/Quran Centers
   profile pages - solid brand green with white text/icon so they read as
   a distinct, branded call-out rather than blending in with regular cards. */
.quick-link-card.legacy-quick-link {
  background: var(--brand-primary);
  color: #fff;
  border-color: var(--brand-primary);
}

.quick-link-card.legacy-quick-link:hover {
  background: var(--brand-primary-dark);
  color: #fff;
}

.quick-link-card.legacy-quick-link .icon {
  background: rgba(255, 255, 255, 0.18);
  color: #fff;
}

/* Record-count badge on a profile page's quick-link cards (staff, student
   and center profiles) - shows how many records exist in that section,
   e.g. how many attendance entries a staff member has. Only rendered by
   the view when the count is greater than zero, so an empty section's
   card stays plain rather than showing a "0". .quick-link-card is a
   Bootstrap .card under the hood, which is already position:relative,
   so this just needs to anchor to that. */
.quick-link-count-badge {
  position: absolute;
  top: 0.5rem;
  inset-inline-end: 0.6rem;
  background: var(--brand-primary);
  color: #fff;
  font-size: 0.72rem;
  font-weight: 700;
  min-width: 1.35rem;
  height: 1.35rem;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 0.35rem;
  line-height: 1;
}

.quick-link-card.legacy-quick-link .quick-link-count-badge {
  background: #fff;
  color: var(--brand-primary);
}

/* ---------------------------------------------------------------------- *
 * Auth pages (login / register / change password) - standalone split-
 * screen layout: a form panel and a brand features panel, side by side.
 * DOM order is always [form panel, features panel]; because the page
 * inherits its dir="ltr"/"rtl" from <html>, plain flex-direction: row
 * mirrors the two panels automatically - no JS, no duplicate markup:
 *   ltr (English) -> form on the left, features on the right
 *   rtl (Arabic)  -> form on the right, features on the left
 * The features panel is hidden below the lg breakpoint via the
 * d-none d-lg-flex utility classes on the partial itself; the form panel
 * (flex-grow: 1) automatically expands to fill the full width when its
 * sibling disappears, so no extra media query is needed for that part.
 * ---------------------------------------------------------------------- */

.auth-page {
  min-height: 100vh;
  display: flex;
  flex-direction: row;
  align-items: stretch;
}

.auth-form-panel {
  flex: 1 1 50%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
  background-color: #f5f5f5;
  background-image: url('/img/auth-bg-admin.png');
  background-repeat: repeat;
  padding: 2rem 1.5rem;
}

:root[data-bs-theme='dark'] .auth-form-panel {
  background-color: #355a32;
  background-image: url('/img/bg-pattern-dark.png');
}

.auth-card {
  width: 100%;
  max-width: 26rem;
  background: #fce4be;
  border-radius: 0.4rem;
  box-shadow: 2px 6px 8px rgba(22, 22, 26, 0.18);
  padding: 2.25rem 2rem;
}

.auth-card .brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 1.5rem;
}

.auth-card .brand img {
  display: block;
  width: 100%;
  max-width: 10rem;
  height: auto;
  object-fit: contain;
}

.auth-logo-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: #fff;
  border-radius: 0.75rem;
  padding: 0.75rem 1.25rem;
  box-shadow: 0 4px 14px rgba(22, 22, 26, 0.16);
}

.auth-card h5 {
  color: var(--brand-primary);
  font-weight: 700;
}

.auth-card .form-label {
  font-weight: 600;
  font-size: 0.85rem;
  color: #2c3f2f;
}

.auth-card .text-muted {
  color: #4f5c47 !important;
}

.auth-card a.small,
.auth-card a.small:hover {
  color: var(--brand-primary);
}

.auth-card .form-control {
  background: #e8f0fe;
  border: 1px solid #ced4da;
}

.auth-card .form-control:focus {
  background: #e8f0fe;
  border-color: var(--brand-primary);
  box-shadow: 0 0 0 0.2rem rgba(37, 97, 66, 0.15);
}

/* Only the language switcher shows here now (the theme toggle button next
   to it is hidden - see login/register/change-password.ejs), so it's
   nudged up closer to the corner and given its own margin instead of
   sitting at the offset that used to center it against both icons. */
.auth-theme-toggle {
  position: absolute;
  top: 0.75rem;
  inset-inline-end: 1.25rem;
  margin: 0.5rem;
}

.auth-mobile-copy {
  margin-top: 1.5rem;
  text-align: center;
  font-size: 0.78rem;
  color: #4f5c47;
}

:root[data-bs-theme='dark'] .auth-mobile-copy {
  color: #d9e8df;
}

/* Features panel - brand-colored marketing side: logo on top, heading +
 * feature bullets in the middle, copyright at the bottom. */

.auth-features-panel {
  flex: 1 1 50%;
  min-width: 0;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  padding: 3rem 2.5rem;
  text-align: center;
  color: #fff;
  background: linear-gradient(135deg, var(--brand-primary), var(--brand-primary-darker));
  position: relative;
  overflow: hidden;
}

.auth-features-panel::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: url('/img/bg-pattern-dark.png');
  background-repeat: repeat;
  opacity: 0.08;
  pointer-events: none;
}

.auth-features-panel > * {
  position: relative;
  z-index: 1;
}

.auth-features-logo {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}

.auth-features-logo img {
  width: 100%;
  max-width: 7rem;
  height: auto;
  object-fit: contain;
}

.auth-logo-badge-lg {
  padding: 1rem 1.75rem;
  border-radius: 1rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
}

.auth-features-logo span {
  font-weight: 700;
  font-size: 1.1rem;
  letter-spacing: 0.02em;
}

/* Basmala above the logo - always shown in Arabic script regardless of the
   active UI language, so it isn't run through t(). */
.auth-features-basmala {
  font-size: 1.05rem;
  font-weight: 600;
  opacity: 0.92;
}

.auth-features-body {
  max-width: 26rem;
}

.auth-features-body h2 {
  font-weight: 700;
  font-size: 1.6rem;
  margin-bottom: 0.75rem;
}

.auth-features-subheading {
  color: rgba(255, 255, 255, 0.82);
  margin-bottom: 1.75rem;
}

.auth-features-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  text-align: start;
}

.auth-features-list li {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 0.5rem;
  padding: 0.65rem 0.9rem;
  font-size: 0.9rem;
}

.auth-features-list li i {
  flex: 0 0 auto;
  width: 2rem;
  height: 2rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--brand-gold);
  color: var(--brand-primary-darker);
  font-size: 1rem;
}

.auth-features-copy {
  font-size: 0.8rem;
  color: rgba(255, 255, 255, 0.7);
}

/* ---------------------------------------------------------------------- *
 * Password show/hide toggle - used on login, change password, register
 * and add-user forms
 * ---------------------------------------------------------------------- */

.password-toggle-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.auth-card .password-toggle-btn {
  background: #e8f0fe;
  border: 1px solid #ced4da;
  color: #4f5c47;
}

.auth-card .password-toggle-btn:hover,
.auth-card .password-toggle-btn:focus {
  background: #dbe7fb;
  color: #2c3f2f;
}

.auth-card .input-group:focus-within .form-control,
.auth-card .input-group:focus-within .password-toggle-btn {
  border-color: var(--brand-primary);
}

/* ---------------------------------------------------------------------- *
 * Notifications bell (topbar)
 * ---------------------------------------------------------------------- */

.notif-badge {
  /* Negative offsets push the badge to sit at the button's outer corner
     (overlapping its border) instead of on top of the bell glyph, which
     otherwise gets partly hidden underneath it. inset-inline-end already
     flips this to the button's left corner in RTL / right corner in LTR
     automatically - no separate ar/en rule needed. */
  position: absolute;
  top: -0.25rem;
  inset-inline-end: -0.25rem;
  min-width: 1.05rem;
  height: 1.05rem;
  padding: 0 0.2rem;
  border-radius: 999px;
  background: #c0392b;
  color: #fff;
  font-size: 0.65rem;
  font-weight: 700;
  line-height: 1.05rem;
  text-align: center;
}

.notif-menu {
  width: 20rem;
  padding: 0;
  overflow: hidden;
}

.notif-menu-header {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--border);
  font-weight: 600;
}

.notif-empty {
  padding: 1.5rem 1rem;
  text-align: center;
  color: var(--text-muted);
}

.notif-item {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  white-space: normal;
}

.notif-item-icon {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 0.6rem;
  background: var(--surface-alt);
  color: var(--brand-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.05rem;
  flex-shrink: 0;
}

.notif-item-title {
  display: block;
  font-weight: 600;
  color: var(--text);
}

.notif-item-sub {
  display: block;
  font-size: 0.78rem;
  color: var(--text-muted);
}

.notif-group-header {
  padding: 0.6rem 1rem 0.2rem;
  color: var(--text-muted);
  text-transform: uppercase;
  font-size: 0.7rem;
  letter-spacing: 0.04em;
}

.notif-view-all {
  color: var(--brand-primary);
  font-weight: 600;
  border-top: 1px solid var(--border);
}

/* Announcements - light-yellow "sticky note" styling shared by the bell
   dropdown (.notif-sticky-wrap li) and the once-per-login popup
   (.notif-sticky-popup, see views/partials/announcement-popup.ejs). */
.notif-sticky-wrap {
  padding: 0;
}

.notif-sticky {
  background: var(--sticky-yellow-bg);
  border: 1px solid var(--sticky-yellow-border);
  border-radius: 0.5rem;
  margin: 0.35rem 0.75rem;
  padding: 0.6rem 0.65rem;
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
}

.notif-sticky-icon {
  background: transparent;
  color: var(--sticky-yellow-text);
}

.notif-sticky-body {
  flex: 1 1 auto;
  font-size: 0.85rem;
  color: var(--sticky-yellow-text);
  white-space: normal;
  word-break: break-word;
}

.notif-sticky-dismiss {
  border: none;
  background: transparent;
  color: var(--sticky-yellow-text);
  opacity: 0.6;
  padding: 0.1rem 0.3rem;
  line-height: 1;
  flex-shrink: 0;
}

.notif-sticky-dismiss:hover {
  opacity: 1;
}

.notif-sticky-popup {
  margin: 0;
}

/* ---------------------------------------------------------------------- *
 * Page loader - shown by default so it's visible from first paint (no
 * flash of the page underneath), faded out once the page finishes
 * loading, and shown again the instant the browser starts leaving for
 * another page. See public/js/page-loader.js for the show/hide triggers.
 * ---------------------------------------------------------------------- */

.page-loader {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  opacity: 1;
  visibility: visible;
  transition: opacity 0.25s ease, visibility 0s linear 0s;
}

.page-loader.is-hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.25s ease, visibility 0s linear 0.25s;
}

.page-loader-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.1rem;
}

.page-loader-logo {
  /* MainLogo.png is a wide ~2:1 lockup (icon + org name + tagline baked
     in), not a square mark - needs real width to stay legible, unlike
     the small icon this used to be. */
  width: 11rem;
  max-width: 70vw;
  height: auto;
  animation: pageLoaderPulse 1.4s ease-in-out infinite;
}

.page-loader-spinner {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  border: 3px solid var(--border);
  border-top-color: var(--brand-primary);
  animation: pageLoaderSpin 0.7s linear infinite;
}

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

@keyframes pageLoaderPulse {
  0%,
  100% {
    opacity: 1;
    transform: scale(1);
  }
  50% {
    opacity: 0.55;
    transform: scale(0.92);
  }
}

/* ---------------------------------------------------------------------- *
 * Site footer - mirrors the brand block used on tarteelbh.com's footer:
 * fixed dark-green bar (same tone as the sidebar) regardless of the
 * active light/dark theme, logo + org name on one side, copyright line
 * on the other. Used at the bottom of the app shell.
 * ---------------------------------------------------------------------- */

.site-footer {
  width: 100%;
  /* relative (not sticky/static) by request - the footer should only
     come into view once the user actually scrolls all the way down to it,
     not stay pinned to the bottom of the viewport while scrolling a long
     page. It has to stay `relative` rather than `static` though: that's
     what keeps .site-footer::before's `position: absolute; inset: 0`
     pattern overlay confined to the footer itself instead of escaping to
     cover the whole page (its nearest positioned ancestor otherwise).
     margin-top: auto is what docks it to the bottom of the screen on a
     short page (.main-column is a flex column stretched to at least
     100vh by .app-shell - see its own rule above - so this pushes the
     footer down to consume whatever's left of that height instead of
     leaving empty space below it); on a long page it simply has no free
     space left to consume, so the footer just sits right after the
     content ends, same as before. */
  position: relative;
  overflow: hidden;
  background: var(--brand-primary-darker);
  color: #d9e8df;
  margin-top: auto;
}

/* Same overlay technique as .sidebar::before / .auth-features-panel::before -
   the brand pattern sits on its own layer at 50% opacity behind the
   footer's actual content, rather than making the text/logo themselves
   translucent. */
.site-footer::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: url('/img/bg-pattern-dark.png');
  background-repeat: repeat;
  opacity: 0.5;
  pointer-events: none;
}

.site-footer > * {
  position: relative;
  z-index: 1;
}

.site-footer-inner {
  max-width: 72rem;
  margin-inline: auto;
  padding: 1rem 1.5rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
}

.site-footer-brand {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.site-footer-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 3rem;
  flex-shrink: 0;
  background: #fff;
  border-radius: 0.5rem;
  padding: 0.3rem 0.6rem;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
}

.site-footer-logo img {
  height: 100%;
  width: auto;
  object-fit: contain;
}

.site-footer-brand-text {
  display: flex;
  flex-direction: column;
  line-height: 1.35;
}

.site-footer-name {
  font-weight: 700;
  font-size: 0.95rem;
  color: #fff;
}

.site-footer-tagline {
  font-size: 0.78rem;
  color: #9db5a8;
}

.site-footer-copy {
  font-size: 0.78rem;
  color: #9db5a8;
}

@media (max-width: 576px) {
  .site-footer-inner {
    flex-direction: column;
    align-items: center;
    text-align: center;
  }
}

/* ---------------------------------------------------------------------- *
 * Table pagination bar - shared across Students / Quran Centers / Users
 * list pages (see partials/pagination-bar.ejs + js/table-pagination.js).
 * ---------------------------------------------------------------------- */

.table-pagination-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 1.25rem;
  padding: 0.85rem 1rem;
  border-top: 1px solid var(--border);
}

.table-pagination-size {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.table-pagination-size .form-select {
  width: auto;
}

.table-pagination-info {
  white-space: nowrap;
}

[data-row-index] {
  color: var(--text-muted);
  width: 1%;
  white-space: nowrap;
}

@media (max-width: 576px) {
  .table-pagination-bar {
    justify-content: center;
    text-align: center;
  }
}

/* Marks a form field as required with a small red asterisk after the label
   text, so a failed save (generic "please fill in all fields" message) is
   self-explanatory before the user ever submits. */
.form-label.required::after {
  content: ' *';
  color: var(--danger, #dc3545);
}

/* ---------------------------------------------------------------------- *
 * Table thumbnail hover preview (partials/photo-hover.ejs + photo-hover.js)
 * - every list table with a photo column (students/staff/users/quranCenters):
 * hovering the small thumbnail pops up a bigger copy of the same photo plus
 * the row's name/label.
 *
 * The popup starts absolutely-positioned right above the thumbnail (used
 * as a same-DOM-position fallback / instant first paint), but every table
 * lives inside a .table-responsive, and Bootstrap's overflow-x:auto on it
 * also clips vertical overflow - CSS computes overflow-y as auto whenever
 * overflow-x isn't visible, even if overflow-y is separately set to
 * visible, so the popup would get cut off near the top of the table no
 * matter how high its z-index is. photo-hover.js works around this: on
 * hover it reparents the popup to <body> and switches it to
 * position:fixed, placed from the thumbnail's actual on-screen coordinates
 * - that escapes the clipping ancestor and every stacking context
 * entirely, so it always renders above everything else on the page.
 * ---------------------------------------------------------------------- */
.photo-hover-preview {
  position: relative;
  display: inline-block;
  line-height: 0;
}

.photo-hover-preview > img {
  cursor: zoom-in;
}

/* Shown on a thumbnail whose row has more than one photo (e.g. an
   activity's photo1..photo5 slots) via a small "+N" badge in the corner -
   set inline by whatever view builds the markup (see activities.ejs),
   since only that page currently has multi-photo rows. Purely a visual
   hint that clicking opens a slideshow, not a slideshow itself. */
.photo-hover-count-badge {
  position: absolute;
  inset-inline-end: -0.35rem;
  inset-block-end: -0.35rem;
  min-width: 1.1rem;
  height: 1.1rem;
  padding: 0 0.25rem;
  border-radius: 999px;
  background: var(--brand-primary);
  color: #fff;
  font-size: 0.6rem;
  font-weight: 700;
  line-height: 1.1rem;
  text-align: center;
  border: 2px solid var(--surface);
}

.photo-hover-preview-popup {
  position: absolute;
  z-index: 1050;
  left: 50%;
  bottom: calc(100% + 0.5rem);
  transform: translateX(-50%) scale(0.92);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  padding: 0.6rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 0.75rem;
  box-shadow: var(--shadow);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.15s ease, transform 0.15s ease;
  white-space: nowrap;
}

/* Applied by photo-hover.js once it reparents the popup to <body>; `left`/
   `top` are then set inline from getBoundingClientRect(), and this anchors
   the popup's bottom-center to that point instead of the CSS above/center
   positioning (which only worked relative to the original DOM parent). */
.photo-hover-preview-popup.photo-hover-preview-popup-fixed {
  position: fixed;
  bottom: auto;
  z-index: 2000;
  transform: translate(-50%, -100%) scale(0.92);
}

.photo-hover-preview-popup img {
  width: 140px;
  height: 140px;
  object-fit: cover;
  border-radius: 0.5rem;
}

.photo-hover-preview-name {
  font-size: 0.8rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--text);
}

.photo-hover-preview:hover .photo-hover-preview-popup {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) scale(1);
}

/* Shown/hidden by photo-hover.js via this class once JS has taken over
   (the plain :hover rule above only applies while the popup is still a
   descendant of .photo-hover-preview, so there's no conflict once it's
   been moved to <body>). */
.photo-hover-preview-popup.is-visible {
  opacity: 1;
  visibility: visible;
}

.photo-hover-preview-popup.photo-hover-preview-popup-fixed.is-visible {
  transform: translate(-50%, -100%) scale(1);
}

/* Added by photo-hover.js when there isn't room above the thumbnail (e.g.
   the first row of a table) - anchors the popup's top-center to the point
   below the thumbnail instead of its bottom-center above it. */
.photo-hover-preview-popup.photo-hover-preview-popup-fixed.photo-hover-preview-popup-flipped {
  transform: translate(-50%, 0) scale(0.92);
}

.photo-hover-preview-popup.photo-hover-preview-popup-fixed.photo-hover-preview-popup-flipped.is-visible {
  transform: translate(-50%, 0) scale(1);
}

@media print {
  .photo-hover-preview-popup {
    display: none !important;
  }
}

/* ---------------------------------------------------------------------- *
 * Click-to-enlarge lightbox (photo-hover.js) - clicking any thumbnail that
 * has a real photo opens that same photo full-size in a centered overlay,
 * closed via the X button, clicking the dark backdrop, or Escape. One
 * lightbox element is built lazily on first use and reused for every
 * thumbnail on the page.
 * ---------------------------------------------------------------------- */
.photo-lightbox {
  position: fixed;
  inset: 0;
  z-index: 3000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.75);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.15s ease;
}

.photo-lightbox.is-open {
  opacity: 1;
  visibility: visible;
}

.photo-lightbox-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  max-width: min(90vw, 640px);
  max-height: 90vh;
}

.photo-lightbox-img {
  max-width: 100%;
  max-height: 80vh;
  border-radius: 0.75rem;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
  object-fit: contain;
  background: var(--surface);
}

.photo-lightbox-name {
  color: #fff;
  font-weight: 600;
  font-size: 1rem;
  text-align: center;
}

.photo-lightbox-close {
  position: fixed;
  top: 1.25rem;
  inset-inline-end: 1.5rem;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.15);
  color: #fff;
  font-size: 1.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.15s ease;
}

.photo-lightbox-close:hover {
  background: rgba(255, 255, 255, 0.3);
}

/* Prev/next arrows for a multi-photo gallery (e.g. an activity's photos) -
   photo-hover.js hides both via [hidden] whenever the current gallery only
   has one photo, so a plain single-photo lightbox looks exactly as before. */
.photo-lightbox-nav {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.15);
  color: #fff;
  font-size: 1.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.15s ease;
}

.photo-lightbox-nav:hover {
  background: rgba(255, 255, 255, 0.3);
}

.photo-lightbox-prev {
  inset-inline-start: 1.5rem;
}

.photo-lightbox-next {
  inset-inline-end: 1.5rem;
}

.photo-lightbox-counter {
  color: rgba(255, 255, 255, 0.85);
  font-size: 0.8rem;
  font-weight: 600;
}

body.photo-lightbox-open {
  overflow: hidden;
}

@media print {
  .photo-lightbox {
    display: none !important;
  }
}

@media (max-width: 576px) {
  .photo-lightbox-nav {
    width: 2.25rem;
    height: 2.25rem;
    font-size: 1rem;
  }
  .photo-lightbox-prev {
    inset-inline-start: 0.5rem;
  }
  .photo-lightbox-next {
    inset-inline-end: 0.5rem;
  }
}

/* ---------------------------------------------------------------------- *
 * List page toolbar: search box + print button, sits above the table
 * inside the same [data-paginate] card.
 * ---------------------------------------------------------------------- */
.table-toolbar {
  padding: 1rem;
  border-bottom: 1px solid var(--border);
}

.table-toolbar .form-control {
  max-width: 20rem;
}

/* Attendance toolbar (search + date-range filter + print) - kept to one
   line rather than wrapping; on a narrow viewport the row scrolls
   horizontally instead of breaking onto a second line. */
.attendance-toolbar {
  overflow-x: auto;
}

/* Add/edit record forms (students/attendance.ejs, students/recite.ejs) - a
   colored ring frames the card while attendance-edit.js/recite-edit.js has
   it in "editing an existing record" mode, so it's obvious the form isn't
   about to add a new row. Shared by any such dual-mode form, not just
   attendance's despite the generic name. */
.form-card-editing {
  transition: box-shadow 0.2s ease, border-color 0.2s ease;
  border-color: var(--brand-primary);
  box-shadow: 0 0 0 3px rgba(37, 97, 66, 0.15);
}

/* ---------------------------------------------------------------------- *
 * Print letterhead: centered logo + app name + report title + print date,
 * shown only in the printed output (see partials/print-letterhead.ejs).
 * Hidden on screen via d-none; Bootstrap's d-print-flex switches it to
 * flex only inside @media print.
 * ---------------------------------------------------------------------- */
.print-letterhead {
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.25rem;
  margin-bottom: 1.25rem;
  padding-bottom: 1rem;
  border-bottom: 2px solid #000;
}

.print-letterhead-logo {
  height: 4.5rem;
  width: auto;
  object-fit: contain;
  margin-bottom: 0.35rem;
}

.print-letterhead-app {
  font-weight: 700;
  font-size: 1rem;
}

.print-letterhead-title {
  font-weight: 700;
  font-size: 1.35rem;
}

.print-letterhead-meta {
  font-size: 0.8rem;
  color: #444;
}

/* ---------------------------------------------------------------------- *
 * Print-only single-record profile form (students/staff/quranCenters
 * profile pages) - see partials/print-profile-form.ejs. Hidden on screen
 * (d-none), shown only inside @media print (d-print-block/d-print-flex).
 * The on-screen photo/info cards on those pages are hidden on print
 * instead, so only this dedicated layout appears in the printed page.
 *
 * Layout: logo (via print-letterhead above), then a photo-on-the-left +
 * main-info-on-the-right row, a divider line, then a contact-info grid
 * below. The photo is kept on the physical left with float/margin (not
 * logical start/end) so it stays on the left even when printing the
 * Arabic (RTL) version, per how this form is meant to look either way.
 * ---------------------------------------------------------------------- */
.print-profile-form {
  margin-top: 0.5rem;
}

.print-profile-identity {
  overflow: hidden; /* clearfix for the floated photo */
  margin-bottom: 1rem;
}

.print-profile-photo {
  float: left;
  width: 140px;
  margin-right: 1.25rem;
  margin-bottom: 0.5rem;
}

.print-profile-photo img {
  width: 140px;
  height: 170px;
  object-fit: cover;
  border: 1px solid #999;
}

.print-profile-photo-placeholder {
  width: 140px;
  height: 170px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #999;
  font-size: 3.5rem;
  color: #999;
}

.print-profile-main {
  overflow: hidden; /* fills the remaining width beside the floated photo */
}

.print-profile-name {
  font-weight: 700;
  font-size: 1.15rem;
}

.print-profile-subtitle {
  font-size: 0.85rem;
  color: #444;
  margin-bottom: 0.5rem;
}

.print-profile-section-label {
  font-weight: 700;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #444;
  margin-bottom: 0.4rem;
}

.print-profile-divider {
  border-top: 1px solid #000;
  margin: 1rem 0;
}

.print-profile-fields {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.5rem 1.5rem;
}

.print-profile-fields-contact {
  grid-template-columns: repeat(2, 1fr);
}

.print-profile-field {
  font-size: 0.9rem;
}

.print-profile-field-label {
  color: #444;
}

.print-profile-field-value {
  font-weight: 600;
}

/* ---------------------------------------------------------------------- *
 * Staff monthly attendance sheet (views/staff/monthly-attendance-sheet.ejs)
 * - a printed per-center sign-in sheet matching the paper form the centers
 * already keep by hand (one row per staff member, one column per day,
 * check-in/check-out stacked inside each cell - see .smas-time-in/
 * .smas-time-out). A full month's worth of day columns can't stay legible
 * even in landscape, so the controller always splits the month into its
 * two calendar halves (1st-15th, 16th-end) and this page prints one half
 * per page (.smas-block-break) - this is also why the page is forced to
 * print landscape - @page has no CSS selector, so that one declaration
 * can't live here in the shared stylesheet (it would force every OTHER
 * printed page in the app into landscape too); it's a small inline
 * <style> block in monthly-attendance-sheet.ejs itself instead, which
 * only loads on this page's print job.
 * ---------------------------------------------------------------------- */

.smas-meta {
  font-size: 0.85rem;
  margin-bottom: 0.4rem;
}

.smas-legend {
  font-size: 0.8rem;
  color: var(--text-muted);
}

.smas-block + .smas-block {
  margin-top: 2rem;
}

.smas-card {
  overflow: hidden;
}

.smas-table {
  font-size: 0.7rem;
  table-layout: fixed;
}

.smas-table th,
.smas-table td {
  padding: 0.25rem 0.2rem;
  text-align: center;
  vertical-align: middle;
  white-space: nowrap;
}

.smas-name-col {
  width: 8rem;
  min-width: 8rem;
  text-align: start;
  overflow: hidden;
  text-overflow: ellipsis;
}

/*
 * One column per day (not a check-in/check-out pair) - see
 * monthly-attendance-sheet.ejs. Each cell stacks both times via
 * .smas-time-in/.smas-time-out, with a once-per-page legend (.smas-legend)
 * saying which line is which, instead of a header row dedicated to it.
 * This is what actually keeps the sheet legible if a print job ever comes
 * out narrower than the intended landscape page (eg. a print
 * driver/dialog that doesn't pick up @page{size:landscape} and falls back
 * to portrait) - with a check-in/check-out column PAIR, that used to force
 * each sub-column so narrow that "Check-in"/"Check-out" (or the Arabic
 * equivalents) wrapped letter-by-letter; a single, twice-as-wide column
 * holding a short numeric time on each of two stacked lines has much more
 * room to spare at the same narrower width, so white-space: nowrap above
 * is enough to keep every line intact instead of wrapping.
 */
.smas-subcol {
  width: 3.2rem;
  min-width: 3.2rem;
}

.smas-time {
  font-variant-numeric: tabular-nums;
  line-height: 1.3;
}

.smas-time-in {
  border-bottom: 1px dotted var(--border);
  margin-bottom: 0.15rem;
  padding-bottom: 0.15rem;
}

.smas-signatures {
  margin-top: 2.5rem;
  font-weight: 600;
}

@media print {
  /* Each half-month block after the first starts on its own page - see
     the comment on dayBlocks in staffController.js's monthlyAttendanceSheet
     for why the month is split up at all. */
  .smas-block-break {
    page-break-before: always;
  }

  .smas-block + .smas-block {
    margin-top: 0;
  }

  /* Shrunk further than the on-screen size (0.7rem) - a calendar-half
     block is still up to 16 narrow day columns plus the name column,
     which only fits on one landscape page at a genuinely small print
     size. */
  .smas-table {
    font-size: 6.5pt;
  }

  .smas-table th,
  .smas-table td {
    border-color: #000 !important;
    padding: 0.2rem 0.15rem;
  }

  /* Wider and bolder than the shared .smas-name-col screen style, and
     bigger than the rest of the table's 6.5pt - the name is what a reader
     actually scans the sheet for, so it gets more visual weight than the
     numeric day/time cells. Paid for by narrowing .smas-subcol below. */
  .smas-name-col {
    width: 36mm;
    min-width: 36mm;
    font-size: 7.5pt;
    font-weight: 700;
  }

  .smas-subcol {
    width: 7mm;
    min-width: 7mm;
  }

  .smas-time-in {
    border-bottom-color: #999;
  }
}

/* ---------------------------------------------------------------------- *
 * Print: list pages print a clean letterhead report - centered logo/title
 * up top, just the table below, no chrome, no action buttons, no
 * pagination controls. table-pagination.js's print handler temporarily
 * shows every row matching the current search before calling window.print().
 * ---------------------------------------------------------------------- */
@media print {
  @page {
    margin-top: 2cm;
    margin-bottom: 2cm;
    margin-left: 1.5cm;
    margin-right: 1.5cm;

    @bottom-center {
      content: counter(page) " / " counter(pages);
      font-size: 9pt;
      color: #444;
    }
  }

  /* Force a plain light report regardless of the on-screen theme (dark
     mode swaps these tokens app-wide, which would otherwise print a
     near-black page). */
  :root,
  :root[data-bs-theme='dark'] {
    --bg: #ffffff;
    --surface: #ffffff;
    --text: #000000;
    --text-muted: #444444;
    --border: #999999;
  }

  /* .app-shell's min-height: 100vh (needed on screen so a short page's
     sidebar/footer still reach the bottom of the viewport) is what causes
     the printed report to spill onto a trailing blank second page: with
     the sidebar/topbar/footer all hidden on print (d-print-none), this
     flex container's only visible content is the short letterhead/report
     itself, but the container is still stretched to a full 100vh - which
     a print "page" very rarely measures to exactly, so it always just
     barely overflows onto an otherwise-empty next page. Printed reports
     never need that min-height, so drop it here. */
  .app-shell {
    display: block !important;
    min-height: 0 !important;
  }

  .main-column {
    margin-inline-start: 0 !important;
  }

  .content {
    padding: 0 !important;
    background: #fff !important;
    background-image: none !important;
  }

  .card {
    border: none !important;
    box-shadow: none !important;
    background: #fff !important;
  }

  .table-responsive {
    overflow: visible !important;
  }

  /* Every printed report table (students/staff/quranCenters/users lists,
     attendance, recite - anything using the shared pagination/print setup)
     gets side margins so it doesn't run edge-to-edge within the @page
     margins above. */
  [data-paginate-table] {
    margin-left: 1rem;
    margin-right: 1rem;
    width: calc(100% - 2rem) !important;
  }

  [data-paginate-table] th,
  [data-paginate-table] td {
    border: 1px solid #999 !important;
    color: #000 !important;
  }

  [data-paginate-table] thead th {
    background: #eee !important;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  /* Keep the active/disabled badge colors legible on print instead of
     stripping them to black-on-white - most browsers still need this hint
     to print background colors even when they render fine on screen. */
  .badge {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  /* ...except the attendance status badge (Present/Late/Absent), which the
     printed attendance report shows as plain text instead of a colored fill
     - keeps the report readable/consistent on a black-and-white printer
     rather than depending on color to convey the status. */
  .attendance-type-badge {
    background: none !important;
    color: #000 !important;
    border: none !important;
    padding: 0 !important;
  }

  /* ...and the users list's Active/Inactive status badge, same reasoning -
     plain text on the printed report instead of a colored fill. */
  .user-status-badge {
    background: none !important;
    color: #000 !important;
    border: none !important;
    padding: 0 !important;
  }

  /* Attendance report table's own column sizing (side margins now come from
     the generic [data-paginate-table] rule above): the day name now has its
     own narrow column (split out from the date column - see
     views/students/attendance.ejs), smaller date/time text since print
     space is tight, and a wider comments column since it's usually the
     longest free-text field. */
  .attendance-report-table .attendance-day-col {
    width: 8%;
  }

  .attendance-report-table .attendance-date-col {
    font-size: 0.7rem;
  }

  .attendance-report-table .attendance-comments-col {
    width: 26%;
  }

  /* Recite report table's own column sizing - same reasoning as attendance
     above, just scoped to its own class since recite has more columns
     overall. */
  .recite-report-table .recite-date-col {
    font-size: 0.7rem;
  }

  .recite-report-table .recite-comments-col {
    width: 20%;
  }
}

/* ==========================================================================
   Mobile pass - phones, portrait and landscape both. Bootstrap's grid
   already reflows the add/edit forms fine (they use col-6/col-md-* pairs
   throughout), and the sidebar/topbar already collapse to an off-canvas
   menu below 992px, so this section is only the patterns that were left
   sized for desktop: the page header action row, and list-page tables that
   have more columns than a phone has room for.
   ========================================================================== */

/* Trim the page's outer padding on small screens - 1.5rem a side eats a
   real chunk of a ~375px-wide phone before any content starts. */
@media (max-width: 575.98px) {
  .content {
    padding: 0.85rem;
  }
}

/* Page header action bar (back button + one or more primary actions) -
   shared by nearly every list/detail page (see page-actions-bar usages in
   views/quranCenters|staff|students|users/index.ejs). On a phone, a back
   button plus a full-text action button ("Add center", icon included) are
   together wider than the screen; that used to just force whichever
   button ran out of room first into an uneven, ugly text-wrap. Instead,
   each direct child gets its own full-width row, and if a child is itself
   a button pair (Quran Centers' "Visits" + "Add center") that pair splits
   evenly so neither button looks squeezed against the other.

   These buttons are normalized to Bootstrap's regular (non -sm) size even
   when the markup uses .btn-sm, and matched by the app-wide
   .btn:not(.btn-sm) rule below for plain Save/submit buttons on add/edit
   forms - previously the two sets of buttons ended up two different
   sizes side by side across pages, which read as inconsistent. Table
   icon-only action buttons are deliberately left out of this and stay
   compact, since they need to stay small to fit a table row.

   Registered-student details is the one page whose actions div can hold
   up to five items (Edit / Print / Send Notes / Accept / Delete), with
   Accept and Delete each wrapped in their own <form> for the confirm-submit
   POST - one level deeper than the plain .btn pairs this block was written
   for. Without the extra rule below, that <form> falls back to its
   default block sizing instead of splitting evenly like its sibling
   buttons, so it either hogs a full row or looks squeezed depending on
   how many items land on its line. The div's own flex-wrap (from
   Bootstrap's .flex-wrap class in the markup) already lets more than two
   items spill onto a second row when they don't fit - this just makes
   forms size the same as plain buttons instead of being the odd one out. */
@media (max-width: 575.98px) {
  .page-actions-bar {
    flex-wrap: wrap;
    row-gap: 0.5rem;
  }

  .page-actions-bar > * {
    flex: 1 1 100%;
    min-width: 0;
  }

  .page-actions-bar > div > .btn,
  .page-actions-bar > div > form {
    flex: 1 1 0;
    min-width: 0;
  }

  .page-actions-bar > div > form > .btn {
    width: 100%;
  }

  /* Registered-student details opts its actions div into full stacking
     (via this extra .page-actions-stack class in the markup) instead of
     the split-evenly-per-row default above - each of its up to five items
     (Edit / Print / Send Notes / Accept / Delete) becomes its own
     full-width row, matching how the back button already behaves on this
     page, since splitting five items evenly per row still read as
     cramped. The extra class keeps this scoped to this one page - every
     other page using the shared pair layout (e.g. Quran Centers' "Visits"
     + "Add center") is unaffected. */
  .page-actions-bar > div.page-actions-stack > .btn,
  .page-actions-bar > div.page-actions-stack > form {
    flex: 1 1 100%;
  }

  .page-actions-bar .btn {
    font-size: 1rem;
    padding: 0.5rem 0.9rem;
    white-space: normal;
    line-height: 1.3;
  }
}

/* List-page tables: the four main list pages (Quran Centers, Staff,
   Students, Users) already hide their least-essential columns below the
   md breakpoint via d-md-table-cell in the view templates themselves, so
   what's left showing on a phone (#, photo, name, status, actions) fits
   without a horizontal scroll in either orientation. Any table that still
   needs to scroll on a phone (report-style tables like attendance/recite,
   or the visits list) just gets a bit more breathing room per row instead
   of looking cramped. */
@media (max-width: 767.98px) {
  .table-responsive table.table {
    font-size: 0.85rem;
  }

  .table-responsive table.table td,
  .table-responsive table.table th {
    padding-inline: 0.55rem;
  }

  .table-responsive table.table td.text-end {
    white-space: nowrap;
  }
}

/* The action-icon buttons in a table's last column (edit / disable-enable
   / delete) sit tight against each other at the smaller mobile padding
   above - a hair more gap keeps them individually tappable. */
@media (max-width: 575.98px) {
  .table-responsive table.table td.text-end .btn,
  .table-responsive table.table td.text-end form {
    margin-inline-start: 0.15rem;
  }
}

/* App-wide button consistency on mobile - normalizes any plain (non
   .btn-sm) button, such as an add/edit form's Save button, to the exact
   same comfortable size as the page-actions-bar buttons above, so every
   primary button reads the same size on a phone rather than some pages
   having a bigger or smaller Save button than others. Table icon-only
   action buttons are untouched (they're already .btn-sm) and stay
   compact so they still fit a table row. */
@media (max-width: 575.98px) {
  .btn:not(.btn-sm) {
    font-size: 1rem;
    padding: 0.5rem 0.9rem;
  }

  /* A form's Save button is same-sized as the header buttons above by
     the rule above, but the header buttons stretch to fill their row
     (see .page-actions-bar > * above) while a bare Save button only
     takes its text width, which still reads as "smaller" next to them.
     Stretching it full-width matches that look and mirrors what several
     report-style forms already do with an explicit .w-100 class. Scoped
     to an actual submit button so it doesn't touch the password
     show/hide toggle buttons (type="button", inside .input-group). */
  form button[type='submit'].btn:not(.btn-sm) {
    width: 100%;
  }
}

/* Photo carousel below each "Global Honour List" table (Centers/Staff/
   Students honour-list-global.ejs) - a native horizontal scroll-snap
   strip instead of the legacy app's Swiper.js carousel. No extra JS
   library needed (just a couple of nudge buttons over normal browser
   scrolling), it respects dark mode via the existing --surface/--text
   variables, and being plain inline-direction scrolling it just works
   correctly in RTL without any special-casing. */
.honour-carousel-track {
  display: flex;
  gap: 0.9rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  padding-block: 0.25rem 0.75rem;
  margin-inline: -0.25rem;
  padding-inline: 0.25rem;
  scrollbar-width: thin;
}

.honour-carousel-track::-webkit-scrollbar {
  height: 6px;
}

.honour-carousel-track::-webkit-scrollbar-thumb {
  background: var(--brand-gold);
  border-radius: 999px;
}

.honour-carousel-item {
  scroll-snap-align: start;
  flex: 0 0 auto;
  width: 148px;
  border-radius: 0.9rem;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  padding: 0.9rem 0.75rem;
  text-align: center;
  text-decoration: none;
  color: var(--text);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.honour-carousel-item:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow);
  color: var(--text);
}

.honour-carousel-photo {
  width: 84px;
  height: 84px;
  margin: 0 auto 0.6rem;
  border-radius: 50%;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  border: 3px solid var(--brand-gold);
}

.honour-carousel-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.honour-carousel-photo i {
  font-size: 2.4rem;
  color: var(--text-muted);
}

.honour-carousel-name {
  font-weight: 600;
  font-size: 0.88rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.honour-carousel-sub {
  font-size: 0.76rem;
  color: var(--text-muted);
  margin-top: 0.15rem;
}

.honour-carousel-comments {
  font-size: 0.74rem;
  color: var(--text-muted);
  margin-top: 0.3rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.honour-carousel-controls .btn {
  border-radius: 50%;
  width: 2.1rem;
  height: 2.1rem;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

@media (max-width: 575.98px) {
  .honour-carousel-controls {
    display: none !important;
  }
}

/* Event cards for the Global Events List page (/EventsGlobalList) - reuses
   the same .honour-carousel-track/-controls scroll-snap strip and the
   generic honour-carousel.js prev/next buttons (it binds to any
   [data-honour-carousel] element, not honour-list-specific despite the
   name), just with a wider text-card item instead of a round photo. */
.event-carousel-item {
  scroll-snap-align: start;
  flex: 0 0 auto;
  width: 260px;
  border-radius: 0.9rem;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-inline-start: 4px solid var(--brand-gold);
  padding: 1rem 1.1rem;
  color: var(--text);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.event-carousel-item:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow);
}

.event-carousel-name {
  font-weight: 700;
  font-size: 0.98rem;
  margin-bottom: 0.5rem;
}

.event-carousel-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.82rem;
  color: var(--text-muted);
  margin-bottom: 0.3rem;
}

.event-carousel-row i {
  color: var(--brand-primary);
  flex-shrink: 0;
}

.event-carousel-map-link {
  font-size: 0.8rem;
  font-weight: 600;
}

/* Inline attachment preview thumbnails (job applier CPR/certificate scans,
   and any similar document-image attachment) - shown directly on the page
   instead of a "view" button, since seeing the document at a glance is the
   point of pulling up these detail pages. object-fit: contain (not cover)
   because these are ID/certificate scans - cropping could hide the actual
   content, unlike a person's photo where cropping to fill is fine. */
.attachment-preview-link {
  display: inline-block;
  border-radius: 0.6rem;
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--surface-alt);
  transition: box-shadow 0.15s ease, transform 0.15s ease;
}

.attachment-preview-link:hover {
  box-shadow: var(--shadow);
  transform: translateY(-2px);
}

.attachment-preview-img {
  display: block;
  width: 100%;
  max-width: 220px;
  height: 160px;
  object-fit: contain;
  background: var(--surface);
}

/* The vendored Bootstrap Icons (v1.11.3) has no place-of-worship icons, so
   the app's own "mosque" icon - used everywhere a Quran center needs an
   icon instead of the generic bi-building - is a small hand-drawn SVG
   silhouette (dome, finial, archway, two minarets). It drops into the
   exact same <i class="bi bi-mosque"> markup as every other icon: the
   vendor CSS's shared ".bi::before" rule still applies (inline-block,
   -.125em baseline) but has no glyph to show since bi-mosque isn't a real
   font glyph, so the actual shape is painted on the <i> element itself via
   a currentColor mask - same 1em box, same baseline, recolors and resizes
   exactly like a real icon font glyph wherever it's used. */
.bi-mosque {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+IDxyZWN0IHg9IjEuMSIgeT0iNiIgd2lkdGg9IjEuMyIgaGVpZ2h0PSI4IiBmaWxsPSIjZmZmIi8+IDxwb2x5Z29uIHBvaW50cz0iMS4xLDYgMS43NSw0LjMgMi40LDYiIGZpbGw9IiNmZmYiLz4gPHJlY3QgeD0iMTMuNiIgeT0iNiIgd2lkdGg9IjEuMyIgaGVpZ2h0PSI4IiBmaWxsPSIjZmZmIi8+IDxwb2x5Z29uIHBvaW50cz0iMTMuNiw2IDE0LjI1LDQuMyAxNC45LDYiIGZpbGw9IiNmZmYiLz4gPGNpcmNsZSBjeD0iOCIgY3k9IjEuNiIgcj0iMC41NSIgZmlsbD0iI2ZmZiIvPiA8cGF0aCBkPSJNOCwyLjNDNi4zLDIuMyw1LjIsNC4zLDUuMSw2LjNMMTAuOSw2LjNDMTAuOCw0LjMsOS43LDIuMyw4LDIuM1oiIGZpbGw9IiNmZmYiLz4gPHBhdGggZD0iTTMuMyw2LjNMMTIuNyw2LjNMMTIuNywxNEw4LjksMTRMOC45LDExLjNDOC45LDEwLjM1LDguNSw5LjgsOCw5LjhDNy41LDkuOCw3LjEsMTAuMzUsNy4xLDExLjNMNy4xLDE0TDMuMywxNFoiIGZpbGw9IiNmZmYiLz4gPC9zdmc+");
  mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+IDxyZWN0IHg9IjEuMSIgeT0iNiIgd2lkdGg9IjEuMyIgaGVpZ2h0PSI4IiBmaWxsPSIjZmZmIi8+IDxwb2x5Z29uIHBvaW50cz0iMS4xLDYgMS43NSw0LjMgMi40LDYiIGZpbGw9IiNmZmYiLz4gPHJlY3QgeD0iMTMuNiIgeT0iNiIgd2lkdGg9IjEuMyIgaGVpZ2h0PSI4IiBmaWxsPSIjZmZmIi8+IDxwb2x5Z29uIHBvaW50cz0iMTMuNiw2IDE0LjI1LDQuMyAxNC45LDYiIGZpbGw9IiNmZmYiLz4gPGNpcmNsZSBjeD0iOCIgY3k9IjEuNiIgcj0iMC41NSIgZmlsbD0iI2ZmZiIvPiA8cGF0aCBkPSJNOCwyLjNDNi4zLDIuMyw1LjIsNC4zLDUuMSw2LjNMMTAuOSw2LjNDMTAuOCw0LjMsOS43LDIuMyw4LDIuM1oiIGZpbGw9IiNmZmYiLz4gPHBhdGggZD0iTTMuMyw2LjNMMTIuNyw2LjNMMTIuNywxNEw4LjksMTRMOC45LDExLjNDOC45LDEwLjM1LDguNSw5LjgsOCw5LjhDNy41LDkuOCw3LjEsMTAuMzUsNy4xLDExLjNMNy4xLDE0TDMuMywxNFoiIGZpbGw9IiNmZmYiLz4gPC9zdmc+");
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

/* Brief "saved" flash on quranCenters/classes.ejs's assign-class <select>
   after a successful ajax save (see public/js/assign-class.js) - the only
   feedback the row gets now that saving no longer reloads the page. */
@keyframes assign-saved-flash {
  0% {
    box-shadow: 0 0 0 0.2rem rgba(37, 97, 66, 0.45);
    border-color: var(--brand-primary);
  }
  100% {
    box-shadow: none;
  }
}

.is-assign-saved {
  animation: assign-saved-flash 1.1s ease-out;
}
