/* player.css — layers the profile page on top of shop.css, which already
   supplies the animated background, the glass card look and the toast. */

/* Theme
   -----
   Every accent on this page reads from these custom properties, so switching
   palette is one write to :root rather than a stylesheet swap. player.js sets
   them from the viewed player's public.theme row; the values below are the
   fallbacks a browser paints before that lands (and if the request fails), so
   they must stay in step with theme_id 1 in sql/theme.sql.

   Alongside each hex is an `-rgb` triple, because a hex can't be fed to rgba()
   — the translucent borders and glows below need `rgba(var(--…-rgb), a)`.

   Nothing that resolves through these properties is transitioned. Rewriting a
   custom property doesn't tick a running transition on a property built from
   it, so a transitioned border-color would sit on the old colour and the new
   palette's outlines would never appear. The switch is instant by necessity;
   only transform/background transitions remain.

   Only this page is themed: shop.css is shared, so anything of its own that has
   to follow the accent (body's gradient, .section-title, .card, .toast) is
   re-declared here rather than changed at the source. */
:root {
  --theme-accent: #74ebd5;
  --theme-accent-rgb: 116, 235, 213;
  --theme-accent-2: #9face6;
  --theme-on-accent: #06232b;
  --theme-bg-1: #0f2027;
  --theme-bg-2: #203a43;
  --theme-bg-3: #2c5364;
  --theme-bg-4: #1e3c72;
  /* The .page-backdrop dim, taken from the darkest gradient stop. */
  --theme-dim-rgb: 15, 32, 39;
}

/* Overrides shop.css's fixed gradient with the palette's four stops. The
   animation itself (gradientShift) still comes from shop.css. */
body {
  background: linear-gradient(-45deg, var(--theme-bg-1), var(--theme-bg-2), var(--theme-bg-3), var(--theme-bg-4));
  background-size: 400% 400%;
}

/* Whole-page profile background
   ------------------------------
   The equipped profile_bg of the player being viewed, painted across the page.
   It sits *over* body's animated gradient from shop.css rather than replacing
   it: the layer is held under 1 opacity and blurred, so the gradient keeps
   shifting through the artwork and the glass cards keep their contrast.

   Positioned (z-index 0) instead of z-index -1 — body is not a stacking
   context, so a negative index would drop this behind body's own gradient. The
   .shop wrapper below is lifted to 1 to stay on top. */
.page-backdrop {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  /* Blur softens the artwork into the gradient; the scale hides the
     transparent edges blur leaves behind. */
  transform: scale(1.06);
  opacity: 0;
  transition: opacity 0.7s ease;
}

/* Added by player.js only when the viewed player has one equipped. */
.page-backdrop.active { opacity: 0.5; }

/* The dim. Darkest at the edges so the centre column stays readable. */
.page-backdrop::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 30%,
    rgba(var(--theme-dim-rgb), 0.3), rgba(var(--theme-dim-rgb), 0.85));
}

/* An animated background renders a complete scene of its own — it doesn't
   need to read as "artwork tinted by the shifting theme gradient" the way a
   photo does, and the 0.5 blend plus the dim above exist specifically for
   that photo look. Both would wash out an animated scene's own colors at
   their photo strength, so this raises the opacity and cuts the dim down to
   a much fainter version rather than dropping either to nothing — a scene
   with zero theme influence read as floating disconnected from the rest of
   the page. AnimatedBg.mount() (public/animatedBg.js) adds .animated-bg-host
   to whatever it mounts into, so this only ever overrides the photo styling
   above for a live scene — a real image never carries that class.
   .active.animated-bg-host, not just .animated-bg-host, because the two
   opacity rules are otherwise equal specificity and would tie on source
   order — the compound selector wins outright regardless of where either
   rule sits in the file. */
.page-backdrop.active.animated-bg-host { opacity: 0.92; }
.page-backdrop.animated-bg-host::after {
  background: radial-gradient(circle at 50% 30%,
    rgba(var(--theme-dim-rgb), 0.05), rgba(var(--theme-dim-rgb), 0.18));
}

/* Keeps the page content above .page-backdrop. */
.shop {
  position: relative;
  z-index: 1;
}

/* Section rules follow the accent (shop.css hardcodes the teal). */
.section-title { border-left-color: var(--theme-accent); }

