/* rehearsal.css — champion select emulator.
 *
 * The stage is deliberately dark in BOTH site themes. Everything else on
 * lafter.gg follows the theme toggle, but this panel is imitating a specific
 * piece of software that has exactly one appearance, and a light-mode champion
 * select would be less faithful, not more considerate. The site chrome around
 * it (nav, bar) stays theme-aware, so the toggle still visibly does something.
 *
 * Palette is local to this file and prefixed --cs-, so it can never be
 * mistaken for a site token or leak into one.
 */

.rehearsalPage {
    --cs-void:      #04070c;
    --cs-deep:      #0a1220;
    --cs-panel:     #101c2e;
    --cs-line:      #1f3552;
    --cs-line-soft: #16283f;
    --cs-gold:      #c8aa6e;
    --cs-gold-dim:  #785a28;
    --cs-gold-lit:  #f0e6d2;
    --cs-blue:      #0acbe6;
    --cs-blue-deep: #0a323c;
    --cs-red:       #ef6d75;
    --cs-red-deep:  #3c1418;
    --cs-text:      #cdd7e0;
    --cs-text-dim:  #7f8b99;

    /* Fixed to the viewport, not min-height: this is imitating an application
       window, and a champion select you have to scroll is not one. Everything
       inside scrolls in its own pane instead. */
    height: 100vh;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

/* ===== Emulator controls (site chrome, theme-aware) ===== */

.rhBar {
    /* A grid, not a flex row. Centring the middle group in a flex row only
       centres it between its neighbours, so it drifted left or right depending
       on how wide the queue slider and the buttons happened to be. Equal 1fr
       side columns put it on the page's centre line regardless. */
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 18px;
    padding: 7px 18px;
    border-bottom: 1px solid var(--border);
    background: var(--bg-card);
    flex-shrink: 0;
}
.rhBarGroup { display: flex; align-items: center; gap: 10px; min-width: 0; }
.rhBarRight { margin-left: auto; }
/* Start/Restart with End beneath it, both centred on the bar's middle. */
.rhBarStack { flex-direction: column; align-items: flex-start; gap: 6px; }
.rhToggles { display: flex; align-items: center; gap: 14px; }

/* Side by side, not stacked -- two short words sit on one line, and stacking
   them was what forced the bar tall enough to need the padding below. */
.rhBarCenter { flex-direction: row; align-items: center; gap: 10px; }
/* One word each now, so they need far less room -- and a size up, since these
   are the two controls the page is actually driven by.

   Widths are fixed per button and expressed in ch, so each is sized to the
   text it holds but does not resize when that text changes. The left one says
   Start or Restart depending on state; sizing it to its content would make it
   jump two characters wider the moment a draft begins, shifting the button
   under the cursor at exactly the moment someone might click it again. 7ch is
   the longer of the two words. End is three characters and gets a width that
   says so, rather than matching a neighbour it has nothing in common with. */
/* Matched against .lg, which these buttons also carry. Both selectors are two
   classes, so specificity ties and source order decides -- and .rehearsalBtn.lg
   is defined further down the file, so it was winning with padding: 9px 22px.
   The widths here were computed as if the padding were zero, which left End
   50px wide holding 44px of padding: the text had nowhere to sit and spilled
   right, which is the off-centre label.
   
   Padding and width now live in the same rule so they cannot disagree again. */
.rhBarCenter .rehearsalBtn.lg {
    white-space: nowrap;
    font-size: 0.82rem;
    padding: 9px 18px;
    text-align: center;
}
/* Start/Restart needs a locked width -- its label changes, and sizing to
   content would resize it under the cursor the moment a draft begins. 7ch is
   the longer word, and ch is the right unit for a monospace face.

   End does not: its label never changes, so it sizes to its own content and is
   centred by definition. Pinning it to 3ch was worse than useless -- ch
   measures the font's "0" glyph, so it is exactly three characters only once
   JetBrains Mono has loaded, and in the swap window the fallback made the box
   the wrong size for its own text. A label that never changes gains nothing
   from a fixed width. */
.rhBarCenter #startBtn { width: calc(7ch + 36px); }

/* Live and Access stacked, one width, so they line up as a block on the right
   and neither moves when the other's label changes. */
.rhBarRight { flex-direction: column; align-items: flex-end; gap: 5px; }
/* One width for both, wide enough that "Access code  On  XXXXXX" stays on one
   line -- the label, the state and the code all sit inside this button. */
.rhBarRight .rhBarBtn { width: 210px; justify-content: center; white-space: nowrap; }

.rhBarBtn.access {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 6px;
    white-space: nowrap;
    background: transparent;
    border-color: var(--border);
    color: var(--text-muted);
}
.rhBarBtn.access:hover { border-color: var(--gold-dim); color: var(--text); }
.rhBarBtn.access.on { border-color: var(--gold-dim); color: var(--gold); }
.accessLabel { font-size: 0.6rem; letter-spacing: 0.08em; text-transform: uppercase; }
.accessState { font-weight: 700; }
.accessCode { font-weight: 700; letter-spacing: 0.14em; color: var(--gold); }
/* Dimmed rather than hidden: the underscores are the point -- they show the
   code slots are there and empty. */
.rhBarBtn.access.noCode .accessCode { color: var(--text-muted); opacity: 0.65; }
.accessCode { cursor: copy; }
.accessCode:hover { color: var(--gold-light); }
.accessCode.copied { color: var(--verified); }
.rhBarLabel {
    font-family: var(--font-mono);
    font-size: 0.6rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
}
.rhBarBtn {
    font-family: var(--font-mono);
    font-size: 0.68rem;
    padding: 5px 12px;
    border-radius: 6px;
    border: 1px solid var(--accent-border);
    background: var(--accent);
    color: var(--accent-text);
    cursor: pointer;
    transition: filter 0.15s ease, border-color 0.15s ease;
}
.rhBarBtn:hover { filter: brightness(1.12); }
.rhBarBtn.on { border-color: var(--gold); }

.rhBarBtn.ghost {
    background: transparent;
    border-color: var(--border);
    color: var(--text-muted);
}
.rhBarBtn.ghost:hover { border-color: var(--gold-dim); color: var(--text); }






.rehearsalToggle {
    display: flex; align-items: center; gap: 5px;
    font-family: var(--font-mono);
    font-size: 0.68rem; color: var(--text-muted);
    cursor: pointer; user-select: none;
}
.rehearsalToggle input { accent-color: var(--gold); cursor: pointer; }

.rehearsalBtn {
    padding: 5px 14px; border-radius: 6px;
    font-family: var(--font-mono); font-size: 0.68rem;
    border: 1px solid var(--gold-dim);
    background: linear-gradient(140deg, var(--gold), var(--gold-light));
    color: var(--on-gold); cursor: pointer;
    transition: filter 0.12s ease;
}
.rehearsalBtn:hover { filter: brightness(1.08); }
.rehearsalBtn.danger { background: transparent; color: var(--red-x); border-color: color-mix(in srgb, var(--red-x) 40%, transparent); }
.rehearsalBtn.danger:hover { background: color-mix(in srgb, var(--red-x) 10%, transparent); }
.rehearsalBtn:disabled { opacity: 0.35; pointer-events: none; }




/* ===== The emulated client ===== */

.csStage {
    position: relative;
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    color: var(--cs-text);
    background:
        radial-gradient(ellipse 90% 55% at 50% 0%, #12243a 0%, transparent 70%),
        linear-gradient(180deg, var(--cs-deep) 0%, var(--cs-void) 100%);
}

.csHeader {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 20px;
    padding: 8px 26px 10px;
    border-bottom: 1px solid var(--cs-line-soft);
    flex-shrink: 0;
}
.csTeamName {
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: 0.22em;
    text-transform: uppercase;
}
.csTeamName.blue { color: var(--cs-blue); }
.csTeamName.red  { color: var(--cs-red); text-align: right; }

.csTimer { text-align: center; min-width: 210px; }
.csPhase {
    font-family: var(--font-mono);
    font-size: 0.62rem; letter-spacing: 0.3em; text-transform: uppercase;
    color: var(--cs-text-dim);
}
.csClock {
    font-family: var(--font-mono);
    font-size: 1.9rem; font-weight: 700; line-height: 1.1;
    color: var(--cs-gold-lit);
    font-variant-numeric: tabular-nums;
}
.timerBar {
    width: 210px; height: 2px; margin: 4px auto 0;
    background: var(--cs-line); overflow: hidden;
}
.timerFill {
    height: 100%; width: 100%;
    background: linear-gradient(90deg, var(--cs-gold-dim), var(--cs-gold));
    transition: width 1s linear;
}
.notStarted .timerBar, .notStarted .csClock,
.finished .timerBar, .finished .csClock { opacity: 0; }

.csMain {
    flex: 1;
    display: grid;
    grid-template-columns: 212px minmax(0, 1fr) 212px;
    gap: 0;
    min-height: 0;
}

/* --- team columns --- */

.csTeam { display: flex; flex-direction: column; min-height: 0; padding: 10px; }
/* The sitewide bug-report button floats bottom-left and was sitting on top of
   the blue team's last slot. Clearing it here rather than moving a widget
   every other page depends on. */
.csTeam.blue { padding-bottom: 52px; }
.csTeam > div { display: flex; flex-direction: column; gap: 6px; height: 100%; }

.csSlot {
    position: relative;
    flex: 1;
    min-height: 0;
    overflow: hidden;
    border: 1px solid var(--cs-line-soft);
    border-radius: 3px;
    background: linear-gradient(180deg, var(--cs-panel), var(--cs-deep));
    cursor: pointer;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.csTeam.blue .csSlot.locked { border-color: color-mix(in srgb, var(--cs-blue) 45%, transparent); }
.csTeam.red  .csSlot.locked { border-color: color-mix(in srgb, var(--cs-red) 45%, transparent); }

.csSlot.active {
    border-color: var(--cs-gold);
    box-shadow: 0 0 0 1px var(--cs-gold-dim), 0 0 22px -6px var(--cs-gold);
}
.csSlot.hovering { border-style: dashed; }

/* The seat you claimed by clicking it.
 *
 * This class was being set and never styled -- there was no way to tell, in
 * rehearsal itself, which slot you had claimed. It is only visible downstream:
 * the payload turns it into localPlayerCellId, and the live page tints that
 * draft row gold and defaults the suggestions panel to that player. So the one
 * page where you set it showed nothing, and a page you were not looking at
 * showed the consequence. Deliberately not gold: gold means on the clock. */
.csSlot.isYou {
    border-color: color-mix(in srgb, var(--cs-gold-lit) 30%, var(--cs-line));
}
.csSlot.isYou::before {
    content: '';
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 2px;
    background: var(--cs-gold-lit);
    opacity: 0.75;
    z-index: 2;
}
.csSlot.isYou .csSlotName { color: var(--cs-gold-lit); }
.csSlot.swapSelected, .csSlot.positionSwapSelected { border-color: var(--cs-gold-lit); }

/* The team-wide highlight during bans: nobody's individual slot is picking,
   so the whole column lights instead of an arbitrary one. */
#myTeamSlots.teamActive, #enemyTeamSlots.teamActive {
    outline: 1px solid var(--cs-gold);
    outline-offset: 4px;
    border-radius: 4px;
}

.csSlotArt { position: absolute; inset: 0; }
.csSlotArt img {
    width: 100%; height: 100%;
    object-fit: cover;
    object-position: 50% 12%;
    filter: saturate(0.9);
}
.csSlot:not(.locked) .csSlotArt img { filter: saturate(0.15) brightness(0.55); }
.csSlotScrim {
    position: absolute; inset: 0;
    background: linear-gradient(180deg, rgba(4,7,12,0.15) 0%, rgba(4,7,12,0.55) 55%, rgba(4,7,12,0.94) 100%);
}

.csSlotRole {
    position: absolute; top: 6px; left: 6px;
    display: flex; align-items: center; gap: 4px;
    font-family: var(--font-mono);
    font-size: 0.52rem; letter-spacing: 0.14em;
    color: var(--cs-text-dim);
    text-shadow: 0 1px 3px #000;
}
.roleIcon { width: 11px; height: 11px; fill: currentColor; }
/* Riot's files are white on transparent. brightness(0) flattens them to black
   so the sepia/saturate chain can tint from a known starting point, which is
   how you colour an <img> you cannot edit. The drawn fallback is an <svg> and
   ignores all of this, inheriting currentColor instead. */
img.roleIcon { opacity: 0.85; filter: brightness(0) invert(1); }
.csRoleTab.active img.roleIcon { opacity: 1; }

.csSlotFoot { position: absolute; left: 0; right: 0; bottom: 0; padding: 7px 8px; }
.csSlotChamp {
    font-family: var(--font-mono);
    font-size: 0.72rem; font-weight: 700;
    color: var(--cs-gold-lit);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.csSlot:not(.locked) .csSlotChamp { color: var(--cs-text-dim); font-weight: 400; font-style: italic; }
.csSlotName {
    font-size: 0.64rem; color: var(--cs-text);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.csSlotTag { color: var(--cs-text-dim); }
.csSlotRank {
    font-family: var(--font-mono);
    font-size: 0.55rem; color: var(--cs-gold); margin-top: 1px;
}

.youBadge, .realDataBadge {
    position: absolute; top: 5px; right: 6px;
    font-family: var(--font-mono); font-size: 0.5rem;
    letter-spacing: 0.1em;
}
.realDataBadge { color: var(--cs-blue); font-size: 0.6rem; }

.csSlotBtns { position: absolute; right: 5px; bottom: 6px; display: flex; gap: 3px; opacity: 0; transition: opacity 0.15s ease; }
.csSlot:hover .csSlotBtns { opacity: 1; }
.swapBtn, .swapPositionBtn {
    border: 1px solid var(--cs-line);
    background: rgba(4,7,12,0.8);
    color: var(--cs-gold);
    border-radius: 3px; cursor: pointer;
    font-size: 0.6rem; line-height: 1; padding: 3px 5px;
}
.swapBtn:hover, .swapPositionBtn:hover { border-color: var(--cs-gold); }

/* --- centre: status, suggestions, picker --- */

.csCenter {
    display: flex; flex-direction: column; min-height: 0;
    /* The grid is capped and centred rather than stretched: on a 1600px screen
       an auto-fill grid runs to 20 columns, which is nothing like the client
       and makes every icon a hunt. */
    --cs-picker-max: 940px;
    padding: 10px 14px 8px;
    border-left: 1px solid var(--cs-line-soft);
    border-right: 1px solid var(--cs-line-soft);
}
.csPickStatus {
    text-align: center;
    font-family: var(--font-mono);
    font-size: 0.72rem; letter-spacing: 0.06em;
    color: var(--cs-gold-lit);
    padding-bottom: 8px;
}
.yourSuggestions {
    display: flex; gap: 6px; justify-content: center; flex-wrap: wrap;
    padding-bottom: 8px;
}

.csPickerHead {
    display: flex; align-items: center; gap: 10px; padding-bottom: 8px;
    width: 100%; max-width: var(--cs-picker-max); margin: 0 auto;
}
.csRoleTabs { display: flex; gap: 2px; flex: 1; flex-wrap: wrap; }
.csRoleTab {
    font-family: var(--font-mono); font-size: 0.58rem;
    letter-spacing: 0.08em;
    padding: 4px 9px;
    border: 1px solid transparent;
    border-bottom-color: var(--cs-line-soft);
    background: none; color: var(--cs-text-dim); cursor: pointer;
    transition: color 0.15s, border-color 0.15s;
}
.csRoleTab:hover { color: var(--cs-text); }
.csRoleTab.active { color: var(--cs-gold-lit); border-bottom-color: var(--cs-gold); }

.searchInputWrap { position: relative; }
.searchInputWrap input {
    width: 150px;
    font-family: var(--font-mono); font-size: 0.66rem;
    padding: 5px 22px 5px 9px;
    background: var(--cs-void);
    border: 1px solid var(--cs-line);
    border-radius: 2px;
    color: var(--cs-text);
}
.searchInputWrap input:focus { outline: none; border-color: var(--cs-gold-dim); }
.searchInputWrap input::placeholder { color: var(--cs-text-dim); }
.searchClearBtn {
    position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
    background: none; border: none; color: var(--cs-text-dim);
    cursor: pointer; font-family: var(--font-mono); font-size: 1rem; line-height: 1; padding: 2px 4px;
}
.searchClearBtn:hover { color: var(--cs-gold); }

.champGridOuter {
    flex: 1; overflow-y: auto; min-height: 0; scrollbar-width: thin;
    width: 100%; max-width: var(--cs-picker-max); margin: 0 auto;
}
.champGrid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(58px, 1fr));
    gap: 5px;
    padding-bottom: 6px;
}
.champCard { cursor: pointer; text-align: center; }
.champCard .champBadge, .champCard img {
    width: 100%; aspect-ratio: 1; display: block;
    border: 1px solid var(--cs-line-soft);
    border-radius: 2px;
    transition: border-color 0.12s ease, filter 0.12s ease;
}
.champCard:hover img { border-color: var(--cs-gold); }
.champCardName {
    font-size: 0.55rem; color: var(--cs-text-dim);
    margin-top: 2px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.champCard.hovered img { border-color: var(--cs-gold-lit); filter: brightness(1.15); }
.champCard.picked, .champCard.banned { pointer-events: none; }
.champCard.picked img { filter: grayscale(1) brightness(0.4); }
.champCard.banned img { filter: grayscale(1) brightness(0.3); }
.champCard.banned { position: relative; }
.champCard.banned::after {
    content: ''; position: absolute; left: 8%; right: 8%; top: 46%;
    height: 1px; background: var(--cs-red); transform: rotate(-45deg);
}

.csCenterFoot {
    display: flex; justify-content: space-between; align-items: center;
    width: 100%; max-width: var(--cs-picker-max); margin: 0 auto;
    padding-top: 6px;
    font-family: var(--font-mono); font-size: 0.55rem;
    color: var(--cs-text-dim);
}
.champRefreshLink { color: var(--cs-gold); text-decoration: none; }
.champRefreshLink:hover { color: var(--cs-gold); }

/* --- ban bar --- */



.banGroup { display: flex; gap: 5px; }
.banGroup.red { justify-content: flex-end; }
.banSlot {
    width: 30px; height: 30px;
    border: 1px solid var(--cs-line-soft);
    border-radius: 2px;
    background: var(--cs-void);
    position: relative;
    overflow: hidden;
}
.banSlot img { width: 100%; height: 100%; display: block; filter: grayscale(1) brightness(0.55); }
.banSlot.filled::after {
    content: ''; position: absolute; left: 3px; right: 3px; top: 50%;
    height: 1px; background: var(--cs-red); transform: rotate(-45deg);
}

/* --- idle veil --- */

.csIdleVeil {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    padding: 24px;
    background: linear-gradient(180deg, rgba(4,7,12,0.82), rgba(4,7,12,0.94));
    backdrop-filter: blur(2px);
    z-index: 3;
}
.csIdleVeil[hidden] { display: none; }
.csIdleCard {
    max-width: 460px; text-align: center;
    border: 1px solid var(--cs-line);
    border-radius: 4px;
    padding: 26px 28px;
    background: linear-gradient(180deg, var(--cs-panel), var(--cs-deep));
}
.csIdleCard h2 {
    font-family: var(--font-head); font-weight: 700;
    font-size: 1.5rem; color: var(--cs-gold); margin: 0 0 10px;
}
.csIdleCard p { font-size: 0.82rem; line-height: 1.55; color: var(--cs-text); margin: 0 0 10px; }
.csIdleSub { color: var(--cs-text-dim) !important; font-size: 0.76rem !important; margin-bottom: 0 !important; }
.csIdleCard strong { color: var(--cs-gold-lit); }

/* ===== Narrow screens ===== */

@media (max-width: 1100px) {
    .csMain { grid-template-columns: 140px minmax(0, 1fr) 140px; }
}
@media (max-width: 780px) {
    .csMain { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr) auto; }
    .csTeam > div { flex-direction: row; height: auto; }
    .csSlot { min-height: 92px; }
    .csCenter { border-left: 0; border-right: 0; border-top: 1px solid var(--cs-line-soft); border-bottom: 1px solid var(--cs-line-soft); }
    .csHeader { grid-template-columns: 1fr; gap: 6px; }
    .csTeamName.red { text-align: left; }
}

/* ===== Champion art progress =====
 *
 * The grid is ~170 images from Riot's CDN. Before this it filled in silently,
 * so a slow connection looked like a broken page rather than a loading one.
 * Driven by --p from the load/error handlers in the page, not by animation:
 * it reports real completions, so it can legitimately stall, and a stall is
 * information. The count is there because a 2px track at 0% looks identical
 * to no bar at all -- which is exactly the moment the user most needs to know
 * something is happening. */
.artLoading {
    width: 100%;
    max-width: var(--cs-picker-max);
    margin: 0 auto 7px;
    /* Above .csIdleVeil (z-index 3). First load is the one time this matters
       most and also the one time the veil is covering the board, so leaving it
       underneath would have hidden it in exactly that case. */
    position: relative;
    z-index: 4;
}
.artLoading[hidden] { display: none; }
.artProgressTrack {
    height: 3px;
    background: var(--cs-line);
    overflow: hidden;
    border-radius: 2px;
}
.artProgressTrack::after {
    content: '';
    display: block;
    height: 100%;
    /* Never a true zero: a sliver reads as "started", nothing reads as broken. */
    width: max(2%, var(--p, 0%));
    background: linear-gradient(90deg, var(--cs-gold-dim), var(--cs-gold));
    transition: width 0.18s ease-out;
}
.artProgressLabel {
    margin-top: 4px;
    font-family: var(--font-mono);
    font-size: 0.55rem;
    letter-spacing: 0.06em;
    color: var(--cs-text-dim);
}

/* ===== Emulator bar controls ===== */

.rhBarStack { flex-direction: column; align-items: flex-start; gap: 5px; }

/* One exclusive choice, so one control. Five separate outlined buttons made
   the reader compare borders to find the live one; a thumb sliding to it
   answers that at a glance. */
.queueSlider {
    position: relative;
    display: flex;
    padding: 2px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--bg);
}
.queueThumb {
    position: absolute;
    top: 2px; left: 0;
    /* Hidden until measured -- see .placed below. */
    opacity: 0;
    height: calc(100% - 4px);
    border-radius: 999px;
    background: color-mix(in srgb, var(--gold) 16%, transparent);
    border: 1px solid var(--gold);
    transition: transform 0.22s cubic-bezier(0.32, 0.72, 0, 1), width 0.22s cubic-bezier(0.32, 0.72, 0, 1);
    pointer-events: none;
}
.queueThumb.placed { opacity: 1; }
.queueOpt {
    position: relative;
    z-index: 1;
    font-family: var(--font-mono);
    font-size: 0.62rem;
    padding: 4px 11px;
    border: 0;
    background: none;
    color: var(--text-muted);
    cursor: pointer;
    border-radius: 999px;
    white-space: nowrap;
    transition: color 0.18s ease;
}
.queueOpt:hover { color: var(--text); }
.queueOpt.active { color: var(--gold); }

.rehearsalBtn.lg {
    padding: 9px 22px;
    font-size: 0.82rem;
    letter-spacing: 0.04em;
    border-radius: 8px;
}

/* Access code: a switch, and a code that appears under it. The old pair was a
   checkbox that set the gate plus a separate readout, so the state lived in two
   places. Fixed widths throughout -- turning it off must not resize the bar. */
.accessState {
    font-family: var(--font-mono);
    font-size: 0.66rem;
    color: var(--text-muted);
    /* Fits "Off" and "On" both, so the row never reflows on toggle. */
    width: 2.1em; text-align: left;
}
.accessCode {
    font-family: var(--font-mono);
    font-size: 0.72rem; font-weight: 700; letter-spacing: 0.18em;
    color: var(--gold);
    cursor: pointer;
    /* Reserved whether or not there is a code to show. */
    width: 7.4em;
}
.accessCode.empty { cursor: default; }

/* ===== Bans, now in the header with their team ===== */

.csSide { display: flex; flex-direction: column; gap: 7px; }
.csSide.red { align-items: flex-end; }
.banGroup { display: flex; gap: 4px; }
.banGroup.red { justify-content: flex-end; }
.banSlot {
    width: 26px; height: 26px;
    border: 1px solid var(--cs-line-soft);
    border-radius: 2px;
    background: var(--cs-void);
    position: relative;
    overflow: hidden;
}
.banSlot img { width: 100%; height: 100%; display: block; filter: grayscale(1) brightness(0.55); }
.banSlot.filled::after {
    content: ''; position: absolute; left: 2px; right: 2px; top: 50%;
    height: 1px; background: var(--cs-red); transform: rotate(-45deg);
}
/* The slot this team's ban is going into. */
.banSlot.next { border-color: var(--cs-gold-dim); }
.banSlot.previewing { border-color: var(--cs-gold); animation: banPulse 1.1s ease-in-out infinite; }
.banSlot.previewing img { filter: grayscale(0.3) brightness(0.9); }
/* A declined ban is not a ban, so it does not get the crossed-out mark -- it
   gets a dash, which is what "nobody was banned here" looks like. */
.banSlot.noBan { display: flex; align-items: center; justify-content: center; }
.banSlot.noBan.filled::after { display: none; }
.banNoneMark { color: var(--cs-text-dim); font-size: 0.95rem; line-height: 1; font-weight: 700; }
@keyframes banPulse {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--cs-gold) 40%, transparent); }
    50%      { box-shadow: 0 0 8px 1px color-mix(in srgb, var(--cs-gold) 32%, transparent); }
}

