/* ============================================================================
   public/src/features/profile/profile.css
   CSS variables at themes.css 
   Architecture:
     .identity-band   → hero unit: avatar, name, level ring, 4 stat chips
     .profile-layout  → 2-col grid: .profile-main (wide) + .profile-rail (sticky)
     .profile-main    → heatmap+mastery row, history, bookmarks, flagged
     .profile-rail    → badges, next-badges, leaderboard, quick stats

   Bilingual: every directional property below uses CSS logical properties
   (inline-start/end, margin-inline-*, etc.) so the layout mirrors correctly
   when <html dir="rtl|ltr"> flips. No physical left/right is used in this
   file except where documented as an intentional non-mirroring exception.
   ============================================================================ */

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  font-size: 16px;
  scroll-behavior: smooth;
  scrollbar-width: thin;
}

body {
  font-family: "IBM Plex Sans Arabic", sans-serif;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
  padding-top: 16px;
  padding-bottom: 40px;
  padding-left: 16px;
  padding-right: calc(var(--sidebar-collapsed-width) + 16px);
  transition: padding-right 250ms cubic-bezier(0.4, 0, 0.2, 1);
  position: relative;
  overflow-x: clip;
}

body.sidebar-expanded {
  padding-right: calc(var(--sidebar-expanded-width) + 16px);
}

.container {
  max-width: 1360px;
  margin-inline: auto;
  position: relative;
  z-index: 1;
}

@media (max-width: 768px) {
  body {
    padding-right: 16px;
  }
}

/* ============================================================
         IDENTITY BAND — Facebook/X-style cover: a short banner strip
         with the avatar overlapping its bottom edge, not a tall 16:9
         hero the rest of the page sits underneath. Structure:
           .identity-cover    → fixed-height banner (edit btn top-end)
           .identity-body     → padded card body below the cover
             .identity-header   → avatar (overlapping upward) + name/
                                   role/info/level row
             .stats-grid        → 4 stat chips
         ============================================================ */
.identity-band {
  border-radius: 24px;
  box-shadow: var(--shadow-xl);
  border: 1px solid var(--color-border-light);
  margin-bottom: 24px;
  position: relative;
  overflow: hidden;
  background-color: var(--color-surface);
}

.identity-band::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  height: 6px;
  background: var(--gradient-accent);
  z-index: 2;
}

/* ============================================================
         COVER — a short strip (not a tall 16:9 hero), so the identity
         row and stats are visible without scrolling. object-fit-style
         center-cover cropping via background-size:cover. Empty by
         default (no background-image, no .has-thumbnail) — the gradient
         scrim and edit button only make sense once a picture is set.
         ============================================================ */
.identity-cover {
  position: relative;
  height: clamp(135px, 19vw, 250px);
  z-index: 0;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  overflow: hidden;
}

.identity-cover.has-thumbnail {
  background-color: var(--color-background-secondary);
}

.identity-cover.has-thumbnail::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Gentle scrim along the bottom edge only, so the cover photo
           itself stays visible instead of being washed out — this strip
           is short enough that a heavy scrim would hide most of it. */
  background: linear-gradient(to bottom,
      transparent 60%,
      color-mix(in srgb, var(--color-surface) 45%, transparent) 100%);
}

/* Default cover gradient when the user/admin hasn't set one.
       Distinct treatment from the circular avatar so defaults are
       visually different and clearly convey "no custom cover yet":
         - Its own offset palette entry (see thumbnailGradientForName in
           avatarEngine.js) so the hue never matches the avatar's own
           background gradient.
         - A faint diagonal stripe overlay (::before) so the banner has a
           distinct texture at a glance, not just a different flat color —
           this also keeps a plain gradient fill from being mistaken for a
           loading/broken-image state.
       background-image itself (the actual gradient colors/angle) is set
       inline by renderThumbnail()/setupVisitorView() per-user; this rule
       only supplies the shared texture and sizing. */
.identity-cover.is-default-thumbnail {
  background-size: cover;
  background-position: center;
  position: relative;
}

.identity-cover.is-default-thumbnail::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(135deg,
      rgba(255, 255, 255, 0.08) 0px,
      rgba(255, 255, 255, 0.08) 2px,
      transparent 2px,
      transparent 14px);
  pointer-events: none;
}

.thumbnail-edit-btn {
  position: absolute;
  inset-block-start: 12px;
  inset-inline-start: 12px;
  z-index: 3;
  pointer-events: auto;
  display: none;
  /* shown via .identity-cover:hover / :focus-within, and unconditionally on touch via JS toggling .is-editable */
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--color-border-light);
  background: var(--color-surface);
  color: var(--color-text);
  cursor: pointer;
  box-shadow: var(--shadow-md);
  transition:
    transform 0.15s ease,
    background-color 0.15s ease;
}

.identity-cover:hover .thumbnail-edit-btn,
.identity-cover:focus-within .thumbnail-edit-btn,
.identity-cover.is-editable .thumbnail-edit-btn {
  display: inline-flex;
}

.thumbnail-edit-btn:hover {
  background: var(--color-primary);
  color: #fff;
  transform: scale(1.06);
}

@media (max-width: 480px) {
  .thumbnail-edit-btn {
    width: 26px;
    height: 26px;
    inset-block-start: 8px;
    inset-inline-start: 8px;
  }
}

/* ============================================================
         BODY — everything below the cover. The avatar is pulled
         upward with a negative margin so it overlaps the cover's
         bottom edge (Facebook/X signature), while the rest of the
         identity row sits at normal body flow beside/below it.
         ============================================================ */
.identity-body {
  position: relative;
  z-index: 1;
  padding: 0 clamp(20px, 4vw, 40px) 24px;
}

.identity-header {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 16px 24px;
  /* Pull up so the avatar overlaps the cover above by roughly half
           its own height (Facebook/X-style) — tuned to the 128px avatar
           below; the responsive rules adjust this to match each
           breakpoint's own avatar size. */
  margin-block-start: -64px;
  padding-block-end: 12px;
}

.identity-text {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
  flex: 1 1 240px;
  padding-block-end: 4px;
}

.identity-text h1 {
  unicode-bidi: plaintext; /* truncate by the name's own direction */
  text-align: start;
  font-size: clamp(1.35rem, 2.4vw, 1.85rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.15;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.editable-username {
  cursor: pointer;
  border-radius: 6px;
  padding: 0 4px;
  margin-inline-start: -4px;
  transition: background-color 0.2s ease;
}

.editable-username:hover {
  background-color: var(--color-background-secondary);
}

.editable-username::after {
  content: "✎";
  font-size: 0.5em;
  vertical-align: middle;
  margin-inline-start: 6px;
  display: none;
  color: var(--color-text-secondary);
  transition: opacity 0.2s ease;
}

.editable-username:hover::after {
  display: inline;
}

.weekly-recap {
  align-self: flex-start;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--color-text-secondary);
  background: var(--color-background-secondary);
  padding: 6px 16px;
  border-radius: 100px;
  border: 1px solid var(--color-border-light);
}

.admin-handle-display-wrap {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
}

.admin-handle-display {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--color-text-secondary);
  direction: ltr;
}

.inline-copy-link-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 4px;
  border: none;
  background: transparent;
  color: var(--color-text-tertiary);
  cursor: pointer;
  transition: all 0.15s ease;
}

.inline-copy-link-btn:hover {
  background: var(--color-background-secondary);
  color: var(--color-text);
}

.inline-copy-icon-success {
  display: none;
}

.inline-copy-link-btn.is-copied {
  color: var(--color-success, #16a34a);
}

.inline-copy-link-btn.is-copied .inline-copy-icon-default {
  display: none;
}

.inline-copy-link-btn.is-copied .inline-copy-icon-success {
  display: inline-flex;
}

.profile-bio-wrap {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin-top: 8px;
  max-width: 520px;
}

.profile-bio-display {
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--color-text-secondary);
  white-space: pre-wrap;
  word-break: break-word;
  margin: 0;
}

.profile-bio-display.profile-bio-empty {
  font-style: italic;
  color: var(--color-text-tertiary);
}

.profile-bio-edit-btn {
  flex-shrink: 0;
  margin-top: 2px;
}

.profile-bio-form {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
  max-width: 520px;
}

.profile-bio-textarea {
  width: 100%;
  min-height: 72px;
  resize: vertical;
  border-radius: 10px;
  border: 1px solid var(--color-border-light);
  background: var(--color-background-secondary);
  color: var(--color-text);
  font-family: inherit;
  font-size: 0.9rem;
  line-height: 1.5;
  padding: 10px 12px;
}

