/* ═══════════════════════════════════════════════════════════════
   FlexRank Editor — Column Depth
   ═══════════════════════════════════════════════════════════════ */

/* Light mode: subtle lift shadow on column cards for depth */
.fr-editor-column {
  box-shadow: 0 1px 3px rgba(60, 40, 20, 0.06),
              0 1px 2px rgba(60, 40, 20, 0.04);
  /* Containment: column is its own layout/paint/style boundary so a
     reorder in one column can't dirty layout/style in another. Drives
     the drag-time UpdateLayoutTree elementCount way down (was 85k+). */
  contain: layout paint style;
}
html[data-theme="dark"] .fr-editor-column {
  box-shadow: none;
}

/* Skip rendering work for columns scrolled off-screen horizontally.
   Active drag column stays in viewport so SortableJS measurements
   are unaffected. Intrinsic size matches typical column width so
   placeholder doesn't collapse the layout. */
.fr-editor-column {
  content-visibility: auto;
  contain-intrinsic-size: auto 800px;
}

/* ═══════════════════════════════════════════════════════════════
   FlexRank Editor — Row Containment
   ═══════════════════════════════════════════════════════════════ */

/* Each player row is its own layout/paint/style boundary. Without
   this, a class change on body or a parent forces every row's
   subtree through style recalc/layout. With it, invalidation stops
   at the row root. Trace pre-fix showed 34k dirty layout objects
   on a single drag move; containment scopes that to the rows that
   actually changed. Style containment is safe here because rows
   don't use counters/quotes that need to escape. */
.fr-row,
.fr-tier-break-row {
  contain: layout paint style;
}

/* Skip rendering work for rows scrolled off-screen vertically.
   Each column scrolls independently; rows the user can't see don't
   need style/layout/paint. Browser auto-promotes to full rendering
   when a row enters the viewport, so SortableJS sees real rects on
   anything it could practically swap with (drag candidate area is
   always in-viewport). Intrinsic sizes match the rendered heights:
   28 px desktop / 48 px mobile for player rows; 20 px / 28 px for
   tier breaks. Off by a couple px on either side is fine —
   content-visibility's main job is to drop the per-row recalc cost,
   not to preserve scroll position to the pixel. */
/* Per-row content-visibility was deliberately removed: it collapses
   off-screen rows' contribution to the column body's scrollHeight, so the
   .overflow-y-auto body never overflows — overflow-y-auto stays inactive
   and spotlight autoscroll has nothing to scroll. The horizontal
   .fr-editor-column content-visibility (above) still skips off-screen
   columns; per-row containment (contain: layout paint style on .fr-row)
   still scopes style/layout invalidation. The drag perf win from #15
   came mostly from `contain`, not from content-visibility on each row. */

/* SortableJS drag list: paint+layout containment so reorder animations
   don't trigger paint invalidation on neighbors. Children (rows) already
   carry style containment, so it isn't needed at this level. */
[data-controller~="drag"] {
  contain: layout paint;
}

/* (Hover-kill rule deliberately not present here. Rationale: a rule
   like `.fr-editor-shell.fr-dragging .fr-row > *` cascades on every
   class toggle of `.fr-dragging`, forcing the style engine to
   re-evaluate `.fr-row > *` matches across the whole editor (~30k+
   elements). The trace after Step 3 showed a 380 ms style recalc
   spike at pointerdown that traced back to this exact toggle pattern.
   The hover cost it suppressed (~85 ms cumulative dragenter/mouseenter)
   was an order of magnitude smaller than the cascade it caused.
   Any future hover-suppression mechanism MUST avoid descendant
   selectors anchored at the toggled class.) */

/* ═══════════════════════════════════════════════════════════════
   FlexRank Editor — Drag & Drop States
   ═══════════════════════════════════════════════════════════════ */

/* ── Ghost: the invisible slot left in the list ────────────────
   Content is hidden. The animated gap from neighboring items
   sliding apart is the drop indicator — no decoration needed.
   No pseudo-elements or position: relative to avoid layout
   recalculation jitter as SortableJS moves the ghost in the DOM. */