/* Section header with controls on the right — the Stats section's age stamp and
   Refresh button. .section-title carries the bottom margin on its own, so it is
   dropped here and the row owns the spacing instead. */
.section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 20px;
}

.section-head .section-title { margin-bottom: 0; }

.stats-meta {
  display: inline-flex;
  align-items: center;
  gap: 12px;
}

.stats-stamp {
  font-size: 0.8rem;
  color: rgba(234, 246, 255, 0.55);
}

.refresh-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid rgba(var(--theme-accent-rgb), 0.45);
  background: rgba(255, 255, 255, 0.08);
  color: #eaf6ff;
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  /* No border-color here on purpose: rewriting --theme-accent-rgb doesn't tick a
     running transition, so a transitioned border sticks on the old colour. */
  transition: background 0.2s ease, transform 0.15s ease;
}

.refresh-btn:hover { background: rgba(255, 255, 255, 0.16); }
.refresh-btn:active { transform: scale(0.97); }

.refresh-btn:disabled {
  opacity: 0.55;
  cursor: default;
  transform: none;
}

.refresh-icon {
  font-size: 1rem;
  line-height: 1;
  color: var(--theme-accent);
}

/* Spins only while a refresh is in flight. */
.refresh-btn.busy .refresh-icon {
  display: inline-block;
  animation: refresh-spin 0.8s linear infinite;
}

@keyframes refresh-spin {
  to { transform: rotate(360deg); }
}

.stat-grid.busy { opacity: 0.6; pointer-events: none; }

/* Nav */
.profile-nav {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 26px;
  flex-wrap: wrap;
}

.nav-link {
  display: inline-flex;
  align-items: center;
  background: rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(var(--theme-accent-rgb), 0.28);
  border-radius: 999px;
  padding: 9px 18px;
  color: #eaf6ff;
  text-decoration: none;
  font-size: 0.92rem;
  font-weight: 600;
  transition: background 0.2s ease, transform 0.15s ease;
}

/* .hidden lives in shop.css and is a single class, exactly like .nav-link — and
   player.css loads second, so `.nav-link { display: inline-flex }` wins and a
   nav link with the hidden class stays on screen. Observed, not theorised: it
   showed the admin link to every member. Any hideable nav item needs this. */
.nav-link.hidden { display: none; }

.nav-link:hover {
  background: rgba(255, 255, 255, 0.18);
  border-color: rgba(var(--theme-accent-rgb), 0.5);
}

.nav-link:active { transform: scale(0.97); }

/* Your own view count. It carries margin-left: auto, so when it is on screen
   it claims the free space and pins itself and the account circle after it to
   the tail of the row together; the circle's own auto margin (below) then has
   nothing left to claim. Hidden it is out of flow, so the circle pins the row
   on its own instead.

   Reuses .nav-link for the pill shape only — it is neither a link nor a button,
   so the hover state is opted out of. Both properties have to be restated
   because .nav-link:hover outranks a single class. */
.nav-views {
  margin-left: auto;
  gap: 7px;
  cursor: default;
  font-variant-numeric: tabular-nums;
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(var(--theme-accent-rgb), 0.2);
  color: #cfe0ec;
}

.nav-views:hover {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(var(--theme-accent-rgb), 0.2);
}

/* The account circle is what pins the tail of this row to the right edge now
   that Home/Admin/Sign out all moved into the nav drawer — .lookup caps at
   380px, so without this the circle sits mid-row with a gap after it. */
.profile-nav .auth-slot { margin-left: auto; }

/* Two auto margins in one flex row *share* the free space rather than one of
   them taking it, which would leave the pill floating short of the circle
   instead of beside it (observed with Sign out before it moved into the
   drawer, same mechanism). So the circle gives its own margin up while the
   pill is on screen and the pill alone pins the tail; the moment the pill is
   hidden this selector stops matching and the circle has it back. Three
   classes, so it outranks the plain `.profile-nav .auth-slot` rule above
   regardless of source order. */
.nav-views:not(.hidden) + .auth-slot { margin-left: 0; }

/* Username lookup */
.lookup {
  display: flex;
  gap: 8px;
  flex: 1;
  min-width: 220px;
  max-width: 380px;
}