.profile-bio-textarea:focus {
  outline: none;
  border-color: var(--color-primary, #6366f1);
}

.profile-bio-textarea:placeholder-shown {
  text-align: right;
}

.profile-bio-form-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.profile-bio-counter {
  font-size: 0.75rem;
  color: var(--color-text-tertiary);
}

.profile-bio-form-buttons {
  display: flex;
  gap: 8px;
}

.profile-bio-cancel-btn,
.profile-bio-save-btn {
  border: none;
  border-radius: 8px;
  padding: 6px 14px;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  transition: opacity 0.15s ease;
}

.profile-bio-cancel-btn {
  background: var(--color-background-secondary);
  color: var(--color-text-secondary);
}

.profile-bio-save-btn {
  background: var(--color-primary, #6366f1);
  color: #fff;
}

.profile-bio-cancel-btn:hover,
.profile-bio-save-btn:hover {
  opacity: 0.85;
}

.role-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  color: #fff;
  box-shadow: var(--shadow-sm);
  border: 2px solid var(--color-surface);
  cursor: pointer;
  /* real button now (opens #roleInfoOverlay) — not just a hover-tooltip */
  transition:
    transform 0.2s ease,
    box-shadow 0.2s ease;
}

.role-badge:hover {
  transform: scale(1.15) rotate(5deg);
  box-shadow: var(--shadow-md);
}

.role-badge:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

.role-badge.admin-badge {
  background: linear-gradient(135deg, #3b82f6, #1e40af);
  box-shadow: 0 4px 10px rgba(37, 99, 235, 0.4);
}

.role-badge.developer-badge {
  background: linear-gradient(135deg, #a855f7, #6d28d9);
  box-shadow: 0 4px 10px rgba(109, 40, 217, 0.4);
}

/* ============================================================
         ROLE INFO MODAL — opened by #roleBadge. Reuses the shared
         .contact-overlay / .contact-overlay-card shell (defined outside
         this file) for the backdrop + positioning, but overrides its own
         layout here rather than the inline-style approach #userInfoOverlay
         used, since this card's content (centered icon/title/desc, no
         header row) doesn't fit that shell's default header/body/footer
         structure. Scoped to #roleInfoOverlay so #userInfoOverlay and
         #contactDevOverlay, which still use the shared shell as-is, are
         unaffected.
         ============================================================ */
.role-info-card {
  max-width: 340px;
  padding: 28px 24px 24px;
  text-align: center;
  position: relative;
}

.role-info-close-btn {
  position: absolute;
  inset-block-start: 10px;
  inset-inline-end: 10px;
  background: transparent;
  border: none;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  color: var(--color-text-secondary);
  padding: 6px;
  border-radius: 50%;
  transition: background-color 0.15s ease;
}

.role-info-close-btn:hover {
  background: var(--color-background-secondary);
  color: var(--color-text);
}

.role-info-icon {
  width: 52px;
  height: 52px;
  margin: 0 auto 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: #fff;
  box-shadow: var(--shadow-md);
}

/* Reuse the exact same gradients as .role-badge.admin-badge /
         .developer-badge so the modal icon visually matches the badge
         that opened it. */
.role-info-icon.admin-badge {
  background: linear-gradient(135deg, #3b82f6, #1e40af);
}

.role-info-icon.developer-badge {
  background: linear-gradient(135deg, #a855f7, #6d28d9);
}

.role-info-title {
  font-size: 1.15rem;
  font-weight: 800;
  margin-bottom: 8px;
}

.role-info-desc {
  font-size: 0.9rem;
  line-height: 1.7;
  color: var(--color-text-secondary);
}

/* Avatar — sized to read clearly overlapping the cover photo above
         it (Facebook/X-style), larger than a plain inline avatar would
         need to be. border matches .identity-band's own surface color so
         it reads as a cutout over the cover, same trick FB/X use. */
.avatar-wrapper {
  position: relative;
  width: 128px;
  height: 128px;
  flex-shrink: 0;
  /* Locks the column to the avatar's own size so it can never be
           squeezed by neighboring grid/flex content (was the cause of the
           avatar overlapping the name/buttons next to it). */
  min-width: 128px;
}

.avatar-wrapper>#avatarImage {
  width: 128px;
  height: 128px;
  border-radius: 50%;
  object-fit: cover;
  border: 4px solid var(--color-surface);
  box-shadow: var(--shadow-lg);
  display: block;
  background: var(--color-background-secondary);
}

.avatar-edit-btn {
  position: absolute;
  inset-block-end: 2px;
  inset-inline-start: 2px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--color-primary);
  color: #fff;
  border: 2px solid var(--color-surface);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: var(--shadow-md);
  transition: transform 0.15s ease;
}

.avatar-edit-btn:hover {
  transform: scale(1.08);
}

@media (max-width: 480px) {
  .avatar-edit-btn {
    width: 26px;
    height: 26px;
  }
}

/* Stats Grid — 4 compact chips under the identity row. #userStatsGrid
         sits inside .identity-body, which already supplies the horizontal
         inset, so no side padding is added here. #adminStatsGrid reuses
         this same .stats-grid class as a page-level sibling of
         .identity-band instead (see markup) and must NOT get any of
         .identity-body's padding — it supplies its own margin instead. */
.stats-grid {
  position: relative;
  z-index: 1;
  display: grid;
  /* auto-fit instead of a hard repeat(4, 1fr): the sidebar reserves up to
     ~256px of horizontal space when expanded, so the *container* can be
     much narrower than the viewport even on wide/square desktop screens.
     A viewport-based breakpoint can't see that, so the grid used to stay
     at 4 columns and clip. Sizing off the container's own width fixes it
     everywhere the sidebar can shrink things, not just on mobile. */
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 14px;
}

.identity-body .stats-grid {
  margin-block-start: 20px;
}

.stat-card {
  padding: 16px;
  border-radius: 14px;
  display: flex;
  align-items: center;
  gap: 14px;
  border: 1px solid var(--color-border-light);
  background-color: var(--color-background-secondary);
  transition:
    transform 0.2s ease,
    box-shadow 0.2s ease;
}

.stat-card:hover {
  
  box-shadow: var(--shadow-md);
}

.stat-card .icon {
  font-size: 1.75rem;
  flex-shrink: 0;
}

.stat-card .icon .section-icon {
  width: 1.75rem;
  height: 1.75rem;
  color: var(--color-text-primary);
}

.stat-card:hover .icon .section-icon .icon-line {
  animation: section-icon-draw 1.2s cubic-bezier(0.45, 0, 0.55, 1) infinite alternate;
}

.stat-card .info {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.stat-card .label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.stat-card .value {
  font-size: 1.3rem;
  font-weight: 800;
}

/* Admin stats grid — same skeleton as .stats-grid, but visually set apart
         (dashed divider, muted surface, small eyebrow label) so it doesn't read
         as a stray, mismatched duplicate of the main stats row underneath it. */
#adminStatsGrid {
  grid-template-columns: repeat(4, 1fr);
  position: relative;
  margin-top: 18px;
  padding-top: 22px;
}

#adminStatsGrid::before {
  content: "إحصاءات الإشراف";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 4px;
  inset-inline-end: 4px;
  transform: translateY(-50%);
  background: var(--color-surface);
  padding-inline: 10px;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--color-text-tertiary);
  text-transform: uppercase;
  width: fit-content;
  max-width: calc(100% - 8px);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

#adminStatsGrid::after {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  border-top: 1px dashed var(--color-border-light);
}

#adminStatsGrid .stat-card {
  background-color: color-mix(in srgb,
      var(--color-primary) 4%,
      var(--color-background-secondary));
  border-color: color-mix(in srgb,
      var(--color-primary) 14%,
      var(--color-border-light));
}

@media (max-width: 1024px) {
  #adminStatsGrid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 768px) {

  body,
  body.sidebar-expanded {
    padding-right: 16px;
    padding-bottom: calc(var(--bottom-nav-height, 54px) + env(safe-area-inset-bottom, 0px) + 16px);
  }

  #adminStatsGrid {
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
  }

  /* 4 admin cards over 2 columns: two even rows, nothing stranded. (A third
     "إجمالي البلاغات" row would need a full-width :last-child rule again if
     the card count ever becomes odd.) */
}

@media (max-width: 480px) {
  #adminStatsGrid {
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
  }

  #adminStatsGrid .stat-card {
    padding: 10px;
  }

  #adminStatsGrid .stat-card .icon {
    font-size: 1.2rem;
  }

  #adminStatsGrid .stat-card .icon .section-icon {
    width: 1.2rem;
    height: 1.2rem;
  }

  #adminStatsGrid .stat-card .value {
    font-size: 0.95rem;
  }
}

/* ============================================================
         MAIN GRID
         ============================================================ */
.profile-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: 24px;
  align-items: start;
}

/* Applied (alongside hiding .profile-rail) on a visited /@handle profile,
   where the rail never has anything visitor-visible in it (badges,
   next-badges, and stats are developer-only; the local leaderboard hides
   itself too) — collapses the grid to a single full-width column instead
   of leaving the 340px rail track reserved as blank space. */
