/* ================================================================
   SportsBiff — Biff leaderboard & directory table
   Rendered by dashboard/_leaderboard.html.erb + biffs/_leaderboard_row.html.erb
   on /dashboard?view=leaderboard and ?view=directory.

   ⚠ THIS FILE OWNS THE TABLE AT EVERY WIDTH — phone, tablet and desktop.
   Deliberately NOT split with mobile.css. The split is exactly what broke it: the
   track list was written inline on the element (so no media query could reach it),
   the phone override was hardcoded in mobile.css, and when the Biff Score column
   arrived only the TABLET override was updated. The phone kept declaring 5 tracks
   for 6 cells, which pushed Profit onto a second grid row, and in the directory —
   which has no Rank cell — every cell shifted one track left, so the names landed
   in a 32px overflow:hidden box and vanished. Keep both breakpoints here.

   Loaded by `stylesheet_link_tag :app`, which links every stylesheet in this
   directory ALPHABETICALLY. So this file lands after homepage.css and BEFORE
   tailwind.css — a Tailwind utility on these elements beats a single-class rule
   here. Where that matters the selector below is deliberately two classes deep
   rather than reaching for !important.

   Mobile-first: the phone card is the base, desktop is the min-width:768px block.
   ================================================================ */

/* ── The grid ───────────────────────────────────────────────────
   Variant classes come from BiffsHelper#leaderboard_grid_classes. Four shapes:
   rank/no-rank × score/no-score. `display: grid` is set inline on the row and is
   never declared !important anywhere — the directory's dirRender() hides rows with
   style.display = 'none', and an !important rule would beat it and break search,
   filtering and pagination. */
.lb-header,
.lb-row { display: grid; }

/* ── Phone (base) — one card per biff ───────────────────────────
   Line 1: rank + avatar + full name. Line 2: a labelled stat strip.

   Each Biff is its own surface with a GAP between, not a rule. There used to be two
   hairlines per Biff — one inside it (name → stats) and one between it and the next — the
   same colour and weight after Hallmark recoloured them, so nothing said which was the
   separator and the list read as one undifferentiated run. A gap cannot be misread. */
.lb-table {
  background: transparent;   /* the cards need to read against the page, not against this */
  border: none;
}

#leaderboard-rows {
  display: grid;
  gap: 8px;
  margin-top: 8px;
}

.lb-header,
.lb-row {
  grid-template-columns: minmax(0, 1fr);
  column-gap: 0;
}

.lb-row {
  position: relative; /* anchors .lb-row-link (stretched) and the mobile .lb-follow pill */
  padding: 13px 14px;
  border-radius: 10px;
  background: var(--bg-secondary);
  border: 1px solid var(--hh-rule, var(--bg-tertiary));
  transition: background 0.14s ease, border-color 0.14s ease;
}

/* The whole-row link, stretched under the content. The row stopped being an <a> when the
   Follow button arrived — interactive content inside an anchor is invalid HTML and
   double-fires navigation. */
.lb-row .lb-row-link {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
}

/* ── Follow pill ─────────────────────────────────────────────────────────────
   Mobile-first: absolute top-right of the stacked card (the base grid stays single-
   column). Desktop overrides below make it the last grid column. Above the stretched
   link via z-index. Gold "Following" = the dark-ink-on-gold precedent (.sporttile__soon). */
