/* =========================================================================
   friends.css — Class & Friends page (friends-page.js)
   =========================================================================
   Layout + the person-row / tab-strip primitives that hub.css's generic
   .etac-card / .etac-rows / .etac-row don't cover: search results, friend
   requests, and the friend list all render as compact rows with inline
   action buttons, so they get their own lighter row style (.fr-row) rather
   than stretching the leaderboard's .etac-row for a job it wasn't built for.
   ========================================================================= */

.fr-layout { display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-start; }
.fr-board { flex: 2 1 340px; min-width: 0; }
.fr-rail { flex: 1 1 280px; min-width: 0; display: flex; flex-direction: column; gap: 14px; }

/* So #join (linked from the daily card's class-cue nudge) lands below the
   sticky HUD instead of under it. */
#join { scroll-margin-top: 84px; }

/* Username nudge — namemodal.js unhides this while the signed-in user has
   no display_name, and hides it again the moment one is saved. */
.etac-namebanner {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 14px; padding: 11px 14px;
  background: var(--bg-input); border: 1px solid var(--border); border-radius: 12px;
}
.etac-namebanner__text { flex: 1; min-width: 200px; font: 600 12.5px/1.5 var(--fsans); color: var(--text-muted); }
.etac-namebanner__text b { color: var(--text); }

/* ---------- Leaderboard scope tabs ---------- */
.fr-tabs { display: flex; gap: 6px; margin: 2px 0 12px; }
.fr-tab {
  flex: 1; padding: 7px 10px; border-radius: 9px; border: 1px solid var(--border);
  background: var(--bg-input); color: var(--text-muted);
  font: 700 11.5px var(--fsans); cursor: pointer; transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.fr-tab:hover { border-color: var(--border-hover); }
.fr-tab.is-active { background: var(--accent); border-color: var(--accent); color: var(--bg); }
.fr-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- Rank tab: standings / this-week toggle ---------- */
.fr-lb-window { display: flex; gap: 6px; margin: -4px 0 12px; }
.fr-lb-window__btn {
  padding: 4px 10px; border-radius: 99px; border: 1px solid var(--border);
  background: var(--bg-input); color: var(--text-dim); font: 700 10.5px var(--fmono);
  cursor: pointer; transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.fr-lb-window__btn:hover { border-color: var(--border-hover); }
.fr-lb-window__btn.is-active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.fr-row__rankbadge {
  display: inline-flex; align-items: center; gap: 5px; font: 700 10.5px var(--fmono);
  color: var(--text);
}
.fr-row__rankbadge:empty::before,
.fr-row__rankbadge:not(:has(.fr-row__rankicon))::before {
  content: ''; width: 8px; height: 8px; border-radius: 50%; background: var(--tier, var(--border));
}
.fr-row__rankicon { width: 14px; height: 16px; object-fit: contain; flex: none; }
.fr-lb-note { margin: 12px 0 0; font: 500 12px/1.5 var(--fsans); color: var(--text-dim); }

/* Rows became focusable (tabindex="0") for the hover card below — keyboard
   users need the same affordance mouse users get from :hover. */
.etac-row[tabindex]:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ---------- Hover/tap/focus user card (leaderboard rows) ---------- */
/* Portaled to document.body via friends-page.js — .etac-rows is
   overflow:hidden so a card positioned inside a row would get clipped. */
/* 4:1, the banners' own aspect ratio (COSMETICS' `art` is 1200x300), so an
   equipped banner shows at its native proportions here instead of being
   centre-cropped. The ratio also makes every card the same shape whether or
   not the person wears a title, which the old content-driven height did not.
   376px is the narrowest width whose quarter still clears the tallest
   content — avatar beside four stacked lines, ~94px. min-height is the
   graceful-degradation floor: below ~390px of viewport the width clamps and
   the card goes slightly taller than 4:1 rather than clipping someone's
   name. */
.etac-usercard {
  position: fixed; z-index: 50; pointer-events: none;
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-radius: 12px; overflow: hidden;
  border: 1px solid var(--border-hover); background: var(--bg-elevated);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .32);
  width: min(376px, calc(100vw - 16px));
  aspect-ratio: 4 / 1;
  min-height: 94px;
}
.etac-usercard:not(.no-motion) { animation: etac-usercard-in .12s ease; }
@keyframes etac-usercard-in { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: none; } }
.etac-usercard[hidden] { display: none; }
.etac-usercard .fr-row__avatar { flex: none; }
.etac-usercard__body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.etac-usercard__name { font: 700 13px var(--fsans); color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.etac-usercard__title { line-height: 1; }
.etac-usercard__rank { display: inline-flex; align-items: center; gap: 5px; font: 700 10px var(--fmono); color: var(--text-muted); }
.etac-usercard__rank:not(:has(.fr-row__rankicon))::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: var(--tier, var(--border)); }
.etac-usercard__stats { font: 600 10.5px var(--fmono); color: var(--text-dim); }
/* Pushed to the right edge by the auto margin — the counterweight that makes
   the 4:1 card read as composed rather than as content plus empty space. */