/* ===== Hover, and the turn after this one ===== */

/* A hovered champion fills the slot the same way a locked one does -- the
   difference is that it breathes, so it reads as a proposal. */
.csSlot.hovering { animation: slotPulse 1.5s ease-in-out infinite; }
.csSlot.hovering .csSlotArt img { filter: saturate(0.75) brightness(0.85); }
.csSlot.hovering .csSlotChamp { color: var(--cs-gold); font-weight: 700; font-style: normal; }
@keyframes slotPulse {
    0%, 100% { border-color: var(--cs-gold-dim); box-shadow: 0 0 0 0 color-mix(in srgb, var(--cs-gold) 35%, transparent); }
    50%      { border-color: var(--cs-gold);     box-shadow: 0 0 18px -4px var(--cs-gold); }
}

/* Two picks in a row for one team: both light, the one on the clock harder,
   so the pair is visible without the second competing with the first. */
.csSlot.onDeck {
    border-color: color-mix(in srgb, var(--cs-gold) 40%, var(--cs-line-soft));
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--cs-gold) 12%, transparent);
}

/* ===== Lock in ===== */


/* ===== Lane tabs and the no-ban card ===== */

.csRoleTab { display: inline-flex; align-items: center; gap: 4px; }
.csRoleTab .roleIcon { width: 12px; height: 12px; fill: currentColor; }
.csRoleTab img.roleIcon { width: 13px; height: 13px; }