.lb-row .lb-follow {
  position: absolute;
  top: 11px;
  right: 12px;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid var(--hh-rule, var(--bg-tertiary));
  background: transparent;
  color: var(--text-secondary);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.3px;
  line-height: 1.4;
  white-space: nowrap;
  cursor: pointer;
  transition: border-color 0.14s ease, background 0.14s ease, color 0.14s ease;
}
.lb-row .lb-follow:hover { border-color: var(--accent-primary); color: var(--accent-primary); }
.lb-row .lb-follow:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: 2px; }
.lb-row .lb-follow:active { transform: translateY(1px); }
.lb-row .lb-follow:disabled { opacity: 0.6; cursor: default; }
.lb-row .lb-follow.is-following {
  background: var(--accent-gold);
  border-color: var(--accent-gold);
  color: #1a1206; /* dark ink on gold — the pill carries the contrast */
}
/* Inert: a status chip, not a control — no hover lift, default cursor. */
.lb-row .lb-follow.is-following { cursor: default; }
.lb-row .lb-follow.is-following:hover { filter: none; color: #1a1206; border-color: var(--accent-gold); }

/* "No settled tips in this window" — recessive, so absence never reads as a result. */
.lb-row .lb-val-none { color: var(--text-muted); font-weight: 600; }

/* ── Biff Score ring gauge ─────────────────────────────────────────────────── */
/* Anatomy is UNSCOPED so biffs/_score_ring renders anywhere (the featured card uses it too);
   only the SIZE stays scoped to .lb-row, because each host sizes it for its own layout. */
.lb-ring {
  position: relative;
  display: inline-block;
  width: 32px;
  height: 32px;
  flex: none;
}
.lb-ring svg { display: block; width: 100%; height: 100%; transform: rotate(-90deg); }
.lb-ring-num {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 9.5px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}
/* Score cell: label above ring on phones (matching the other stat cells' stacked
   label/value), ring right-aligned on desktop. */
.lb-row .lb-score-cell {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 3px;
}

/* Phones: every stat VALUE gets the ring's 32px box height, centred — so ROI/Win/Profit
   sit on the same horizontal axis as the score ring instead of floating above it. */
@media (max-width: 767px) {
  .lb-row .lb-score-cell { align-items: center; }
  .lb-row .lb-stats .lb-val {
    display: inline-flex;
    align-items: center;
    min-height: 32px;
  }
}
/* ⚠ CSS, never an inline handler. This was a pair of onmouseover/onmouseout attributes
   whose mouseout wrote `background: transparent` onto the element — inline, so the first
   hover permanently erased the card background. */
.lb-row:hover {
  background: var(--bg-tertiary);
  border-color: color-mix(in oklab, var(--accent-primary) 40%, var(--hh-rule, var(--bg-tertiary)));
}

.lb-id { display: flex; align-items: center; gap: 10px; min-width: 0; padding-right: 84px; /* clears the absolute Follow pill */ }
.lb-hfollow { display: none; } /* the mobile sort bar has no follow column */

/* One equal column per child, whatever the cell count — this is what makes the
   layout survive the Rank and Biff Score columns coming and going. */
.lb-stats {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 4px;
  margin-top: 11px;
}

/* Bounded to the phone range rather than written as a base rule with a desktop override:
   these are two classes deep so they beat Tailwind's .text-right / .text-sm, which load
   later — and once you have won that fight you cannot hand the value back on desktop
   (font-size: unset would inherit 16px rather than restore .text-sm's 14px).

   The Profit cell is the only one without a Tailwind size class, so left alone it renders
   16px against the others' 14px and sits visibly lower in the strip. */
@media (max-width: 767px) {
  .lb-row .lb-stats > * {
    text-align: center; min-width: 0;
    font-size: 14.5px; line-height: 1.25;
    font-variant-numeric: tabular-nums;
  }
}

/* ...and the stat labels recede, so the strip reads as secondary to it. */
.lb-lab {
  display: block;
  margin-bottom: 3px;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
  opacity: 0.75;
}

.lb-avatar {
  width: 34px; height: 34px; flex-shrink: 0;
  border-radius: 9px;
  background: linear-gradient(135deg, rgba(59,130,246,0.25), rgba(139,92,246,0.25));
  display: flex; align-items: center; justify-content: center;
  font-size: 17px;
}
.lb-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }

/* The name finally has room. Ellipsis stays as insurance, not as the normal case. */
.lb-biff-col { min-width: 0; overflow: hidden; }
.lb-biff-col > div { min-width: 0; }
/* The name anchors the card, so it leads on size and weight. */
.lb-biff-col .font-semibold {
  display: block; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 16px; font-weight: 700; letter-spacing: -0.01em;
  color: var(--text-primary);
}