/* Anchors the suggestion list to the input alone. The z-index is load-bearing:
   .hero's fadeInUp ends on `transform: translateY(0)` under `fill-mode: both`,
   so the hero forms a stacking context for the rest of the page's life — and
   sitting later in tree order at the same auto level, it paints straight over a
   dropdown that hasn't lifted itself out of the way. */
.lookup-field {
  position: relative;
  flex: 1;
  min-width: 0;
  z-index: 5;
}

.lookup input {
  width: 100%;
  padding: 9px 16px;
  border-radius: 999px;
  border: 1px solid rgba(var(--theme-accent-rgb), 0.28);
  background: rgba(255, 255, 255, 0.08);
  color: #eaf6ff;
  font-family: inherit;
  font-size: 0.92rem;
}

.lookup input::placeholder { color: rgba(234, 246, 255, 0.5); }

.lookup input:focus {
  outline: none;
  border-color: rgba(var(--theme-accent-rgb), 0.7);
  background: rgba(255, 255, 255, 0.12);
}

.lookup button {
  padding: 9px 18px;
  border: none;
  border-radius: 999px;
  font-family: inherit;
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--theme-on-accent);
  cursor: pointer;
  background: linear-gradient(90deg, var(--theme-accent), var(--theme-accent-2));
  background-size: 200% auto;
  transition: background-position 0.4s ease, transform 0.15s ease;
}

.lookup button:hover { background-position: right center; }
.lookup button:active { transform: scale(0.97); }

/* Lookup suggestions
   ------------------
   Deliberately declares no `display`, so shop.css's single-class `.hidden` has
   no specificity tie to lose — the bug documented at .nav-link.hidden above.
   The dim comes from the palette's darkest gradient stop, the same source
   .page-backdrop uses, because this panel has to stay opaque enough to read
   over whatever artwork the viewed player has equipped. */
.lookup-suggest {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  margin: 0;
  padding: 6px;
  list-style: none;
  /* Eight rows fit; the cap in player.js means it never needs more, but a long
     nickname can wrap the count off the bottom of a narrow screen. */
  max-height: 320px;
  overflow-y: auto;
  background: rgba(var(--theme-dim-rgb), 0.94);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(var(--theme-accent-rgb), 0.32);
  border-radius: 14px;
  box-shadow: 0 16px 34px rgba(0, 0, 0, 0.45);
}

/* Only `background` is transitioned. Everything else here resolves through a
   --theme-* property, and rewriting a custom property doesn't tick a running
   transition on a property built from it — see the note at the top of the
   file. */
.lookup-option {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 8px 12px;
  border-radius: 10px;
  cursor: pointer;
  transition: background 0.15s ease;
}

/* One highlight for both the arrow keys and the mouse, because Enter acts on
   whichever row is wearing it. */
.lookup-option.active { background: rgba(var(--theme-accent-rgb), 0.2); }

.lookup-option-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.9rem;
  font-weight: 600;
  color: #eaf6ff;
}

/* Pushed to the right edge and never shrunk: the username is what ?id=
   resolves by, so it is the half worth reading in full when the nickname has
   to be clipped. */
.lookup-option-user {
  margin-left: auto;
  flex: none;
  font-size: 0.76rem;
  color: rgba(234, 246, 255, 0.55);
}

/* Hero */
.hero {
  background: rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(var(--theme-accent-rgb), 0.3);
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.4);
  margin-bottom: 38px;
  animation: fadeInUp 0.6s ease both;
}

/* players.banner_bg renders here */
.hero-banner {
  position: relative;
  height: 170px;
  background-color: rgba(var(--theme-accent-rgb), 0.14);
  background-size: cover;
  background-position: center;
}

/* Same reasoning as .page-backdrop.animated-bg-host above — the theme-accent
   tint at its full photo-backdrop strength (0.14, or hero-avatar's solid
   --theme-bg-2) washed out an animated scene, but dropping it to fully
   transparent read as the scene floating with no relation to the page's own
   palette. A faint version of the same accent keeps that tie without
   competing with the scene's own colors. */
.hero-banner.animated-bg-host,
.hero-avatar.animated-bg-host {
  background-color: rgba(var(--theme-accent-rgb), 0.05);
}