.fr-sortable-ghost {
  opacity: 0 !important;
}

/* Force-render the dragged row's subtree. Rows carry
   `content-visibility: auto` for offscreen-skip perf, but Chromium's
   native HTML5 drag-image snapshot generator skips content-visibility
   subtrees, producing an empty drag image. Override on the states
   active during drag so the snapshot includes the row's content. */
.fr-sortable-chosen,
.fr-sortable-fallback {
  content-visibility: visible;
}

/* ── Chosen: the instant lift when you grab a row ──────────────
   Provides immediate tactile feedback before dragging begins. */
.fr-sortable-chosen {
  z-index: 10;
}
.fr-sortable-chosen > .fr-row-content {
  background-color: var(--surface-2);
  box-shadow: inset 0 0 0 2px var(--accent),
              0 4px 12px -2px rgba(0, 0, 0, 0.15);
  border-radius: 0.25rem;
}

/* ── Fallback: the card following your cursor ──────────────────
   Elevated card with subtle scale for depth. Clean silhouette
   with hidden expand/tier-break content. */
.fr-sortable-fallback {
  box-shadow: 0 16px 32px -6px rgba(0, 0, 0, 0.30),
              0 8px 16px -4px rgba(0, 0, 0, 0.15),
              0 0 0 1px rgba(255, 255, 255, 0.06);
  border-radius: 0.375rem;
  opacity: 0.95;
  background-color: var(--surface);
}
.fr-sortable-fallback .drag-exclude {
  display: none !important;
}

/* (Expanded-row collapse during drag is now handled in
   drag_controller.js#onChoose by setting inline style directly on the
   handful of `.fr-row-expanded` elements, then restoring on drag-end.
   The previous descendant rule `.fr-editor-shell.fr-dragging .fr-row-expanded`
   triggered a full-tree style invalidation on every `.fr-dragging`
   toggle even though only 0-1 elements actually matched. Same root
   cause as the hover-kill cascade above — descendant selectors
   anchored at a toggled class are paid in full on toggle.) */

/* ── Drop landing: brief highlight when item settles ───────────
   Applied by JS after drop, removed after animation completes. */
.fr-drop-landed > .fr-row-content {
  animation: drop-land 400ms ease-out;
}
@keyframes drop-land {
  0%   { background-color: var(--accent-bg); box-shadow: inset 0 0 0 2px var(--accent); }
  100% { background-color: transparent; box-shadow: none; }
}

/* ── Composite list syncing lock: applied while async propagation is
   in flight. Removed implicitly when the container is replaced by the
   server's turbo_stream response (synchronous path) or by an
   ActionCable broadcast (async path). The watchdog refetch in
   composite_lock_controller is the recovery path for dropped streams.

   Visual treatment: dim the list + lay a slow diagonal stripe shimmer
   over it to communicate "working, don't touch yet". pointer-events
   are blocked on the inner content so accidental clicks/drags can't
   interleave with the in-flight server work. */
.fr-list-syncing {
  position: relative;
  opacity: 0.65;
  transition: opacity 120ms ease-out;
}
.fr-list-syncing > [data-controller~="drag"] {
  pointer-events: none;
}
.fr-list-syncing::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(
    45deg,
    transparent 0 10px,
    var(--accent-bg) 10px 20px
  );
  background-size: 200% 200%;
  animation: fr-list-syncing-shimmer 1.4s linear infinite;
  border-radius: inherit;
  mix-blend-mode: multiply;
}
html[data-theme="dark"] .fr-list-syncing::after {
  mix-blend-mode: screen;
}
@keyframes fr-list-syncing-shimmer {
  from { background-position: 0 0; }
  to   { background-position: 28px 0; }
}
@media (prefers-reduced-motion: reduce) {
  .fr-list-syncing::after {
    animation: none;
    background: var(--accent-bg);
    opacity: 0.35;
  }
}

/* ── Synced move: flash on composite list nodes that moved as a
   result of a positional list reorder. Applied server-side via
   the Turbo Stream replace so the animation plays on render. */