.lb-row [data-rank] { padding: 2px 7px; font-size: 11px; }

/* The Confidence line under the name is a hover tooltip, so it is inert on touch —
   and the "How the Biff Score works" panel below the table already explains it. */
.lb-biff-sub { display: none; }

/* ── Phone header → sort bar ────────────────────────────────────
   Same buttons, same onclick, same .sort-indicator: sortLeaderboard() is untouched. */
.lb-hid { display: none; }

.lb-header {
  padding: 11px 14px;
  border-radius: 10px;
  background: var(--bg-secondary);
  border: 1px solid var(--hh-rule, var(--bg-tertiary));
  /* The gold-caps eyebrow the rest of the app uses for a section label. */
  position: relative;
}
.lb-header::before {
  content: "Sort";
  display: block;
  margin-bottom: 7px;
  font-size: 9.5px; font-weight: 800; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--accent-gold, var(--text-muted));
}
.lb-hstats {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 4px;
}
/* The !important below is load-bearing: the header cells and their buttons carry INLINE
   justify-content / font-size / gap / letter-spacing from the ERB, and inline beats any
   stylesheet rule short of !important. Nothing else in this file needs it. */
.lb-header .lb-h-cell { justify-content: center !important; min-width: 0; }
.lb-header .lb-h-cell button {
  justify-content: center;
  width: 100%;
  min-height: 34px;
  gap: 3px !important;
  font-size: 10px !important;
  letter-spacing: 0.04em !important;
  white-space: nowrap;
}
/* Hover-only affordance, useless on touch, and it crowds a 70px chip. */
.lb-header .bs-tt { display: none; }
.lb-h-long { display: none; }
.lb-h-short { display: inline; }

/* ── Sport filter ───────────────────────────────────────────────────────────────
   Rendered by shared/_sport_filter.html.erb on both pages.

   A <details> dropdown: the trigger names the current selection, and opening it reveals
   "All sports" plus the nine sports. It replaced a flat strip of ten equal cells where
   All — the least informative of them — was the widest and loudest thing on the page.

   A one-row scrolling strip is deliberately NOT used anywhere here: mobile.css records
   that being tried for the hero tabs and rejected — primary navigation should not need
   discovering. */
.sportbar {
  border: 1px solid var(--hh-rule, var(--bg-tertiary));
  border-radius: 12px;
  background: var(--bg-secondary);
  overflow: hidden;
}

.sportbar__trigger {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 10px;
  min-height: 48px; padding: 10px 14px;
  /* The gold rule the old All button carried. It is this control's marker, so the trigger
     keeps it whether the dropdown is open or shut — and when open it doubles as the divider
     between the trigger and the panel.

     ⚠ The radius below is what makes the rule CURVE instead of clipping. An inset shadow
     follows its own element's border-box, and this summary is a square box inside a 12px
     rounded, overflow:hidden parent — so the gold ran straight into the corners and got cut
     off by the parent's curve. Matching the parent's radius (minus its 1px border) makes the
     shadow trace the same arc. */
  border-radius: 11px;
  box-shadow: inset 0 -2px 0 0 var(--accent-gold);
}
/* Open, the trigger's bottom edge meets the panel, so only the top corners are round. */
.sportbar[open] .sportbar__trigger { border-radius: 11px 11px 0 0; }
.sportbar__trigger::-webkit-details-marker { display: none; }
.sportbar__trigger:hover { background: var(--bg-tertiary); }
/* Without this the browser paints its own ring on the summary, which reads as a rendering
   fault rather than a focus state. */
.sportbar__trigger:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: -2px; }