/* The mobile counterpart of #viewCount in the nav — see the media query
   below for the swap. Hidden unconditionally here rather than only via the
   .hidden class player.js toggles, so it can never appear on a full-width
   screen even for isSelf, where .hidden wouldn't otherwise be present to
   stop it. Its own background rather than reusing .nav-views, since it sits
   on top of arbitrary banner artwork rather than the page's own gradient and
   needs to hold contrast against either. */
.hero-view-count {
  display: none;
  position: absolute;
  top: 14px;
  right: 14px;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(var(--theme-dim-rgb), 0.65);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.25);
  color: #eaf6ff;
  font-size: 0.8rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  z-index: 2;
}

/* Bottom-right of the banner, mirroring .hero-view-count's top-right pill so
   the two share one visual language on top of arbitrary banner artwork.
   Bottom-right rather than bottom-left: .hero-avatar sits at the banner's
   bottom-left, overlapping its own bottom 46px (.hero-body's negative
   margin-top, see above), so anything placed there needs to dodge it. The
   right side has no such overlap, so this can sit flush at 14px like the
   view-count pill does at the top. */
.hero-share-btn {
  position: absolute;
  bottom: 14px;
  right: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: 50%;
  background: rgba(var(--theme-dim-rgb), 0.65);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.25);
  color: #eaf6ff;
  font-size: 1rem;
  cursor: pointer;
  z-index: 2;
  transition: transform 0.15s ease, background 0.15s ease;
}

.hero-share-btn:hover {
  background: rgba(var(--theme-accent-rgb), 0.55);
  transform: scale(1.08);
}

.hero-body {
  /* Positioned, not just static, because .hero-banner now is too (for
     .hero-view-count's absolute positioning) — a positioned box always
     paints above a non-positioned sibling regardless of source order, so
     without this the avatar and name were painting *under* the banner
     instead of overlapping its bottom edge as the negative margin below
     intends. Both being positioned with the same (auto) z-index restores
     plain DOM-order stacking, which is what put the avatar back on top. */
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: 20px;
  padding: 0 26px 22px;
  margin-top: -46px;
}

/* players.profile_bg renders here, behind players.avatar if there is one.
   position: relative — unlike every other .animated-bg-host (shop.css), this
   is the one that didn't already have a position context of its own, and a
   mounted canvas positions itself absolutely against its container. */
.hero-avatar {
  position: relative;
  width: 104px;
  height: 104px;
  flex: none;
  border-radius: 50%;
  border: 3px solid rgba(var(--theme-accent-rgb), 0.75);
  background-color: var(--theme-bg-2);
  background-size: cover;
  background-position: center;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.45);
}

.hero-text { padding-bottom: 6px; min-width: 0; }

.hero-text h1 {
  font-size: 1.9rem;
  line-height: 1.2;
  word-break: break-word;
}

.hero-handle {
  color: rgba(234, 246, 255, 0.65);
  font-size: 0.95rem;
  margin-top: 2px;
}

.hero-tags {
  display: flex;
  gap: 8px;
  margin-top: 10px;
  flex-wrap: wrap;
}

.tag {
  font-size: 0.75rem;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  font-weight: 700;
  padding: 4px 11px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(var(--theme-accent-rgb), 0.28);
}

.tag.you {
  color: var(--theme-accent);
  border-color: rgba(var(--theme-accent-rgb), 0.5);
}

.profile-error {
  background: rgba(255, 110, 110, 0.14);
  border: 1px solid rgba(255, 110, 110, 0.45);
  color: #ffd9d9;
  border-radius: 12px;
  padding: 13px 18px;
  margin-bottom: 30px;
  text-align: center;
}

/* Stats */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
  gap: 16px;
}

.stat {
  background: rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(var(--theme-accent-rgb), 0.26);
  border-radius: 14px;
  padding: 18px;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.3);
  transition: transform 0.2s ease;
}

.stat:hover { transform: translateY(-4px); }

.stat-label {
  font-size: 0.76rem;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: rgba(234, 246, 255, 0.6);
}

.stat-value {
  font-size: 1.75rem;
  font-weight: 700;
  margin-top: 8px;
}