.fr-synced-move {
  animation: synced-flash 600ms ease-out;
}
@keyframes synced-flash {
  0%   { background-color: var(--accent-bg); box-shadow: inset 3px 0 0 0 var(--accent); }
  100% { background-color: transparent; box-shadow: none; }
}

/* ── Cross-list highlight: same player in other lists ─────────
   When a player row is grabbed, matching rows in other columns
   get an accent tint so the ranker can see where that player
   sits across lists. */
.fr-cross-highlight > .fr-row-content {
  background-color: var(--accent-bg);
  box-shadow: inset 3px 0 0 0 var(--accent);
}

/* ── Composite row dividers: stronger contrast against surface-3 ─
   Default --border is near-invisible on composite column backgrounds,
   so bump to --border-hover for legible row separation. */
.bg-surface-3 .border-b {
  border-color: var(--border-hover);
}

/* ── Position row tints: faint background color by position ────
   Uses the same CSS variables as position badges so badge color
   and row tint always match across themes. */
[data-nfl-position="QB"]  > .fr-row-content { background-color: var(--pos-qb-tint); }
[data-nfl-position="RB"]  > .fr-row-content { background-color: var(--pos-rb-tint); }
[data-nfl-position="WR"]  > .fr-row-content { background-color: var(--pos-wr-tint); }
[data-nfl-position="TE"]  > .fr-row-content { background-color: var(--pos-te-tint); }
[data-nfl-position="K"]   > .fr-row-content { background-color: var(--pos-k-tint); }
[data-nfl-position="DEF"] > .fr-row-content { background-color: var(--pos-def-tint); }

/* ── Position number: smooth transition on renumber ────────── */
[data-position-number] {
  transition: opacity 100ms ease;
}
.fr-pos-updating {
  opacity: 0.4;
}

/* ── Prevent text selection during drag ─────────────────────── */
.drag-handle {
  user-select: none;
  -webkit-user-select: none;
}
.fr-sortable-fallback {
  user-select: none;
  -webkit-user-select: none;
}

/* ── Drag handle affordance ──────────────────────────────────── */
.drag-handle {
  transition: color 120ms ease;
}
.drag-handle:hover svg {
  color: var(--accent) !important;
  transform: scale(1.15);
  transition: transform 120ms ease, color 120ms ease;
}
.drag-handle:active svg {
  transform: scale(0.95);
}

/* ── Player spotlight: hover and pinned states ────────────────
   Hover gives ephemeral highlight; pinned is stronger for
   sustained cross-list tracking. Applied to row wrapper, so
   use > .fr-row-content to match existing fr-cross-highlight
   pattern and layer over position tints. */
.fr-spotlight-hover > .fr-row-content {
  background-color: var(--accent-bg);
  box-shadow: inset 3px 0 0 0 var(--accent);
}

.fr-spotlight-pinned > .fr-row-content {
  background-color: var(--accent-bg);
  box-shadow: inset 3px 0 0 0 var(--accent), 0 0 0 1px var(--accent);
}

/* ── ECR delta indicators ────────────────────────────────────
   Hidden by default. Visible when fr-show-ecr is active on an
   ancestor. Content and color are set by JS after toggle/drag. */
.fr-ecr-delta {
  display: none;
}
.fr-show-ecr .fr-ecr-delta {
  display: inline;
}

/* ── Snapshot delta indicators ───────────────────────────────
   Same pattern as ECR. Visible when fr-show-snapshot is active. */
.fr-snapshot-delta {
  display: none;
}
.fr-show-snapshot .fr-snapshot-delta {
  display: inline;
}

/* Tiny single-letter source label inside delta pills (E = ECR, S = Snapshot).
   Same color as delta value via currentColor, dimmed so number stays primary. */
.fr-delta-label {
  font-size: 7px;
  font-weight: 600;
  opacity: 0.55;
  margin-right: 1px;
  letter-spacing: 0.02em;
}