.sportbar__current {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  flex: 1; min-width: 0;
}
.sportbar__cur-ico { display: block; line-height: 0; flex: none; }
.sportbar__cur-ico .sport-ico { height: 22px; }
.sportbar__cur-label {
  font-size: 13px; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The arrow is a CHIP, not a glyph — a bare ▾ read as decoration; a bordered box with a
   bold chevron reads as "this opens". The SVG rotates, the chip stays put. */
.sportbar__chev {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  background: var(--bg-tertiary);
  border: 1px solid var(--hh-rule, var(--bg-tertiary));
  color: var(--accent-primary);
}
.sportbar__chev svg { width: 14px; height: 14px; transition: transform .15s ease; }
.sportbar[open] .sportbar__chev svg { transform: rotate(180deg); }
.sportbar__trigger:hover .sportbar__chev { border-color: var(--accent-primary); }

.sportbar__panel { padding: 8px; }

/* The reset. Without it the trigger could sit on "Football" with no way back. */
.sportbar__all {
  display: block; width: 100%;
  padding: 9px 12px; margin-bottom: 8px;
  border: none; border-radius: 8px;
  background: transparent; font: inherit;
  font-size: 12px; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase;
  text-align: center; color: var(--text-secondary); cursor: pointer;
  transition: background .14s ease, color .14s ease;
}
.sportbar__all:hover { background: var(--bg-tertiary); color: var(--text-primary); }

/* Nine sports: three across on a phone, so the grid comes out exactly 3 x 3 with no ragged
   last row, and one row of nine once there is width for it. Never a bare 1fr — an icon in
   an auto-min track grows the track instead of being made to fit. */
.sportbar__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 4px;
}

.sporttile {
  position: relative;              /* the Soon pill is positioned against this */
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 56px;
  padding: 6px 4px 14px;           /* the bottom padding is the pill's lane */
  border: none;
  border-radius: 8px;
  background: transparent;
  font: inherit;
  color: var(--text-secondary);
  cursor: pointer;
  transition: background 0.14s ease;
}
.sporttile:hover { background: var(--bg-tertiary); }
.sporttile__ico { display: block; line-height: 0; }
.sport-ico { height: 30px; width: auto; object-fit: contain; display: block; margin: 0 auto; }
/* Emoji stand-in for a sport with no PNG (baseball). Matched to the images' box so the
   tiles keep one baseline. */
.sport-ico--emoji {
  height: 30px; line-height: 30px; font-size: 26px;
  display: flex; align-items: center; justify-content: center;
}

/* Selected — a gold rule under a lifted tile, not a solid accent slab. */
.sportbar__all.is-active,
.sporttile.is-active {
  background: var(--bg-tertiary);
  box-shadow: inset 0 -2px 0 0 var(--accent-gold);
  color: var(--text-primary);
}

.sportbar__all:focus-visible,
.sporttile:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: -2px;
}

/* Coming soon — the pill sits ON the icon rather than under it, so a disabled tile is the
   same height as a live one.

   Anchored to the tile's LOWER edge, not its centre: centred, the pill is wider than the
   icon and covers it outright, which turns nine distinct sports into nine identical gold
   badges. Overlapping only the bottom of the icon keeps the sport recognisable — which is
   the whole reason the icon is there. Same reason it only dims to 0.5. */
.sporttile.is-soon { cursor: not-allowed; }
.sporttile.is-soon:hover { background: transparent; }
.sporttile.is-soon .sport-ico { opacity: 0.5; }
.sporttile__soon {
  position: absolute;
  bottom: 3px; left: 50%;
  transform: translateX(-50%);
  padding: 2px 5px;
  border-radius: 4px;
  background: var(--accent-gold);
  color: #1a1206;                  /* dark ink on gold — the pill has to carry the contrast */
  font-size: 8.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  line-height: 1;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
}

/* Five period tabs want more room than a 320px phone has. */
@media (max-width: 400px) {
  .lb-periods a { font-size: 12px; padding-left: 2px; padding-right: 2px; }
}

/* ── Tablet + desktop (≥768px) — the table, unchanged ───────────
   display:contents dissolves the two wrappers so their children are direct grid
   items again, which is what keeps the desktop table byte-for-byte what it was.
   Same trick mobile.css §1c uses on .match-details for the fixture rows. */