/* Medal tiles keep their own colours — those are the medal, not the theme. */
.stat.gold .stat-value { color: #ffd76a; }
.stat.silver .stat-value { color: #d8e3ef; }
.stat.bronze .stat-value { color: #e0a479; }
.stat.accent .stat-value { color: var(--theme-accent); }

/* Theme picker — own profile only */
.theme-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: 14px;
}

.theme-swatch {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  color: #eaf6ff;
  font-family: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}

.theme-swatch:hover { transform: translateY(-3px); }

/* Each card previews its *own* palette, so these two read the per-swatch
   variables player.js writes inline — not the page-level ones. */
.theme-chip {
  height: 42px;
  border-radius: 10px;
  background: linear-gradient(135deg, var(--swatch-accent), var(--swatch-accent-2));
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.25);
}

.theme-name {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
}

/* The applied palette. Outlined in its own accent so it stands out even while
   the rest of the page is mid-transition. */
.theme-swatch.selected {
  border-color: var(--swatch-accent);
  box-shadow: 0 0 0 1px var(--swatch-accent), 0 14px 30px rgba(0, 0, 0, 0.4);
}

.theme-swatch.selected .theme-name::after {
  content: '✓';
  color: var(--swatch-accent);
}

.theme-grid.busy { opacity: 0.6; pointer-events: none; }

/* Inventory */
.inventory-hint {
  color: rgba(234, 246, 255, 0.6);
  font-size: 0.9rem;
  margin: -8px 0 22px;
}

/* The permanent "go buy more" nudge under Inventory's own hint line — same
   typography as .inventory-hint, but without its -8px pull toward
   .section-title, which is calibrated for sitting directly under the
   heading and would double up if reused on a second paragraph. Ends on the
   same 22px .sub-title relies on (.sub-title:first-of-type zeroes its own
   top margin), so "Banners" is spaced exactly as it was before this line
   existed. */
.shop-link-hint {
  color: rgba(234, 246, 255, 0.6);
  font-size: 0.9rem;
  margin: 0 0 22px;
}

.sub-title {
  font-size: 1.05rem;
  font-weight: 600;
  margin: 26px 0 14px;
  color: rgba(234, 246, 255, 0.85);
}

.sub-title:first-of-type { margin-top: 0; }

/* Themed outline for the shared shop.css card. */
.card { border-color: rgba(var(--theme-accent-rgb), 0.26); }

/* Owned item cards are clickable in full, not just via the button. */
.card.inventory-card { cursor: pointer; }

.card.inventory-card.in-use {
  border-color: rgba(var(--theme-accent-rgb), 0.65);
  box-shadow: 0 0 0 1px rgba(var(--theme-accent-rgb), 0.35), 0 16px 36px rgba(0, 0, 0, 0.45);
}

.card.inventory-card.busy { opacity: 0.6; pointer-events: none; }

.use-btn {
  margin-top: auto;
  padding: 11px 16px;
  border: none;
  border-radius: 10px;
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--theme-on-accent);
  cursor: pointer;
  background: linear-gradient(90deg, var(--theme-accent), var(--theme-accent-2));
  background-size: 200% auto;
  transition: background-position 0.4s ease, transform 0.15s ease;
}

.use-btn:hover { background-position: right center; }
.use-btn:active { transform: scale(0.97); }

/* Equipped items get the inverted treatment — the action is now "remove". */
.use-btn.unuse {
  background: rgba(255, 255, 255, 0.14);
  color: #eaf6ff;
  border: 1px solid rgba(var(--theme-accent-rgb), 0.45);
}

.use-btn.unuse:hover { background: rgba(255, 255, 255, 0.22); }

.nav-inline {
  color: var(--theme-accent);
  text-decoration: underline;
}

/* Gameplay clips and Captures — same grid rhythm as .banner-grid /
   .background-grid in shop.css, sized a little wider since a video or
   screenshot reads worse cramped than an inventory thumbnail does. Shared
   between both sections (see createMediaSection in player.js) since a video
   tile and an image tile differ only in the tag inside .media-preview. */
.media-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 22px;
}

.media-card { position: relative; }

/* Wraps .media-preview so the whole tile is one click target for
   openMediaLightbox rather than requiring a precise hit on the <video>/<img>
   itself — a plain <button>, not a <label> like .media-add, since there's no
   file input here to trigger. */
.media-preview-btn {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}