.profile-layout.no-rail {
  grid-template-columns: minmax(0, 1fr);
}

.profile-main {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 28px;
}

.profile-rail {
  min-width: 0;
  position: sticky;
  top: 24px;
  display: flex;
  flex-direction: column;
  gap: 20px;
  /* Cap height so a very long rail scrolls independently instead of
           pushing the footer far below the shorter main column. */
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  scrollbar-width: thin;
  padding-inline-end: 2px;
}

.widget-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
}

.content-section h2 {
  font-size: 1.15rem;
  font-weight: 700;
  margin-bottom: 14px;
}

/* ============================================================
         TOP ADMINS GALLERY — horizontal scrollable strip of all top
         admins at the bottom of .profile-main, replacing the old
         #adminLeaderboardCard vertical list. Each item: circular avatar
         with a YouTube-style notch-cut role badge, handle, upload count.
         ============================================================ */
.admin-gallery-wrap {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
}

.admin-gallery {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  scroll-behavior: smooth;
  scroll-snap-type: x proximity;
  padding: 6px 2px 10px;
  flex: 1;
  min-width: 0;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}

.admin-gallery-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  width: 120px;
  scroll-snap-align: start;
  text-decoration: none;
  color: inherit;
  padding: 6px 4px 8px;
  border-radius: 12px;
  transition:
    transform 0.15s ease,
    background 0.15s ease;
}

a.admin-gallery-item:hover,
a.admin-gallery-item:focus-visible {
  background: var(--color-background-secondary);
  
  outline: none;
}

.admin-gallery-avatar-wrap {
  position: relative;
  width: 96px;
  height: 96px;
  flex-shrink: 0;
}