@media (min-width: 768px) {
  .lb-id, .lb-stats, .lb-hid, .lb-hstats { display: contents; }

  /* A leaderboard is scanned by column, so the table stays a table up here — the cards are
     a phone treatment only. The wrapper comes back as the surface, the rows go back to
     being rows inside it, and the separator is a single rule again. */
  .lb-table {
    background: var(--bg-secondary);
    border: 1px solid var(--hh-rule, var(--bg-tertiary));
  }
  #leaderboard-rows { display: block; gap: 0; margin-top: 0; }

  .lb-row {
    border: none;
    border-bottom: 1px solid var(--hh-rule, var(--bg-tertiary));
    border-radius: 0;
    background: transparent;
  }
  .lb-row:last-child { border-bottom: none; }
  .lb-row:hover { background: var(--bg-tertiary); border-color: var(--hh-rule, var(--bg-tertiary)); }

  .lb-header {
    border: none;
    border-radius: 10px 10px 0 0;
    background: var(--bg-tertiary);
  }

  /* Every list ends with the 92px Follow column — the pill is a real grid cell here. */
  .lb-header,
  .lb-row {
    padding: 18px 24px;
    grid-template-columns: minmax(160px, 3fr) minmax(90px, 1fr) minmax(90px, 1fr) minmax(100px, 1.2fr) 92px;
  }
  .lb-header.lb-has-score,
  .lb-row.lb-has-score {
    grid-template-columns: minmax(160px, 3fr) minmax(90px, 1fr) minmax(90px, 1fr) minmax(90px, 1fr) minmax(100px, 1.2fr) 92px;
  }
  .lb-header.lb-has-rank,
  .lb-row.lb-has-rank {
    grid-template-columns: 84px minmax(160px, 3fr) minmax(90px, 1fr) minmax(90px, 1fr) minmax(100px, 1.2fr) 92px;
  }
  .lb-header.lb-has-rank.lb-has-score,
  .lb-row.lb-has-rank.lb-has-score {
    grid-template-columns: 84px minmax(160px, 3fr) minmax(90px, 1fr) minmax(90px, 1fr) minmax(90px, 1fr) minmax(100px, 1.2fr) 92px;
  }
  .lb-row .lb-follow {
    position: relative; /* stays above the stretched link; static grid placement */
    top: auto;
    right: auto;
    justify-self: end;
  }
  .lb-id { padding-right: 0; } /* display:contents dissolves the box; keep it clean anyway */
  .lb-hfollow { display: block; }
  .lb-row .lb-ring { width: 36px; height: 36px; }
  .lb-row .lb-ring-num { font-size: 10.5px; }
  .lb-row .lb-score-cell { flex-direction: row; justify-content: flex-end; align-items: center; }

  .lb-header { padding: 16px 24px; }
  .lb-header::before { content: none; }
  .lb-lab { opacity: 1; }
  .lb-stats { margin-top: 0; padding-top: 0; border-top: none; }
  .lb-lab { display: none; }

  .lb-avatar { width: 40px; height: 40px; border-radius: 10px; font-size: 20px; }
  .lb-biff-col .font-semibold { font-size: 16px; }
  .lb-biff-sub { display: block; }
  .lb-row [data-rank] { padding: 6px 12px; font-size: 14px; }

  .lb-header .bs-tt { display: inline-flex; }
  .lb-h-long { display: inline; }
  .lb-h-short { display: none; }

  .sportbar__trigger { min-height: 52px; }
  .sportbar__cur-label { font-size: 14px; }
  .sportbar__cur-ico .sport-ico { height: 24px; }
  .sportbar__panel { padding: 10px; }
  .sportbar__all { font-size: 13px; margin-bottom: 10px; }
  /* Nine across in one row once there is room for it. */
  .sportbar__grid { grid-template-columns: repeat(9, minmax(0, 1fr)); }
  .sporttile { min-height: 62px; padding: 7px 4px 16px; }
  .sport-ico { height: 34px; }
  .sport-ico--emoji { height: 34px; line-height: 34px; font-size: 30px; }
  .sporttile__soon { font-size: 9.5px; padding: 3px 7px; bottom: 4px; }
}