.media-preview {
  width: 100%;
  display: block;
  aspect-ratio: 16 / 9;
  background: rgba(0, 0, 0, 0.35);
  object-fit: cover;
}

/* Videos ship muted with no native controls in the tile (see buildCard in
   player.js) — this glyph is what says "this opens bigger, and plays there",
   the same job home.css's .post-play plays for the Recent Posts teaser. */
.media-play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 1.7rem;
  color: #fff;
  background: rgba(4, 16, 22, 0.28);
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.6);
  transition: background 0.2s ease;
}

.media-preview-btn:hover .media-play { background: rgba(4, 16, 22, 0.42); }

/* Holds Share (always) and Delete (owner only) — a row rather than either
   button being placed on its own, so a card with both doesn't need two
   separately-margined siblings agreeing on spacing. */
.media-actions {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 12px 16px 16px;
}

.media-share {
  padding: 8px 14px;
  border: 1px solid rgba(var(--theme-accent-rgb), 0.3);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.08);
  color: #eaf6ff;
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s ease;
}

.media-share:hover { background: rgba(255, 255, 255, 0.16); }

.media-delete {
  padding: 8px 14px;
  border: 1px solid rgba(255, 110, 110, 0.45);
  border-radius: 10px;
  background: rgba(255, 110, 110, 0.14);
  color: #ffd9d9;
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s ease;
}

.media-delete:hover { background: rgba(255, 110, 110, 0.24); }

.media-card.busy { opacity: 0.6; pointer-events: none; }

/* The upload tile — a label wrapping a hidden file input, so the whole card
   is the click target rather than just a nested button. */
.media-add {
  aspect-ratio: 16 / 9;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 20px;
  color: rgba(234, 246, 255, 0.75);
  font-weight: 600;
  cursor: pointer;
  border-style: dashed;
  transition: background 0.2s ease, border-color 0.2s ease;
}

.media-add:hover {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(var(--theme-accent-rgb), 0.5);
}

.media-add.disabled {
  cursor: default;
  opacity: 0.55;
}

.media-add.disabled:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.18);
}

/* The Add tile while an upload is in flight — see upload() in player.js,
   which is what sets/clears this class; the rule here only draws it.
   Stacked rather than the tile's normal single row, so the spinner sits
   above the label instead of squeezed beside it. */
.media-add.uploading {
  flex-direction: column;
  gap: 10px;
  cursor: default;
}

.media-add.uploading:hover {
  background: none;
  border-color: rgba(255, 255, 255, 0.18);
}

.media-upload-spinner {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 2px solid rgba(234, 246, 255, 0.25);
  border-top-color: var(--theme-accent, #74ebd5);
  animation: refresh-spin 0.8s linear infinite;
}

/* The optional caption, between the media and the Delete button — absent
   entirely (see buildCard in player.js) rather than rendered empty when a
   file has none, so there's no blank strip to leave behind. */
.media-title {
  margin: 10px 16px 0;
  color: #eaf6ff;
  font-size: 0.9rem;
  line-height: 1.35;
  overflow-wrap: break-word;
}

/* .media-actions already carries its own top margin (12px) and the title sits
   right above it — when both are present the caption's bottom spacing is the
   actions row's own margin, not a second gap stacked on top of it. A
   title-less card has no .media-title, so that margin does the same job
   alone. */
.media-card:has(.media-title) .media-actions { margin-top: 4px; }

/* Reveals more Gameplay/Captures tiles without a second fetch — same idea as
   home.css's `.roster-toggle`, restyled to follow this page's theme
   variables instead of the roster's fixed accent. A sibling of .media-grid,
   not a grid item inside it, matching how .roster-more sits outside
   #playerGrid rather than spanning it. */
.media-more {
  display: flex;
  justify-content: center;
  margin-top: 22px;
}

.media-more-btn {
  padding: 11px 26px;
  border-radius: 999px;
  font: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  color: #eaf6ff;
  cursor: pointer;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(var(--theme-accent-rgb), 0.35);
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}

.media-more-btn:hover {
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(var(--theme-accent-rgb), 0.6);
}

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

/* The title prompt
   -----------------
   One overlay shared by both media sections (see the JS section of the same
   name in player.js). `.modal-overlay.hidden` is a two-class selector for the
   same reason `.nav-link.hidden` is above: `.modal-overlay` sets its own
   `display: flex` to centre the card, and a bare single-class `.hidden` from
   shop.css would tie with it on specificity and could lose depending on
   stylesheet order — the compound selector always wins regardless. */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(var(--theme-dim-rgb), 0.75);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.modal-overlay.hidden { display: none; }

.modal-card {
  width: 100%;
  max-width: 420px;
  background: rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(var(--theme-accent-rgb), 0.3);
  border-radius: 18px;
  padding: 24px;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.55);
}