.etac-usercard__badge {
  flex: none; margin-left: auto; width: 60px; height: 60px;
  object-fit: contain; filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .45));
}
@media (prefers-reduced-motion: reduce) {
  .etac-usercard { animation: none !important; }
}

/* An equipped banner (applied by applyBanner() in hud.js — layering contract
   documented in hub.css) turns the card into a mini hero: same
   light-on-dark-via-scrim language profile.css's .p-banner already uses, at
   card scale, so a banner reads the same wherever it shows up. This card is
   ~260px by whatever its rows come to — near enough the art's own 4:1 that
   `cover` barely crops. It's also the ONLY list surface that ever paints a
   banner (rows themselves never do, and only one card exists at a time,
   reused), so a page listing fifty people still decodes one SVG, not fifty. */
.etac-usercard.has-banner {
  background: linear-gradient(135deg, var(--c1), var(--c2, var(--c1)));
  border-color: transparent;
}
/* z-index 1, not 0: .etac-banner-art (hub.css) is a real first child sitting
   at 0, and ::before counts as a first child too — at equal z-index the art
   would paint over the scrim that exists to darken it. */
.etac-usercard.has-banner::before {
  content: ''; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(100deg, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .18) 60%);
}
.etac-usercard.has-banner .fr-row__avatar,
.etac-usercard.has-banner .etac-usercard__badge,
.etac-usercard.has-banner .etac-usercard__body { position: relative; z-index: 2; }
.etac-usercard.has-banner .etac-usercard__name,
.etac-usercard.has-banner .etac-usercard__rank,
.etac-usercard.has-banner .etac-usercard__stats {
  color: #fff; text-shadow: 0 1px 3px rgba(0, 0, 0, .5);
}
.etac-usercard.has-banner .etac-usercard__rank:not(:has(.fr-row__rankicon))::before {
  background: var(--tier, rgba(255, 255, 255, .6));
}

/* ---------- Search ---------- */
.fr-search { margin-bottom: 10px; }
.fr-search input { width: 100%; }

/* ---------- Leaderboard side cell: total XP over "+N this week" ---------- */
.etac-row__side { display: inline-flex; flex-direction: column; align-items: flex-end; line-height: 1.2; }
.fr-row__week { font: 600 9.5px var(--fmono); color: var(--text-dim); margin-top: 1px; }

/* ---------- Person rows (search results / requests / friends) ---------- */
.fr-list { display: flex; flex-direction: column; gap: 6px; }
.fr-list:empty { display: none; }
.fr-list + .fr-list { margin-top: 10px; }

/* The "browse every user" scroll pane (admin.html). `contain` stops a flick
   at the bottom from scrolling the card (and then the page) behind it —
   same reasoning as .etac-onboard__doc's scroll pane. */
.fr-list--scroll {
  max-height: min(60vh, 480px); overflow-y: auto; overscroll-behavior: contain;
  padding: 8px; border-radius: 12px; background: var(--bg-input); border: 1px solid var(--border);
}
.fr-list--scroll:empty::after {
  content: 'Loading…'; display: block; font: 600 11.5px var(--fmono); color: var(--text-dim); padding: 10px 2px;
}
.fr-list-label {
  font: 700 9.5px var(--fmono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-dim); margin: 2px 0 2px;
}
.fr-row {
  display: flex; align-items: center; gap: 9px; padding: 8px 9px;
  border: 1px solid var(--border); border-radius: 10px; background: var(--bg-input);
}
.fr-row__avatar {
  width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0;
  background: var(--bg-elevated); object-fit: cover;
  display: grid; place-items: center; font: 700 12px var(--fsans); color: var(--text-dim);
}
.fr-row__main { flex: 1; min-width: 0; }
.fr-row__name {
  display: block; font: 700 12.5px var(--fsans); color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fr-row__meta { display: block; font: 600 10.5px var(--fmono); color: var(--text-dim); margin-top: 1px; }
.fr-row__actions { display: flex; gap: 5px; flex-shrink: 0; }
.fr-row__btn {
  font: 700 11px var(--fsans); padding: 5px 10px; border-radius: 8px; border: 1px solid var(--border);
  background: var(--bg-elevated); color: var(--text); cursor: pointer; white-space: nowrap;
  transition: background .15s ease, border-color .15s ease;
}
.fr-row__btn:hover:not(:disabled) { border-color: var(--border-hover); }
.fr-row__btn:disabled { opacity: .55; cursor: default; }
.fr-row__btn.is-primary { background: var(--accent); border-color: var(--accent); color: var(--bg); }
.fr-row__btn.is-primary:hover:not(:disabled) { filter: brightness(1.07); }
.fr-row__btn.is-danger { color: var(--warning-fg); border-color: var(--warning-bd); }

/* ---------- Empty / loading states ---------- */
.fr-empty {
  border: 1px dashed var(--border-hover); border-radius: 12px; padding: 16px; text-align: center;
  color: var(--text-dim); font: 500 12px/1.5 var(--fsans);
  background: color-mix(in oklab, var(--bg-input) 60%, transparent);
}

/* ---------- Teacher zone hint text (friends-page.js) ---------- */
.fr-hint { margin: 0 0 10px; font: 400 13px/1.5 var(--fsans); color: var(--text-muted); }