.admin-gallery-avatar {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  object-fit: cover;
  display: block;
  box-shadow: var(--shadow-sm);
  -webkit-mask-image: radial-gradient(circle 17px at 12px calc(100% - 12px),
      transparent 98%,
      #000 100%);
  mask-image: radial-gradient(circle 17px at 12px calc(100% - 12px),
      transparent 98%,
      #000 100%);
}

html[dir="ltr"] .admin-gallery-avatar {
  -webkit-mask-image: radial-gradient(circle 17px at calc(100% - 12px) calc(100% - 12px),
      transparent 98%,
      #000 100%);
  mask-image: radial-gradient(circle 17px at calc(100% - 12px) calc(100% - 12px),
      transparent 98%,
      #000 100%);
}

.admin-gallery-avatar:not(.has-badge) {
  -webkit-mask-image: none;
  mask-image: none;
}

.admin-gallery-badge.role-badge {
  position: absolute;
  inset-inline-end: -3px;
  inset-block-end: -3px;
  width: 30px;
  height: 30px;
  border-width: 0;
  cursor: default;
}

.admin-gallery-badge.role-badge:hover {
  transform: none;
}

.admin-gallery-handle {
  font-size: 0.78rem;
  font-weight: 700;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  direction: ltr;
}

.admin-gallery-meta {
  font-size: 0.7rem;
  color: var(--color-text-secondary);
  text-align: center;
}

.admin-gallery-arrow {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid var(--color-border-light);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  transition:
    background 0.15s ease,
    color 0.15s ease,
    transform 0.15s ease;
}

.admin-gallery-arrow svg {
  width: 16px;
  height: 16px;
}

.admin-gallery-arrow:hover {
  background: var(--color-background-secondary);
  color: var(--color-text-primary);
  transform: scale(1.08);
}

.admin-gallery-arrow:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

@media (max-width: 480px) {
  .admin-gallery-arrow {
    display: none;
  }
}

/* ============================================================
         SECTION-HEADING ICONS — replace the old emoji glyphs in
         .profile-main / .profile-rail heading text with inline SVGs,
         following the same hover line-draw approach as .offline-icon
         (public/offline.html): dash-array set on each path, animated on
         hover by animating stroke-dashoffset. #badgeContainer's own
         per-badge icons are untouched (those come from BADGES data, not
         a heading here).
         ============================================================ */
.content-section h2,
.sidebar-card h3 {
  display: flex;
  align-items: center;
  gap: 8px;
}

.section-icon {
  width: 19px;
  height: 19px;
  flex-shrink: 0;
  color: var(--color-primary, #6366f1);
  transition: color 0.2s ease;
}

.section-icon .icon-line {
  /* Draw length for the shared `section-icon-draw` keyframe. Section icons
     are hand-tuned to a 60-unit dash; .empty-state-icon overrides this to 1
     because its paths carry pathLength="1" (see the empty-state block
     further down). Keeping the keyframe var-driven lets both scales share
     one animation instead of silently animating to the wrong end value. */
  --icon-draw-length: 60;
  stroke-dasharray: 60;
  stroke-dashoffset: 0;
}

h2:hover .section-icon,
h3:hover .section-icon {
  color: var(--color-primary-hover, #4f46e5);
}

h2:hover .section-icon .icon-line,
h3:hover .section-icon .icon-line {
  animation: section-icon-draw 1.2s cubic-bezier(0.45, 0, 0.55, 1) infinite alternate;
}

@keyframes section-icon-draw {
  0% {
    stroke-dashoffset: 0;
  }

  100% {
    /* Was a hard-coded 60, which under-drew every .empty-state-icon
       (dasharray 80) and left the stroke permanently half-dashed. Resolved
       per-element from --icon-draw-length instead. */
    stroke-dashoffset: var(--icon-draw-length, 60);
  }
}

/* "الأداء الفائق" (High Performance) turns the line-draw off for empty-state
   icons only. .section-icon (heading + stat-card icons) is intentionally
   excluded from this kill switch — that hover animation stays active even
   under High Performance / reduced motion mode.

   themes.css does crush animation-duration to 0.001ms under this attribute,
   but that is not sufficient on its own here: `section-icon-draw` ends at
   stroke-dashoffset: var(--icon-draw-length) — the fully ERASED frame — so
   collapsing its duration to zero just lands on the erased end state. Nothing
   pins the strokes back, which is why the icons need an explicit cancel plus a
   reset of the offset rather than a shorter duration. */
html[data-motion="reduced"] .empty-state:hover .empty-state-icon .icon-line,
html[data-motion="reduced"] .empty-state:focus-within .empty-state-icon .icon-line,
html[data-motion="reduced"] .empty-state.is-drawing .empty-state-icon .icon-line {
  animation: none !important;
  stroke-dashoffset: 0 !important;
}

/* ============================================================
         HISTORY / BOOKMARKS / FLAGGED — dense list rows
         All three widgets reuse .history-item, rebuilt as a compact
         grid row: [score-pill] [info] [actions]. Height target ~56px
         so 8+ rows are visible on a standard mobile viewport without
         scrolling.
         ============================================================ */
.history-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.history-item {
  display: grid;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border-radius: 12px;
  min-height: 56px;
  box-shadow: var(--shadow-sm);
  transition:
    transform 0.15s ease,
    box-shadow 0.15s ease,
    border-color 0.15s ease;
  background-color: var(--color-surface);
  border: 1px solid var(--color-border-light);
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "info   score"
    "info   actions";
}

.history-item:hover {
  border-color: var(--color-primary-light);
  box-shadow: var(--shadow-md);
}

.history-info {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  grid-area: info;
}

.history-info h4 {
  unicode-bidi: plaintext; /* truncate by the name's own direction */
  text-align: start;
  font-size: 0.9rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.history-info small {
  unicode-bidi: plaintext; /* truncate by the text's own direction */
  text-align: start;
  font-size: 0.72rem;
  color: var(--color-text-tertiary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.history-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex-shrink: 0;
  grid-area: actions;
  justify-self: end;
}

/* Score pill: compact fixed-width badge instead of a bulky rectangle */
.history-score {
  font-weight: 800;
  font-size: 0.78rem;
  padding: 5px 10px;
  border-radius: 100px;
  white-space: nowrap;
  flex-shrink: 0;
}

.history-score.pass {
  background: var(--color-success-light);
  color: var(--color-success);
}

.history-score.fail {
  background: var(--color-error-light);
  color: var(--color-error);
}

/* "Go to exam / question" — icon-only on compact rows. The label text is
         kept in the DOM (from profile.js/profileWidgets.js templates) for
         accessibility/SEO, but visually replaced with a compact arrow glyph via
         a fixed square button so it never wraps or bloats the row. */
.history-actions .nav-btn.primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0 !important;
  font-size: 0 !important;
  border-radius: 8px;
  flex-shrink: 0;
  background: var(--color-primary-light);
  color: var(--color-primary);
  transition:
    background-color 0.15s ease,
    transform 0.15s ease;
}

.history-actions .nav-btn.primary::before {
  content: "";
  width: 15px;
  height: 15px;
  /* Arrow icon via mask so `currentColor` (the pill's text color) drives
           it — flips automatically with `transform` in RTL vs LTR below. */
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  background-color: currentColor;
  /* Default markup direction is RTL (arrow should point start/right);
           mirror it for LTR documents below. */
  transform: scaleX(-1);
}

html[dir="ltr"] .history-actions .nav-btn.primary::before {
  transform: none;
}

.history-actions .nav-btn.primary:hover {
  background: var(--color-primary);
  color: #fff;
  transform: scale(1.06);
}

.history-actions .nav-btn.primary:active {
  transform: scale(0.95);
}

.history-actions .nav-btn.primary:focus-visible {
  outline: 3px solid var(--color-focus-ring, var(--color-primary));
  outline-offset: 2px;
}

/* Delete / unstar icon buttons — compact square, icon only */
.delete-btn,
.unstar-btn {
  background: transparent;
  border: none;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  cursor: pointer;
  flex-shrink: 0;
  transition:
    transform 0.15s ease,
    background-color 0.15s ease,
    color 0.15s ease;
  display: flex;
  align-items: center;
  justify-content: center;
}

.delete-btn svg,
.unstar-btn svg {
  width: 16px;
  height: 16px;
}

.delete-btn {
  color: var(--color-error);
}

.delete-btn:hover {
  background: var(--color-error-light);
  transform: scale(1.06);
}

.delete-btn:active {
  transform: scale(0.95);
}

.unstar-btn {
  color: var(--color-text-secondary);
}

.unstar-btn:hover {
  background: var(--color-hover-overlay);
  color: var(--color-primary);
  transform: scale(1.06);
}

.unstar-btn:active {
  transform: scale(0.95);
}

.delete-btn:focus-visible,
.unstar-btn:focus-visible {
  outline: 3px solid var(--color-focus-ring, var(--color-primary));
  outline-offset: 2px;
}

/* ============================================================
         SIDEBAR / RAIL CARDS
         ============================================================ */
.sidebar-card {
  padding: 22px;
  background-color: var(--color-surface);
  border-radius: 16px;
  box-shadow: var(--shadow-md);
  border: 1px solid var(--color-border-light);
}

.sidebar-card h3 {
  margin-bottom: 14px;
  font-size: 1.05rem;
}

/* Badge Display
     Each tile is a fixed-height flip card: icon + label sit on the front
     face at all times (label is clamped to 2 lines with ellipsis so a
     long word like "Perfectionist" can never blow out the grid), and the
     description swaps in on hover/focus so it never has to be crammed
     into the tile itself. */
.badge-display-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
  gap: 10px;
}

.dash-badge {
  position: relative;
  text-align: center;
  height: 112px;
  padding: 8px 6px;
  border-radius: 12px;
  transition: all 0.2s ease;
  cursor: pointer;
  border: 1px solid var(--color-border);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.dash-badge:hover,
.dash-badge:focus-visible,
.dash-badge.is-revealed {
  
  box-shadow: var(--shadow-md);
  outline: none;
}

.dash-badge .badge-icon {
  font-size: 1.75rem;
  margin-bottom: 4px;
  transition: opacity 0.15s ease;
}

.dash-badge .badge-title {
  font-size: 0.72rem;
  font-weight: 600;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.25;
  word-break: break-word;
  transition: opacity 0.15s ease;
}

/* Description face: absolutely positioned over the tile, fades in on
   hover/focus/tap in place of the icon+title. Dynamic sizing ensures descriptions
   fit cleanly without overflowing or getting cut off. */
.badge-desc-overlay {
  position: absolute;
  inset: 0;
  padding: 8px 6px;
  font-size: 0.74rem;
  font-weight: 500;
  line-height: 1.35;
  color: var(--color-text);
  background: var(--color-surface, var(--color-background-secondary));
  opacity: 0;
  transition: opacity 0.18s ease;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  overflow-y: auto;
  scrollbar-width: none;
  word-break: break-word;
}

.badge-desc-overlay::-webkit-scrollbar {
  display: none;
}

.badge-desc-overlay.desc-long {
  font-size: 0.67rem;
  line-height: 1.25;
}

.badge-desc-overlay.desc-medium {
  font-size: 0.72rem;
  line-height: 1.3;
}

.badge-desc-overlay.desc-short {
  font-size: 0.78rem;
  line-height: 1.35;
}

.dash-badge:hover .badge-icon,
.dash-badge:hover .badge-title,
.dash-badge:focus .badge-icon,
.dash-badge:focus .badge-title,
.dash-badge:focus-visible .badge-icon,
.dash-badge:focus-visible .badge-title,
.dash-badge.is-revealed .badge-icon,
.dash-badge.is-revealed .badge-title {
  opacity: 0;
}

.dash-badge:hover .badge-desc-overlay,
.dash-badge:focus .badge-desc-overlay,
.dash-badge:focus-visible .badge-desc-overlay,
.dash-badge.is-revealed .badge-desc-overlay {
  opacity: 1;
}

/* Leaderboard */
.leaderboard-list {
  display: flex;
  flex-direction: column;
}

.lb-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 6px;
  border-block-end: 1px solid var(--color-border-light);
  transition: background 0.2s ease;
  font-size: 0.85rem;
}

.lb-row:last-child {
  border-block-end: none;
}

.lb-row.highlight {
  font-weight: 700;
  border-inline-start: 4px solid var(--color-primary);
  padding-inline-start: 8px;
  background: color-mix(in srgb, var(--color-primary) 5%, transparent);
  border-radius: 8px;
}

/* Rows with an avatar (both bot and real-admin leaderboards) */
.lb-row-avatar {
  gap: 10px;
}

.lb-rank {
  font-weight: 800;
  color: var(--color-primary);
  width: 18px;
  flex-shrink: 0;
  font-size: 0.8rem;
  text-align: center;
}

.lb-name {
  unicode-bidi: plaintext; /* truncate by the name's own direction */
  text-align: start;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lb-metric {
  flex-shrink: 0;
  white-space: nowrap;
}

.lb-avatar-hover {
  position: relative;
  flex-shrink: 0;
  display: flex;
}

.lb-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  object-fit: cover;
  border: 2px solid var(--color-surface);
  box-shadow: var(--shadow-sm);
  flex-shrink: 0;
  transition: transform 0.15s ease;
}

.lb-avatar-hover:hover .lb-avatar {
  transform: scale(1.1);
}

/* Hover card: a single shared tooltip element, appended directly to
         <body> and driven by JS (see leaderboardIdentity.js's attachHoverCard),
         rather than one `position: absolute` card per row. .profile-rail
         scrolls (overflow-y: auto), and an absolutely-positioned descendant
         can never escape a scrolling ancestor's clipping box no matter its
         z-index — so a per-row card was always going to get cut off there.
         `position: fixed` on a body-level element sidesteps that entirely. */
.lb-hover-card {
  position: fixed;
  min-width: 180px;
  max-width: 220px;
  background: var(--color-surface);
  border: 1px solid var(--color-border-light);
  border-radius: 12px;
  box-shadow: var(--shadow-lg);
  padding: 12px 14px;
  z-index: 1000;
  opacity: 0;
  pointer-events: none;
  transition:
    opacity 0.15s ease,
    transform 0.15s ease;
  text-align: start;
  transform: translateY(4px);
}

.lb-hover-card.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.lb-hover-card.is-interactive.is-visible {
  pointer-events: auto;
}

.lb-hover-card::after {
  content: "";
  position: absolute;
  inset-inline-start: 50%;
  transform: translateX(50%);
  border: 6px solid transparent;
}

/* Arrow direction flips depending on whether JS placed the card above
         or below the avatar (near the top of the viewport, below is used). */
.lb-hover-card[data-placement="top"]::after {
  inset-block-start: 100%;
  border-top-color: var(--color-surface);
}

.lb-hover-card[data-placement="bottom"]::after {
  inset-block-end: 100%;
  border-bottom-color: var(--color-surface);
}

.lb-hover-name {
  display: block;
  font-weight: 800;
  font-size: 0.85rem;
  margin-bottom: 6px;
  white-space: normal;
}

.lb-hover-card-lore {
  min-width: 200px;
  max-width: 240px;
}

.lb-hover-lore {
  display: block;
  font-size: 0.74rem;
  line-height: 1.6;
  color: var(--color-text-secondary);
  font-style: italic;
}

/* Statistics Container */
.stats-container {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.stat-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 9px 0;
  border-block-end: 1px solid var(--color-border-light);
  font-size: 0.85rem;
}

.stat-row:last-child {
  border-block-end: none;
}

.stat-row strong {
  font-size: 1rem;
}

/* Empty States */
.empty-state {
  text-align: center;
  padding: 32px 20px;
  border-radius: 15px;
}

.empty-state-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 12px;
  opacity: 0.35;
  color: var(--color-text-secondary);
  transition:
    opacity 0.2s ease,
    color 0.2s ease;
}

.empty-state-icon svg {
  width: 44px;
  height: 44px;
}

.empty-state-icon .icon-line {
  --icon-draw-length: 1;
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
}

.empty-state:hover .empty-state-icon {
  opacity: 0.6;
  color: var(--color-primary, #6366f1);
}

.empty-state:hover .empty-state-icon .icon-line,
.empty-state:focus-within .empty-state-icon .icon-line,
.empty-state.is-drawing .empty-state-icon .icon-line {
  animation: section-icon-draw 1.2s cubic-bezier(0.45, 0, 0.55, 1) infinite alternate;
}

.empty-state:hover .empty-state-icon,
.empty-state:focus-within .empty-state-icon,
.empty-state.is-drawing .empty-state-icon {
  opacity: 0.6;
  color: var(--color-primary, #6366f1);
}

/* ============================================================
         ACTIVITY HEATMAP — redesigned after the 21st.dev heatmap-chart
         component: rounded cells with a soft enter animation, a wider
         card (spans the full widget-row instead of sharing 1fr with
         category mastery), month/day axis labels, and a legend that
         mirrors the swatch styling used in the cells themselves.
         Still degrades to a snap-scroll strip with fading edges when
         the natural grid is wider than the container.
         ============================================================ */
.heatmap-widget-card {
  display: flex;
  flex-direction: column;
  min-width: 0;
  /* Span the full .widget-row instead of sharing 1fr with the mastery
           card — the heatmap reads far better wide, mastery doesn't need it. */
  grid-column: 1 / -1;
}

#activityHeatmap {
  /* Compact year grid: ~53 weeks need to scroll, but cells stay dense
           enough that most of a desktop card shows ~8–10 months at once. */
  --heatmap-cell: clamp(9px, 1.35vw, 12px);
  --heatmap-gap: 2px;
  min-width: 0;
}

.heatmap-body {
  position: relative;
  display: flex;
  flex-direction: row-reverse;
  gap: 6px;
  min-width: 0;
  justify-content: center;
}

/* Edge fades live on the body wrapper — NOT on .heatmap-grid. Masking the
         scrollport itself breaks touch horizontal scrolling on iOS/WebKit. */
.heatmap-body.is-overflowing::before,
.heatmap-body.is-overflowing::after {
  content: "";
  position: absolute;
  inset-block: 0;
  width: 18px;
  z-index: 2;
  pointer-events: none;
}

.heatmap-body.is-overflowing::before {
  inset-inline-start: 0;
  background: linear-gradient(to inline-end,
      var(--color-surface) 0%,
      transparent 100%);
}

.heatmap-body.is-overflowing::after {
  inset-inline-end: 0;
  background: linear-gradient(to inline-start,
      var(--color-surface) 0%,
      transparent 100%);
}

.heatmap-daylabels {
  display: flex;
  flex-direction: column;
  gap: var(--heatmap-gap);
  padding-top: 2px;
  font-size: 0.58rem;
  font-weight: 600;
  color: var(--color-text-tertiary);
  flex-shrink: 0;
  width: 3.25rem;
  line-height: 1.1;
  position: relative;
  z-index: 3;
  background: var(--color-surface);
}

.heatmap-daylabels span {
  height: var(--heatmap-cell);
  display: flex;
  align-items: center;
  justify-content: flex-start;
  white-space: nowrap;
}

.heatmap-daylabels-spacer {
  height: 16px !important;
}

.heatmap-grid {
  display: flex;
  flex-direction: row-reverse;
  gap: var(--heatmap-gap);
  overflow-x: auto;
  overflow-y: hidden;
  padding-block-end: 8px;
  padding-block-start: 2px;
  padding-inline: 6px;
  scroll-snap-type: x proximity;
  flex: 0 1 auto;
  min-width: 0;
  justify-content: flex-start;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  touch-action: pan-x pinch-zoom;
  scrollbar-width: thin;
  scrollbar-color: var(--color-border) transparent;
}

.heatmap-grid.is-overflowing {
  flex: 1 1 0%;
}

.heatmap-grid::-webkit-scrollbar {
  height: 6px;
}

.heatmap-grid::-webkit-scrollbar-thumb {
  background: var(--color-border);
  border-radius: 999px;
}

.heatmap-col {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--heatmap-gap);
  scroll-snap-align: start;
  /* Lock width to the cell — full Arabic month names must NOT stretch
           a single week column (that was creating huge empty gaps between months). */
  width: var(--heatmap-cell);
  flex: 0 0 var(--heatmap-cell);
}

.heatmap-col-label {
  position: absolute;
  inset-block-start: 0;
  inset-inline-end: 0;
  height: 16px;
  font-size: 0.62rem;
  font-weight: 600;
  color: var(--color-text-tertiary);
  white-space: nowrap;
  overflow: visible;
  line-height: 1;
  pointer-events: none;
  z-index: 1;
}

/* Reserve label row space inside the column without letting the text
         contribute to the column's flex width. */
.heatmap-col::before {
  content: "";
  display: block;
  height: 16px;
  flex-shrink: 0;
}

.heatmap-cell {
  width: var(--heatmap-cell);
  height: var(--heatmap-cell);
  border-radius: 3px;
  background: var(--color-background-secondary);
  display: inline-block;
  flex-shrink: 0;
  opacity: 0;
  transform: scale(0.5);
  animation: heatmapCellIn 0.4s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  transition:
    transform 0.15s ease,
    box-shadow 0.15s ease;
  cursor: default;
}

.heatmap-cell:hover {
  transform: scale(1.28);
  box-shadow:
    0 0 0 2px var(--color-surface),
    0 2px 6px rgba(0, 0, 0, 0.18);
  position: relative;
  z-index: 2;
}

@keyframes heatmapCellIn {
  from {
    opacity: 0;
    transform: scale(0.5);
  }

  to {
    opacity: 1;
    transform: scale(1);
  }
}

html[data-motion="reduced"] {
  .heatmap-cell {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

.heatmap-cell[data-level="0"] {
  background: var(--color-background-secondary);
  box-shadow: inset 0 0 0 1px var(--color-border-light);
}

.heatmap-cell[data-level="1"] {
  background: var(--color-primary-light);
}

.heatmap-cell[data-level="2"] {
  background: color-mix(in srgb,
      var(--color-primary) 45%,
      var(--color-primary-light));
}

.heatmap-cell[data-level="3"] {
  background: color-mix(in srgb, var(--color-primary) 75%, transparent);
}

.heatmap-cell[data-level="4"] {
  background: var(--color-primary);
  box-shadow: 0 0 8px color-mix(in srgb, var(--color-primary) 55%, transparent);
}

/* Out-of-year week padding (before Jan 1 / after Dec 31) — muted so
         they read as disabled vs empty in-year days (data-level="0"). */
.heatmap-cell-disabled {
  background: transparent;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-border-light) 55%, transparent);
  cursor: default;
  animation-name: heatmapCellInDisabled;
}

.heatmap-cell-disabled:hover {
  transform: scale(1);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-border-light) 55%, transparent);
}

@keyframes heatmapCellInDisabled {
  from {
    opacity: 0;
    transform: scale(0.5);
  }

  to {
    opacity: 0.35;
    transform: scale(1);
  }
}

html[data-motion="reduced"] {
  .heatmap-cell-disabled {
    animation: none;
    opacity: 0.35;
    transform: none;
  }
}

.heatmap-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: auto;
  padding-top: 14px;
  font-size: 0.78rem;
  color: var(--color-text-secondary);
  flex-wrap: wrap;
  gap: 8px;
}

.heatmap-footer strong {
  color: var(--color-text-primary);
  font-weight: 700;
}

.heatmap-legend {
  display: flex;
  align-items: center;
  gap: 5px;
}

.heatmap-legend .heatmap-cell {
  animation: none;
  opacity: 1;
  transform: none;
  width: 12px;
  height: 12px;
  border-radius: 3px;
}

.heatmap-legend .heatmap-cell:hover {
  transform: none;
  box-shadow: none;
}

.heatmap-hover-card {
  min-width: 140px;
  max-width: 200px;
  padding: 10px 12px;
  text-align: center;
  pointer-events: none;
}

.heatmap-hover-date {
  display: block;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--color-text-secondary);
  margin-bottom: 4px;
}

.heatmap-hover-count {
  display: block;
  font-size: 0.85rem;
  font-weight: 800;
  color: var(--color-text-primary);
}

/* ============================================================
         CATEGORY MASTERY — up to 8 rows (renderCategoryMastery() already
         caps at 8), which on some layouts pushes the card visibly taller
         than heatmap-widget-card beside it in .widget-row. Collapsed by
         default to a max-height showing ~3 rows, with a fade + the shared
         .show-more-row/.show-more-btn pattern (already used by History/
         Bookmarks/Flagged) to reveal the rest. renderCategoryMastery()
         only adds the collapse/toggle when there are enough rows to need
         it — 3 or fewer rows render with no cap and no button at all.
         ============================================================ */
.category-mastery-card {
  min-width: 0;
}

.mastery-list {
  overflow: hidden;
  position: relative;
  max-height: 195px;
  transition: max-height 0.3s ease;
}

.mastery-list.is-expanded {
  max-height: 1200px;
  /* generous ceiling well above 8 rows' actual height */
}

/* Fade-to-transparent cue at the clipped edge — hidden once expanded. */
.mastery-list.is-collapsible:not(.is-expanded)::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  height: 36px;
  background: linear-gradient(to bottom, transparent, var(--color-surface) 90%);
  pointer-events: none;
}