.modal-heading { font-size: 1.2rem; margin-bottom: 6px; }

.modal-hint {
  color: rgba(234, 246, 255, 0.6);
  font-size: 0.85rem;
  margin-bottom: 16px;
}

.modal-textarea {
  width: 100%;
  resize: vertical;
  min-height: 60px;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid rgba(var(--theme-accent-rgb), 0.28);
  background: rgba(255, 255, 255, 0.08);
  color: #eaf6ff;
  font-family: inherit;
  font-size: 0.92rem;
}

.modal-textarea::placeholder { color: rgba(234, 246, 255, 0.5); }

.modal-textarea:focus {
  outline: none;
  border-color: rgba(var(--theme-accent-rgb), 0.7);
  background: rgba(255, 255, 255, 0.12);
}

.modal-count {
  margin-top: 8px;
  text-align: right;
  font-size: 0.78rem;
  color: rgba(234, 246, 255, 0.55);
}

.modal-count.over-limit { color: #ff9d9d; }

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 18px;
}

.modal-btn {
  padding: 10px 18px;
  border: none;
  border-radius: 10px;
  font-family: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s ease, transform 0.15s ease, opacity 0.2s ease;
}

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

.modal-btn-secondary {
  background: rgba(255, 255, 255, 0.1);
  color: #eaf6ff;
  border: 1px solid rgba(var(--theme-accent-rgb), 0.3);
}

.modal-btn-secondary:hover { background: rgba(255, 255, 255, 0.18); }

.modal-btn-primary {
  color: var(--theme-on-accent);
  background: linear-gradient(90deg, var(--theme-accent), var(--theme-accent-2));
  background-size: 200% auto;
}

.modal-btn-primary:hover { background-position: right center; }

.modal-btn-primary:disabled {
  opacity: 0.5;
  cursor: default;
  transform: none;
}

/* The media lightbox — a Gameplay/Capture tile's clip or capture at full
   size. Reuses .modal-overlay/.modal-card for the centring/backdrop/base
   card look; this is only what's different from the title prompt above:
   wider, no padding of its own (the media fills the card edge-to-edge), and
   a round close button in the corner rather than a Cancel/Confirm pair. */
.media-lightbox-card {
  max-width: min(900px, 92vw);
  max-height: 90vh;
  padding: 0;
  overflow: hidden;
  display: grid;
}

.media-lightbox-close {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 1;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid rgba(var(--theme-accent-rgb), 0.28);
  background: rgba(4, 16, 22, 0.6);
  color: #eaf6ff;
  font-size: 1rem;
  cursor: pointer;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  transition: background 0.2s ease;
}

.media-lightbox-close:hover { background: rgba(4, 16, 22, 0.85); }

.media-lightbox-media {
  display: grid;
  place-items: center;
  max-height: 80vh;
  background: #000;
}

.media-lightbox-media video,
.media-lightbox-media img {
  display: block;
  width: 100%;
  max-height: 80vh;
  object-fit: contain;
}

.media-lightbox-title {
  margin: 0;
  padding: 14px 18px;
  color: #eaf6ff;
  font-size: 0.9rem;
  line-height: 1.4;
  border-top: 1px solid rgba(var(--theme-accent-rgb), 0.2);
}

.in-use-flag {
  font-size: 0.75rem;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--theme-accent);
}

/* Themed toast outline (shop.css hardcodes the teal). */
.toast { border-color: rgba(var(--theme-accent-rgb), 0.45); }

/* Nav drawer
   ----------
   Visible on every viewport, not just mobile — Home, Admin and Sign out live
   only in the drawer this opens now (see player.html), so there is no wide-
   screen fallback to hide the button behind any more.

   The three bars are a fixed-size button rather than an emoji or icon font,
   so it lines up with the pill-shaped .nav-link buttons beside it and themes
   the same way (border/background follow --theme-accent-rgb like every other
   nav control on this page). */