/* ── Scrollbars: thin, translucent, non-intrusive ──────────── */
::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background-color: rgba(0, 0, 0, 0.12);
  border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
  background-color: rgba(0, 0, 0, 0.2);
}
::-webkit-scrollbar-corner {
  background: transparent;
}
/* Firefox */
* {
  scrollbar-width: thin;
  scrollbar-color: rgba(0, 0, 0, 0.12) transparent;
}
html[data-theme="dark"] ::-webkit-scrollbar-thumb {
  background-color: rgba(255, 255, 255, 0.1);
}
html[data-theme="dark"] ::-webkit-scrollbar-thumb:hover {
  background-color: rgba(255, 255, 255, 0.2);
}
html[data-theme="dark"] * {
  scrollbar-color: rgba(255, 255, 255, 0.1) transparent;
}

/* ── Number input spinners: hide until hover ──────────────── */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  opacity: 0;
  transition: opacity 150ms ease;
}
input[type="number"]:hover::-webkit-inner-spin-button,
input[type="number"]:hover::-webkit-outer-spin-button,
input[type="number"]:focus::-webkit-inner-spin-button,
input[type="number"]:focus::-webkit-outer-spin-button {
  opacity: 1;
}
/* Firefox: hide spinners entirely — value is editable via typing */
input[type="number"] {
  -moz-appearance: textfield;
}

/* ── Reduced motion ────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .fr-sortable-ghost::after,
  .fr-sortable-chosen,
  .fr-sortable-fallback,
  .fr-drop-landed > .fr-row-content,
  .fr-synced-move,
  .fr-cross-highlight > .fr-row-content,
  .fr-spotlight-hover > .fr-row-content,
  .fr-spotlight-pinned > .fr-row-content,
  [data-position-number],
  .drag-handle,
  .drag-handle svg {
    transition: none !important;
    animation: none !important;
    transform: none !important;
  }
}

/* ═══════════════════════════════════════════════════════════════
   FlexRank Editor — Preset Tab Switching
   ═══════════════════════════════════════════════════════════════ */

/* Columns revealed by a preset change get a brief enter animation
   so the swap reads as a deliberate transition, not a reload. */
@keyframes fr-column-enter {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.fr-column-enter {
  animation: fr-column-enter 160ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .fr-column-enter {
    animation: none;
  }
}

/* ═══════════════════════════════════════════════════════════════
   FlexRank Editor — Row Insert Menu
   ═══════════════════════════════════════════════════════════════ */

/* Popover shell — layered shadow gives lift without overpowering the
   surrounding editor. */
.fr-row-insert-menu {
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.25),
    0 8px 24px rgba(0, 0, 0, 0.45);
}

/* Icon column — fixed width so labels line up across items even when
   glyph shapes differ (plus vs dashed-line). Lights up to the accent
   color on parent menuitem hover/focus. */
.fr-row-insert-glyph {
  flex: none;
  width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-muted, #8a92a0);
  opacity: 0.85;
  transition: color 120ms ease, opacity 120ms ease;
}

.fr-row-insert-item:hover .fr-row-insert-glyph,
.fr-row-insert-item:focus .fr-row-insert-glyph {
  color: var(--color-accent, #4a9eff);
  opacity: 1;
}

/* Keyboard shortcut chip. Top inset highlight + slight bottom shadow +
   thicker bottom border give a faux-3D "physical key" affordance, so P / T
   read as keystrokes, not as labels. */
.fr-row-insert-kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  background:
    linear-gradient(to bottom, rgba(255, 255, 255, 0.05), rgba(0, 0, 0, 0.12)),
    var(--color-surface-2, #1f232c);
  border: 1px solid var(--color-border, #353a45);
  border-bottom-width: 2px;
  border-radius: 5px;
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-size: 11px;
  font-weight: 600;
  color: var(--color-text-muted, #c0c6d1);
  line-height: 1;
  flex: none;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.04),
    0 1px 0 rgba(0, 0, 0, 0.25);
  transition: color 120ms ease, border-color 120ms ease;
}

.fr-row-insert-item:hover .fr-row-insert-kbd,
.fr-row-insert-item:focus .fr-row-insert-kbd {
  border-color: var(--color-accent, #4a9eff);
  color: var(--color-text-primary, #e6e8eb);
}