.category-mastery-card .show-more-row {
  margin-block-start: 14px;
  margin-block-end: 0;
}

.mastery-row {
  margin-bottom: 14px;
}

.mastery-row:last-child {
  margin-bottom: 0;
}

.mastery-row-header {
  display: flex;
  justify-content: space-between;
  font-size: 0.82rem;
  margin-bottom: 6px;
  gap: 8px;
}

.mastery-title {
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.mastery-value {
  font-weight: 700;
  flex-shrink: 0;
}

.mastery-bar-track {
  height: 7px;
  background: var(--color-background-secondary);
  border-radius: 100px;
  overflow: hidden;
}

.mastery-bar-fill {
  height: 100%;
  border-radius: 100px;
  transition: width 0.5s ease;
}

.mastery-bar-fill.mastery-success {
  background: var(--color-success);
}

.mastery-bar-fill.mastery-warning {
  background: var(--color-warning);
}

.mastery-bar-fill.mastery-error {
  background: var(--color-error);
}

.mastery-attempts {
  font-size: 0.7rem;
  color: var(--color-text-tertiary);
  margin-top: 4px;
  display: inline-block;
}

/* ============================================================
         NEXT BADGE TEASERS
         ============================================================ */
.next-badge-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 0;
}

.next-badge-icon {
  font-size: 1.4rem;
  flex-shrink: 0;
  width: 32px;
  text-align: center;
}