.champCard.noChamp {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.noChampMark {
    width: 100%; aspect-ratio: 1;
    display: flex; align-items: center; justify-content: center;
    border: 1px dashed var(--cs-line);
    border-radius: 2px;
    color: var(--cs-text-dim);
    font-size: 1.1rem;
}
/* Mouse hover. Every other card in the grid lights its border on hover, and
   this one did not -- so the only card whose whole purpose is "you may click
   me to decline" was also the only one that gave no sign it was clickable.
   The dashed border also goes solid, since the dash is what reads as
   "nothing here" and the point of the hover is that something IS here. */
.champCard.noChamp:hover .noChampMark {
    border-style: solid;
    border-color: var(--cs-gold);
    color: var(--cs-gold);
    background: rgba(200, 168, 88, 0.08);
}
/* Selected (the draft's own hover state, not the mouse's) stays stronger than
   the mouse hover so the two are never confused. */
.champCard.noChamp.hovered .noChampMark {
    border-style: solid;
    border-color: var(--cs-gold-lit);
    color: var(--cs-gold-lit);
    background: rgba(200, 168, 88, 0.16);
}

/* [hidden] loses to display:flex on specificity, so a filtered-out card would
   stay on screen. It matters now that filtering hides cards instead of
   rebuilding the grid without them. */
.champCard[hidden] { display: none !important; }


/* ===== Lock in =====
 *
 * Fixed over the board, centred, just above the bottom edge -- the commit
 * action for the whole draft, so it does not belong inside a column. Big
 * enough to be the obvious thing to press without hunting for it.
 *
 * Colour follows the phase: red while banning, gold while picking, so which
 * half of the draft you are in is readable without reading. */
.lockInBtn {
    position: fixed;
    left: 50%;
    bottom: 26px;
    transform: translateX(-50%);
    z-index: 6;

    min-width: 230px;
    padding: 14px 40px;
    font-family: var(--font-mono);
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: 0.22em;
    text-transform: uppercase;

    color: var(--cs-void);
    background: linear-gradient(180deg, var(--cs-gold), var(--cs-gold-dim));
    border: 1px solid var(--cs-gold-lit);
    border-radius: 3px;
    cursor: pointer;
    box-shadow: 0 10px 34px -10px rgba(0,0,0,0.85);
    transition: filter 0.14s ease, transform 0.1s ease, opacity 0.16s ease;
}
.lockInBtn[hidden] { display: none; }
.lockInBtn:hover:not(:disabled) { filter: brightness(1.1); }
.lockInBtn:active:not(:disabled) { transform: translateX(-50%) translateY(1px); }

/* Waiting on a selection. Still visible -- it says what phase you are in --
   but plainly not pressable.
 *
 * Deliberately still phase-COLOURED rather than grey. The whole point of the
 * button being red in the ban phase is that you can read which half of the
 * draft you are in without reading words, and the disabled state is when the
 * button is doing nothing BUT saying "BANNING..." -- so greying it out threw
 * the colour away at exactly the moment it was the only thing the button was
 * for. Lightened and desaturated instead: unmistakably not pressable, still
 * unmistakably the ban phase. */
.lockInBtn:disabled {
    color: var(--cs-void);
    /* Opaque. These were rgba() tints, which let the champion grid show
       straight through the button -- and a control you can read the page
       through does not look disabled, it looks unfinished. Same hue, mixed
       against the dark ground ahead of time instead of at composite. */
    background: linear-gradient(180deg, #c2a768, #9c8449);
    border-color: #d0b67a;
    cursor: default;
    box-shadow: none;
}

/* Built from --cs-red (#ef6d75), not from hand-picked hexes. The previous
   values were a brick/brown red that belonged to no other part of this UI and
   read as muddy against the client's blues. */
.lockInBtn.isBan {
    color: var(--cs-void);
    background: linear-gradient(180deg, var(--cs-red), #c04b55);
    border-color: var(--cs-red);
}
/* Waiting on a selection during a ban: lighter, same hue. Still obviously the
   ban phase, still obviously not pressable. */
.lockInBtn.isBan:disabled {
    color: #2a0f12;
    background: linear-gradient(180deg, #e88d94, #cf6f78);
    border-color: #f0a0a6;
}

/* The word stays centred in the button and the ellipsis hangs off its end.
   Left in normal flow, the dots add their width to the label and push the word
   off-centre -- so "Banning..." and "Ban" would sit at different places, and
   the text would visibly shift every time the state changed. Absolutely
   positioning them at the end of the word takes them out of the centring
   calculation entirely while keeping them attached to it. */
.lockInBtn .lockWord { position: relative; display: inline-block; }
.lockInBtn .lockDots { position: absolute; left: 100%; top: 0; }

/* Which side is on the clock, as a shape rather than a word -- it points at
   the team panel whose turn it is. Cheaper to read mid-draft than re-parsing
   the label, and it sits outside the text so the button width is unaffected
   by it appearing and disappearing. */
.lockInBtn::before,
.lockInBtn::after {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    font-size: 0.9em;
    line-height: 1;
    opacity: 0.9;
    pointer-events: none;
}
.lockInBtn[data-side="mine"]::before  { content: '\25C0'; left: 14px; }
.lockInBtn[data-side="enemy"]::after  { content: '\25B6'; right: 14px; }

/* The beat after a click, confirming what happened before the next step. */
.lockInBtn.settled {
    color: var(--cs-void);
    background: var(--cs-gold-lit);
    border-color: #fff;
    opacity: 1;
}
.lockInBtn.isBan.settled {
    color: #fff;
    background: var(--cs-red);
    border-color: #fff;
}

/* Admin-only note that the lane map has fallen behind Data Dragon. */
.laneGap { color: var(--cs-red); }

/* Relay status, on the Live button. The draft runs locally whether or not it
   is reaching the relay, so without this a dead connection is invisible until
   someone opens the watch link and finds an empty page. */
.relayDot {
    display: inline-block;
    width: 7px; height: 7px;
    margin-right: 7px;
    border-radius: 50%;
    background: var(--cs-line);
    vertical-align: middle;
    transition: background-color 0.2s ease;
}
.relayDot[data-state="live"]       { background: #4fbf7a; }
.relayDot[data-state="connecting"] { background: var(--cs-gold); animation: relayPulse 1.1s ease-in-out infinite; }
.relayDot[data-state="down"]       { background: var(--cs-red); }
@keyframes relayPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }

/* An image that gave up after its retries. It holds a transparent pixel, so
   what shows is this: a dim, dashed square that reads as "not loaded" rather
   than the browser's broken-image glyph, which reads as "broken page". */
img.imgFailed {
    border-style: dashed !important;
    border-color: var(--cs-line) !important;
    background: repeating-linear-gradient(
        45deg,
        rgba(255,255,255,0.02) 0 6px,
        rgba(255,255,255,0.04) 6px 12px
    );
}


/* [hidden] must actually hide these.
 *
 * .banSlot img and .csSlotArt img both set display, and a class selector beats
 * the user-agent stylesheet's [hidden] rule -- so setting img.hidden = true in
 * JS changed nothing at all. That only became visible once these elements
 * stopped being rebuilt: reconciliation deliberately keeps an <img>'s src so a
 * decoded image is never thrown away and re-fetched, which means an image that
 * fails to hide keeps showing the LAST champion it held. Restarting a draft
 * left the previous draft's bans sitting in the ban strip.
 *
 * Same class of bug as .champCard[hidden], and the same fix. Anything that is
 * hidden by attribute rather than by being removed needs this. */
.banSlot img[hidden],
.banSlot .banNoneMark[hidden],
.csSlotArt img[hidden],
.csSlot .realDataBadge[hidden],
.csSlotBtns button[hidden],
.csSlotFoot .csSlotRank[hidden] {
    display: none !important;
}