.nav-hamburger {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 4px;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 1px solid rgba(var(--theme-accent-rgb), 0.28);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.1);
  cursor: pointer;
  transition: background 0.2s ease;
}

.nav-hamburger:hover { background: rgba(255, 255, 255, 0.18); }

.nav-hamburger-bar {
  display: block;
  width: 16px;
  height: 2px;
  border-radius: 1px;
  background: #eaf6ff;
}

/* Same two-class pattern as .modal-overlay.hidden above, and for the same
   reason: .drawer-overlay sets its own display to cover the screen, and a
   bare single-class .hidden from shop.css would tie with it on specificity. */
.drawer-overlay {
  position: fixed;
  inset: 0;
  z-index: 70;
  background: rgba(var(--theme-dim-rgb), 0.6);
}

.drawer-overlay.hidden { display: none; }

/* Off-screen by default and slid in via .open — transform rather than
   left/right, so the panel is composited on its own layer instead of
   triggering layout on every frame of the slide. */
.nav-drawer {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: 71;
  width: min(78vw, 280px);
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 70px 18px 24px;
  background: rgba(var(--theme-dim-rgb), 0.96);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-right: 1px solid rgba(var(--theme-accent-rgb), 0.3);
  box-shadow: 0 0 40px rgba(0, 0, 0, 0.5);
  transform: translateX(-100%);
  transition: transform 0.25s ease;
}

.nav-drawer.open { transform: translateX(0); }

.drawer-close {
  position: absolute;
  top: 16px;
  right: 16px;
  width: 32px;
  height: 32px;
  border: 1px solid rgba(var(--theme-accent-rgb), 0.28);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.1);
  color: #eaf6ff;
  font-size: 0.95rem;
  cursor: pointer;
}

.drawer-close:hover { background: rgba(255, 255, 255, 0.18); }

.drawer-link {
  display: block;
  padding: 13px 16px;
  border-radius: 12px;
  color: #eaf6ff;
  text-decoration: none;
  font-size: 1rem;
  font-weight: 600;
  text-align: left;
  background: transparent;
  border: none;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.2s ease;
}

.drawer-link:hover { background: rgba(255, 255, 255, 0.1); }

/* Same rule .nav-link.hidden documents: a plain .hidden here would tie with
   .drawer-link on specificity and could lose depending on load order. */
.drawer-link.hidden { display: none; }

@media (max-width: 600px) {
  .hero-body {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }

  .hero-text h1 { font-size: 1.5rem; }
  /* The row wraps at this width, so nothing is being pinned to a right edge
     any more — the margin goes, or the pill is left stranded on its own line. */
  .nav-views { margin-left: 0; }
  .lookup { max-width: none; }

  /* .lookup keeps its 220px min-width here and the Go button takes 63px of it,
     so the input is down to ~163px (measured at 375px) — one line is not enough
     for a nickname and a handle both. Stacked, each gets the full width;
     side by side, clipping "[IND] AMEEN HI !! INDIA" to nothing to keep
     @ameen0591 company helps nobody. */
  .lookup-option {
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
  }

  .lookup-option-user { margin-left: 0; }

  /* The account circle moves to the front of the row (top left) rather than
     staying pinned to the tail — the row already wraps here, so "the tail"
     isn't a fixed right edge any more, and the circle reads better as the
     first thing on the page than as a stray item on its own wrapped line. */
  .profile-nav .auth-slot { order: -1; margin-left: 0; }

  /* The view pill moves off the topbar entirely on a narrow screen — the
     hero banner badge above takes over its job, so the row doesn't need to
     make room for it beside the search bar, and .auth-slot's own margin
     (just above) is all that's left to zero out. Unconditional, unlike the
     .hidden class this element also carries: isSelf still decides whether
     there's a number to show at all, just on the banner instead now. */
  .nav-views { display: none; }

  /* Shown here, not in the base rule, so it can never appear on a
     full-width screen even for isSelf — where .hidden wouldn't be present
     to stop it there. */
  .hero-view-count:not(.hidden) { display: flex; }
}