.next-badge-info {
  flex: 1;
  min-width: 0;
}

.next-badge-header {
  display: flex;
  justify-content: space-between;
  font-size: 0.78rem;
  margin-bottom: 5px;
  gap: 8px;
}

.next-badge-title {
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.next-badge-fraction {
  color: var(--color-text-tertiary);
  flex-shrink: 0;
}

.next-badge-bar-track {
  height: 6px;
  background: var(--color-background-secondary);
  border-radius: 100px;
  overflow: hidden;
}

.next-badge-bar-fill {
  height: 100%;
  background: var(--gradient-accent);
  border-radius: 100px;
  transition: width 0.5s ease;
}

/* ============================================================
         INFINITE SCROLL SENTINEL
         ============================================================ */
.infinite-scroll-sentinel {
  height: 1px;
}

/* ============================================================
         SHOW MORE — pill button centered on a horizontal rule, after
         the first page of a list (History / Bookmarks / Flagged /
         Uploaded Quizzes). Chevron flips when expanded via
         .show-more-row.is-expanded — used for the "Show less" state.
         ============================================================ */
.show-more-row {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-block: 18px 4px;
}

.show-more-row::before,
.show-more-row::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--color-border-light);
}

.show-more-btn {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 20px;
  border-radius: 999px;
  border: 1px solid var(--color-border-light);
  background: var(--color-surface);
  color: var(--color-text);
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
  transition:
    background-color 0.15s ease,
    transform 0.1s ease;
}

.show-more-btn:hover {
  background: var(--color-background-secondary);
}

.show-more-btn:active {
  transform: scale(0.97);
}

.show-more-btn svg {
  transition: transform 0.2s ease;
}

.show-more-row.is-expanded .show-more-btn svg {
  transform: rotate(180deg);
}

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

/* Tablet: rail moves below main content, still 2-col stats */
@media (max-width: 1024px) {
  .profile-layout {
    grid-template-columns: 1fr;
  }

  .profile-rail {
    position: static;
    max-height: none;
    overflow-y: visible;
  }

  .widget-row {
    grid-template-columns: 1fr;
  }

  /* .identity-header already wraps (flex-wrap: wrap); once the level
           gauge drops to its own row, let it take the full row width
           instead of sitting flex-basis 280px off to one side. */
  .identity-level {
    flex-basis: 100%;
    justify-content: flex-start;
  }
}

@media (max-width: 768px) {
  body {
    padding-block: 20px;
    padding-inline-start: 16px;
  }

  .identity-cover {
    height: clamp(100px, 29vw, 155px);
  }

  .identity-body {
    padding: 0 18px 20px;
  }

  .avatar-wrapper,
  .avatar-wrapper>#avatarImage {
    width: 84px;
    height: 84px;
    min-width: 84px;
  }

  .identity-header {
    /* Half of the smaller 84px mobile avatar, matching the desktop
             ratio (see .identity-header's own -64px comment). */
    margin-block-start: -42px;
    gap: 12px 16px;
    padding-block-end: 10px;
  }

  .profile-layout {
    gap: 18px;
  }

  .profile-main {
    gap: 20px;
  }

  /* History rows: keep the same compact grid, just tighten padding —
           this is the key mobile-density fix. No column collapsing to
           flex-column here, so rows stay ~52px tall and 8+ fit on screen. */
  .history-item {
    padding: 8px 10px;
    min-height: 52px;
    gap: 8px;
  }

  .history-info h4 {
    font-size: 0.85rem;
  }

  .history-actions {
    gap: 4px;
  }

  .history-score {
    font-size: 0.7rem;
    padding: 4px 8px;
  }

  .history-actions .nav-btn.primary,
  .delete-btn,
  .unstar-btn {
    width: 30px;
    height: 30px;
  }

  .badge-display-grid {
    grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
    gap: 8px;
  }

  .dash-badge {
    height: 106px;
    padding: 6px 4px;
  }

  .badge-desc-overlay {
    padding: 6px 4px;
  }

  .badge-desc-overlay.desc-long {
    font-size: 0.65rem;
    line-height: 1.22;
  }

  .badge-desc-overlay.desc-medium {
    font-size: 0.7rem;
    line-height: 1.26;
  }

  .badge-desc-overlay.desc-short {
    font-size: 0.76rem;
    line-height: 1.3;
  }
}

@media (max-width: 480px) {
  .identity-cover {
    height: clamp(80px, 33vw, 122px);
  }

  .avatar-wrapper,
  .avatar-wrapper>#avatarImage {
    width: 68px;
    height: 68px;
    min-width: 68px;
  }

  .identity-header {
    margin-block-start: -34px;
  }

  .stats-grid {
    gap: 10px;
  }

  .stat-card {
    padding: 12px;
    gap: 10px;
  }

  .stat-card .icon {
    font-size: 1.4rem;
  }

  .stat-card .icon .section-icon {
    width: 1.4rem;
    height: 1.4rem;
  }

  .stat-card .value {
    font-size: 1.1rem;
  }

  .history-score {
    grid-area: score;
    justify-self: end;
  }
}

/* ==================== Avatar Picker Overlay ==================== */
.avatar-picker-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: var(--z-modal);
  opacity: 0;
  transition: opacity 0.2s ease;
  padding: 20px;
}

.avatar-picker-overlay.open {
  opacity: 1;
}

.avatar-picker-card {
  background: var(--color-surface);
  border-radius: 20px;
  max-width: 420px;
  width: 100%;
  box-shadow: var(--shadow-xl);
  border: 1px solid var(--color-border-light);
  overflow: hidden;
}

.avatar-picker-scroll {
  padding: 28px;
  max-height: 90vh;
  max-height: calc(100dvh - 40px);
  overflow-y: auto;
}

@media (max-width: 480px) {
  .avatar-picker-scroll {
    padding: 20px;
    max-height: calc(100dvh - 24px);
  }

  .avatar-picker-overlay {
    padding: 12px;
  }
}

.avatar-picker-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.avatar-picker-header h3 {
  font-size: 1.15rem;
}

.avatar-picker-close-btn {
  background: transparent;
  border: none;
  cursor: pointer;
  color: var(--color-text-secondary);
  padding: 4px;
  display: flex;
}

/* ============================================================
         PICKER MODE TABS — "الصورة الشخصية" / "الصورة المصغرة".
         Same overlay, avatarPicker.js swaps which store/grid/labels
         are active; this is just the switch itself.
         ============================================================ */
.avatar-picker-tabs {
  display: flex;
  gap: 4px;
  padding: 4px;
  margin-bottom: 20px;
  background: var(--color-background-secondary);
  border-radius: 12px;
}

.avatar-picker-tab {
  flex: 1;
  padding: 8px 12px;
  border: none;
  border-radius: 9px;
  background: transparent;
  color: var(--color-text-secondary);
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  transition:
    background-color 0.15s ease,
    color 0.15s ease;
}

.avatar-picker-tab.is-active {
  background: var(--color-surface);
  color: var(--color-text);
  box-shadow: var(--shadow-sm);
}

.avatar-picker-section {
  margin-bottom: 24px;
}

.avatar-picker-section-label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--color-text-secondary);
  margin-bottom: 12px;
  display: block;
}

.avatar-preset-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
}

.avatar-preset-btn {
  aspect-ratio: 1;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
  color: #fff;
  font-weight: 700;
  font-size: 1.25rem;
  box-shadow: var(--shadow-sm);
  transition:
    transform 0.15s ease,
    border-color 0.15s ease,
    box-shadow 0.15s ease;
}

.avatar-preset-btn:hover {
  transform: scale(1.08);
  border-color: var(--color-surface);
  box-shadow:
    0 0 0 2px var(--color-text-primary),
    var(--shadow-md);
}

/* Thumbnail preset grid uses the same button/grid mechanics as the avatar
       grid above, but previews should read as small banner swatches (matching
       the cover's own wide aspect) rather than circular avatar swatches. */
.thumbnail-preset-btn {
  aspect-ratio: 3 / 1;
  border-radius: 10px;
  grid-column: span 2;
  /* 2 swatches per row instead of 4, given the wide aspect */
}

.thumbnail-preset-btn:hover {
  transform: scale(1.03);
}

.avatar-picker-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.avatar-action-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border-radius: 12px;
  border: 1px solid var(--color-border);
  background: var(--color-background-secondary);
  color: var(--color-text-primary);
  cursor: pointer;
  font-size: 0.9rem;
  font-weight: 500;
  transition: background 0.15s ease;
}

.avatar-action-btn:hover {
  background: var(--color-hover-overlay);
}

.avatar-action-btn.danger {
  color: var(--color-error);
}

/* Primary variant — used for the crop step's confirm button, where it's
       the clear "commit this action" choice next to a plain-styled cancel. */
.avatar-action-btn.primary {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: #fff;
}

.avatar-action-btn.primary:hover {
  background: color-mix(in srgb, var(--color-primary) 85%, black);
}

.avatar-dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 16px;
  border: 2px dashed var(--color-border);
  border-radius: 12px;
  transition:
    border-color 0.15s ease,
    background 0.15s ease;
}

.avatar-dropzone .avatar-action-btn {
  width: 100%;
}

.avatar-dropzone-hint {
  font-size: 0.78rem;
  color: var(--color-text-tertiary);
  text-align: center;
}

.avatar-dropzone.drag-over {
  border-color: var(--color-primary);
  background: var(--color-hover-overlay);
}

.avatar-dropzone.drag-over .avatar-dropzone-hint {
  color: var(--color-primary);
}

.avatar-camera-preview {
  width: 100%;
  border-radius: 12px;
  margin-top: 10px;
  display: none;
  background: #000;
  aspect-ratio: 1;
  object-fit: cover;
}

/* Thumbnail overlay's camera preview needs the wide aspect, not the
       avatar's square — same element/class, scoped by which overlay it's
       nested in rather than a second class, since every other rule in
       .avatar-camera-preview (radius, background, object-fit) is identical
       between the two. */
#thumbnailPickerOverlay .avatar-camera-preview {
  aspect-ratio: 3 / 1;
}

/* ============================================================
         CROP STEP — appears in place of the picker body once a file is
         chosen (upload/drop/camera), before the picture is actually saved.
         See cropperEngine.js for the interaction model this drives:
         dragging pans, wheel/pinch/the slider below zoom, around whatever
         point the user is interacting with.
         ============================================================ */
.avatar-crop-step {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.avatar-crop-stage {
  position: relative;
  width: 100%;
  overflow: hidden;
  border-radius: 12px;
  background: var(--color-background-secondary);
  cursor: grab;
  touch-action: none;
  /* prevent the browser's own pan/zoom from fighting the cropper's pointer handling */
  user-select: none;
}

.avatar-crop-stage:active {
  cursor: grabbing;
}

.avatar-crop-stage-square {
  aspect-ratio: 1;
  /* Circular mask so what's previewed while cropping matches the
         circular avatar it will actually render as. */
  border-radius: 50%;
}

.avatar-crop-stage-wide {
  aspect-ratio: 3 / 1;
}

/* Positioned by cropperEngine.js: absolutely centered at natural size,
       then panned/scaled via an inline transform (translate + scale) set on
       every drag/zoom update. Centering here (rather than in the transform
       itself) keeps the transform's math to pure translate+scale, with no
       baked-in centering offset to account for. */
.cropper-image {
  position: absolute;
  top: 50%;
  left: 50%;
  max-width: none;
  transform-origin: 0 0;
  pointer-events: none;
  /* the stage element owns pointer/drag handling, not the img itself */
}

.avatar-crop-hint {
  margin: 0;
  font-size: 0.78rem;
  color: var(--color-text-tertiary);
  text-align: center;
}

.avatar-crop-zoom-row {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--color-text-tertiary);
}

.avatar-crop-zoom-slider {
  flex: 1;
  accent-color: var(--color-primary);
}

.avatar-crop-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

/* ==================== Footer ==================== */
.watermark {
  text-align: center;
  margin-top: 60px;
  padding-top: 32px;
  border-top: 1px solid var(--color-border-light);
  font-size: 0.8rem;
  font-weight: 500;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.watermark p {
  margin: 0;
}

.watermark a {
  color: var(--color-primary);
  text-decoration: none;
  font-weight: 600;
}

/* ==================== Nav Role Badge Overlay ==================== */
/* Intentionally empty. This file used to carry its own .nav-badge-overlay
         sizing/position block, duplicating the one in side-menu.css. Since
         profile.html loads side-menu.css *after* profile.css and both rules had
         identical specificity, every shared property here was already being
         overwritten — the block only ever contributed confusion about which of
         the two was authoritative, and a second place to forget to update.
         The badge is now owned entirely by side-menu.css (loaded on every page
         that has a sidebar, this one included). */

.heatmap-hover-card {
  min-width: max-content;
  max-width: max-content;
  padding: 6px 10px;
  font-size: 0.85rem;
  text-align: center;
  color: var(--color-text);
  font-weight: 500;
}

@media (max-width: 480px) {
  #activityHeatmap {
    --heatmap-cell: 9px;
    --heatmap-gap: 2px;
  }

  .heatmap-daylabels {
    width: 1.75rem;
    font-size: 0.5rem;
  }

  .heatmap-body.is-overflowing::before,
  .heatmap-body.is-overflowing::after {
    width: 12px;
  }

  .heatmap-col-label {
    font-size: 0.55rem;
  }

  .heatmap-grid {
    padding-block-end: 10px;
  }
}

/* ============================================================
   PROFILE SKELETON — structural loading placeholder
   ------------------------------------------------------------
   Mirrors the real DOM one-for-one (identity band -> cover strip ->
   overlapping avatar -> stats grid -> two-column .profile-layout) so the
   swap to real content produces no layout shift. Every dimension below is
   lifted from the matching real rule rather than eyeballed:

     .skeleton-cover        <- .identity-cover   height: clamp(135px, 19vw, 250px)
     .skeleton-avatar       <- .avatar-wrapper   128px / margin-block-start: -64px
     .skeleton-stats        <- .stats-grid       auto-fit minmax(130px, 1fr) / gap 14px
     .skeleton-layout       <- .profile-layout   minmax(0, 1fr) 340px / gap 24px
     .skeleton-main         <- .profile-main     flex column / gap 28px
     .skeleton-rail         <- .profile-rail     flex column / gap 20px

   A duplicated skeleton tree (rather than shimmering the real cards in
   place) is deliberate: the real cards can't be measured or safely styled
   until their JS has populated them, and half-built cards flashing through
   a shimmer look worse than a clean placeholder.
   ============================================================ */

/* Single source of truth for the shimmer, so every block below stays in
   sync and there's one place to retune the speed/contrast. */
.profile-skeleton {
  --skeleton-base: var(--color-background-secondary, #e9edf2);
  --skeleton-sheen: var(--color-surface, #f6f8fb);
  --skeleton-speed: 1.4s;
}

.skeleton-block {
  background-color: var(--skeleton-base);
  /* 200% width + a moving background-position is cheaper than animating a
     pseudo-element's transform on dozens of nodes: background-position is
     compositor-friendly and doesn't create a stacking context per block. */
  background-image: linear-gradient(90deg,
      transparent 0%,
      color-mix(in srgb, var(--skeleton-sheen) 70%, transparent) 50%,
      transparent 100%);
  background-size: 200% 100%;
  background-repeat: no-repeat;
  animation: skeleton-sweep var(--skeleton-speed) ease-in-out infinite;
  border-radius: 8px;
  /* Blocks are pure decoration — never let one swallow a click that was
     meant for the real UI underneath during the cross-fade. */
  pointer-events: none;
}

@keyframes skeleton-sweep {
  0% {
    background-position: 200% 0;
  }

  100% {
    background-position: -200% 0;
  }
}

/* ── Visibility handshake ───────────────────────────────────────────────
   .container starts with .is-profile-loading (set in profile.html's inline
   critical CSS, so it applies before this stylesheet has even downloaded).
   profile.js removes it once the first real render lands. Real content is
   hidden with visibility+position rather than display:none so the browser
   still lays it out and can start decoding the avatar image underneath. */
.container.is-profile-loading>.identity-band,
.container.is-profile-loading>.profile-layout,
.container.is-profile-loading>#adminStatsGrid {
  visibility: hidden;
  position: absolute;
  inset-inline: 0;
  opacity: 0;
}

.container:not(.is-profile-loading)>.profile-skeleton {
  display: none;
}

.container:not(.is-profile-loading)>.identity-band,
.container:not(.is-profile-loading)>.profile-layout {
  animation: profile-content-in 220ms ease-out both;
}

@keyframes profile-content-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* ── Identity band skeleton ─────────────────────────────────────────── */
.skeleton-identity {
  border-radius: 24px;
  border: 1px solid var(--color-border-light);
  background-color: var(--color-surface);
  box-shadow: var(--shadow-xl);
  margin-bottom: 24px;
  overflow: hidden;
  position: relative;
}

/* Matches .identity-band::before — the 6px accent rule at the top edge.
   Kept solid (not shimmering) because it's a real design element that also
   exists in the loaded state; shimmering it would make the swap visible. */
.skeleton-identity::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  height: 6px;
  background: var(--gradient-accent);
  z-index: 2;
}

.skeleton-cover {
  height: clamp(135px, 19vw, 250px);
  border-radius: 0;
}

.skeleton-identity-body {
  padding: 0 24px 24px;
}

.skeleton-identity-header {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 16px 24px;
  margin-block-start: -64px;
  padding-block-end: 12px;
}

.skeleton-avatar {
  width: 128px;
  height: 128px;
  min-width: 128px;
  border-radius: 50%;
  border: 4px solid var(--color-surface);
  flex-shrink: 0;
}

.skeleton-identity-text {
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1 1 240px;
  min-width: 0;
  padding-block-end: 4px;
}

.skeleton-line {
  height: 12px;
  border-radius: 6px;
}

.skeleton-line.is-title {
  height: 22px;
  width: 42%;
  min-width: 140px;
}

.skeleton-line.is-meta {
  width: 28%;
  min-width: 100px;
}

.skeleton-line.is-bio {
  width: 62%;
}

.skeleton-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 14px;
  margin-block-start: 20px;
}

.skeleton-stat-card {
  height: 74px;
  border-radius: 14px;
}

/* ── Main grid skeleton ─────────────────────────────────────────────── */
.skeleton-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: 24px;
  align-items: start;
}

/* Visitor view (/@handle) hides the rail entirely — see .profile-layout.no-rail.
   render-profile.js injects the admin:handle meta tag, and profile.html's inline
   critical CSS adds .no-rail to the skeleton when that tag is present, so the
   skeleton matches the one-column visitor layout instead of reserving 340px
   that's about to disappear. */
.skeleton-layout.no-rail {
  grid-template-columns: minmax(0, 1fr);
}

.skeleton-layout.no-rail .skeleton-rail {
  display: none;
}

/* Attribute-driven twin of the rule above — set by profile.html's inline
   bootstrap script, which runs before .skeleton-layout exists and so can't
   put the class on the element directly. */
.container[data-visitor-view] .skeleton-layout {
  grid-template-columns: minmax(0, 1fr);
}

.container[data-visitor-view] .skeleton-rail {
  display: none;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skeleton-main {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 28px;
}

.skeleton-rail {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.skeleton-card {
  border-radius: 15px;
  border: 1px solid var(--color-border-light);
  background-color: var(--color-surface);
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.skeleton-card-head {
  display: flex;
  align-items: center;
  gap: 10px;
}

.skeleton-card-head .skeleton-block:first-child {
  width: 22px;
  height: 22px;
  border-radius: 6px;
  flex-shrink: 0;
}

.skeleton-card-head .skeleton-block:last-child {
  height: 14px;
  width: 38%;
  min-width: 110px;
  border-radius: 7px;
}

.skeleton-body-tall {
  height: 168px;
  border-radius: 10px;
}

.skeleton-body-short {
  height: 96px;
  border-radius: 10px;
}

/* Dense list rows — mirrors .history-item's ~56px row target so the
   history/bookmarks/flagged cards don't jump height on hydrate. */
.skeleton-rows {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.skeleton-row {
  height: 56px;
  border-radius: 12px;
}

/* ── Responsive — mirrors the real layout's own breakpoints ─────────── */
@media (max-width: 1024px) {
  .skeleton-layout {
    grid-template-columns: minmax(0, 1fr);
  }

  .skeleton-rail {
    display: none;
  }
}

@media (max-width: 768px) {
  .skeleton-identity-body {
    padding: 0 16px 16px;
  }

  .skeleton-avatar {
    width: 96px;
    height: 96px;
    min-width: 96px;
  }

  .skeleton-identity-header {
    margin-block-start: -48px;
  }
}

/* ============================================================
   REDUCED MOTION 
   ============================================================ */
html[data-motion="reduced"] {
  /* .skeleton-block's shimmer is intentionally left running under reduced
     motion — the loading skeleton keeps animating even in "الأداء الفائق"
     (High Performance) mode. */

  .container:not(.is-profile-loading)>.identity-band,
  .container:not(.is-profile-loading)>.profile-layout {
    animation: none;
  }

  /* .section-icon (heading + stat-card icons) is intentionally excluded
     from this kill switch — see the block above for the full rationale. */
  .empty-state:hover .empty-state-icon .icon-line,
  .empty-state:focus-within .empty-state-icon .icon-line,
  .empty-state.is-drawing .empty-state-icon .icon-line {
    animation: none;
  }
}


/* ============================================================
   UPLOADED CONTENT LIST (quizzes + lessons) + VISITOR DATA SHIMMER
   ============================================================ */

/* Type badge before each upload's title. */
.upload-type-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-inline-end: 8px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.68rem;
  font-weight: 700;
  vertical-align: middle;
  background: color-mix(in srgb, var(--color-primary) 12%, transparent);
  color: var(--color-primary);
}

.upload-type-badge.is-lesson {
  background: color-mix(in srgb, #7c3aed 12%, transparent);
  color: #7c3aed;
}

/* Shared shimmer, same sweep as the page skeleton (skeleton-sweep). Own
   variables because .profile-skeleton (which defines them) is removed from
   the DOM once the hero is ready. */
.upload-skeleton-bar,
.container.is-data-loading .stat-card .value,
.container.is-data-loading #activityHeatmap::before {
  --skeleton-base: var(--color-background-secondary, #e9edf2);
  --skeleton-sheen: var(--color-surface, #f6f8fb);
  background-color: var(--skeleton-base);
  background-image: linear-gradient(90deg,
      transparent 0%,
      color-mix(in srgb, var(--skeleton-sheen) 70%, transparent) 50%,
      transparent 100%);
  background-size: 200% 100%;
  background-repeat: no-repeat;
  animation: skeleton-sweep 1.4s ease-in-out infinite;
  border-radius: 8px;
}

.upload-skeleton-row {
  pointer-events: none;
}

.upload-skeleton-bar {
  display: block;
}

.upload-skeleton-bar.is-title {
  width: min(260px, 70%);
  height: 16px;
  margin-block-end: 8px;
}

.upload-skeleton-bar.is-meta {
  width: min(160px, 45%);
  height: 11px;
}

.upload-skeleton-bar.is-btn {
  width: 110px;
  height: 34px;
  flex-shrink: 0;
}

/* Visitor view (/@handle): the identity band is server-injected and paints
   at once, while the numbers and the heatmap arrive from the API. Until
   profile.js drops .is-data-loading, show shimmer where they will appear
   instead of "0" values and an empty card. Layout is unchanged (same boxes),
   so nothing shifts when real data lands. */
.container.is-data-loading .stat-card .value {
  color: transparent;
  user-select: none;
  min-width: 3.2ch;
  display: inline-block;
}

.container.is-data-loading #activityHeatmap {
  position: relative;
  min-height: 150px;
}

.container.is-data-loading #activityHeatmap::before {
  content: "";
  position: absolute;
  inset: 0;
}

@media (prefers-reduced-motion: reduce) {

  .upload-skeleton-bar,
  .container.is-data-loading .stat-card .value,
  .container.is-data-loading #activityHeatmap::before {
    animation: none;
  }
}
