* { box-sizing: border-box; }
body {
  font-family: system-ui, sans-serif;
  max-width: 1100px;
  margin: 0 auto;
  padding: 16px;
  background: #111;
  color: #eee;
}
.countdown {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  font-size: 13px; font-weight: bold; letter-spacing: .02em;
  color: #2ecc71; background: #16241c; border: 1px solid #1f3327;
  border-radius: 8px; padding: 8px 12px; margin-bottom: 6px;
}
.countdownRight { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
#countdownText { white-space: nowrap; }
.pageTitle { margin-bottom: 4px; font-size: 20px; color: #ddd; }

/* No box/background of its own — it sits directly on the page, unlike the
   green countdown bar above it, and is collapsed behind the header's
   calendar icon by default (see #calendarToggleBtn in app.js) rather than
   permanently taking up header space. */
#calendarSection { margin-top: 14px; }
.calendarSectionTitle { text-align: center; margin: 0 0 10px; font-size: 15px; }
.calendarHint { font-size: 11px; color: #888; margin: 6px 0 0; text-align: center; }

.calendarToggleIconBtn {
  display: flex; align-items: center; justify-content: center;
  width: 34px; margin: -8px 0; padding: 0;
  background: none; border: 1px solid #2a3f31; border-radius: 6px; color: #7fae8c;
}
.calendarToggleIconBtn svg { width: 18px; height: 18px; }
.calendarToggleIconBtn:hover,
.calendarToggleIconBtn.calendarToggleIconBtn--open { background: #1c2b21; }

/* Narrow screens: the days-left figure is the least essential piece, so it
   goes first; the calendar icon stays since it's the only way in to the
   rehearsal widget. */
@media (max-width: 480px) {
  #countdownText { display: none; }
}

/* One fixed-width strip, today through the concert. When it all fits the
   header's width it just hugs the right edge (calendarStrip--fit); when it
   doesn't, it scrolls (calendarStrip--scroll) with the finish cell pinned
   via position:sticky and a fading zone just before it — see
   renderCalendarStrip()/updateCalendarStripFade() in app.js.

   The scrollbar is flipped to sit ABOVE the strip instead of below: the
   container and each of its direct children are rotated 180° around the
   X axis, which visually relocates the (horizontal) scrollbar to the top
   edge while the double rotation on the children cancels back out to
   normal, right-side-up rendering. Purely a rendering trick — scroll
   direction and sticky positioning behave exactly as normal underneath it. */
.calendarStrip {
  display: flex; gap: 4px; overflow-x: auto; overflow-y: hidden;
  scrollbar-width: thin; scrollbar-color: #2a3f31 transparent;
  transform: rotateX(180deg);
  touch-action: pan-y; /* horizontal drag is handled in JS; let vertical touch scroll the page */
}
.calendarStrip--scroll { cursor: grab; }
.calendarStrip--scroll:active { cursor: grabbing; }
.calendarStrip > * { transform: rotateX(180deg); }
.calendarStrip::-webkit-scrollbar { height: 4px; }
.calendarStrip::-webkit-scrollbar-track { background: transparent; }
.calendarStrip::-webkit-scrollbar-thumb { background: #2a3f31; border-radius: 2px; }
.calendarStrip::-webkit-scrollbar-thumb:hover { background: #3a5443; }
.calendarStrip.calendarStrip--fit { justify-content: flex-end; }

.calStripDay {
  flex: 0 0 42px; width: 42px; height: 48px; padding: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  background: #1c1c1c; border: 1px solid #2a2a2a; border-radius: 6px;
  color: #ccc; font-size: 12px; position: relative;
}
/* A real flex item marking the week boundary — see CAL_WEEK_DIVIDER_HTML in
   app.js for why this isn't a pseudo-element line anymore. The container's
   own `gap` (4px) already sits on both sides of it for free, so it's
   centered by construction rather than by computed offsets. */
.calWeekDivider {
  flex: 0 0 1px; width: 1px; align-self: stretch;
  margin: 5px 0; background: #555;
}
.calStripWeekday { font-size: 9px; color: #789; text-transform: uppercase; line-height: 1; }
.calStripNum { font-variant-numeric: tabular-nums; font-size: 14px; line-height: 1.4; }
.calStripCount { position: absolute; bottom: 2px; right: 3px; font-size: 8px; color: #eee; font-weight: bold; }
.calStripDay.calMine { border-color: #e74c3c; }
.calStripDay.calFinish {
  background: linear-gradient(160deg, #289e5c, #185f38);
  border: none; font-weight: bold;
  box-shadow: 0 0 8px rgba(46, 204, 113, .35);
}
.calStripDay.calFinish .calStripNum,
.calStripDay.calFinish .calStripWeekday { color: #08170f; }
.calendarStrip--scroll .calStripDay:last-child {
  position: sticky; right: 0; z-index: 1;
}

/* A zero-width sticky anchor pinned right where the finish cell begins,
   with a gradient bleeding leftward from it — days scrolling underneath
   fade smoothly as they approach the pinned edge instead of snapping away
   the instant they're covered. Zero width means it adds no extra scroll
   distance of its own. The left edge mirrors this with an actual
   (non-zero) reserved cell instead, since there's no pinned element to
   anchor against there — see #calStripFadeLeft.

   right:38px (not 42, the finish cell's exact width) is deliberate — sticky
   offsets aren't affected by the flex `gap`, so lining this up flush with
   the finish cell's edge left a hairline sliver where a scrolling day (and
   its red "mine" outline) could still peek through. A few px of overlap
   under the opaque finish cell closes that for good. */
.calStripFade {
  flex: 0 0 0; width: 0; position: sticky; right: 38px; z-index: 1;
  pointer-events: none; transition: opacity .2s;
}
.calendarStrip--fit .calStripFade { display: none; }
.calStripFade::before {
  content: ''; position: absolute; top: 0; bottom: 0; right: 0; width: 62px;
  background: linear-gradient(to right, rgba(17, 17, 17, 0) 0%, rgba(17, 17, 17, .5) 32%, rgba(17, 17, 17, 1) 100%);
}

/* Reserves a little breathing room before today's cell so it doesn't sit
   flush against the left fade — without this, today would look clipped
   the moment the page loads even though nothing is actually scrolled away
   yet. Fades out days as they scroll past the left edge the same way the
   right side does, just without anything pinned to anchor against. */
.calStripFadeLeft {
  flex: 0 0 14px; width: 14px; position: sticky; left: 0; z-index: 1;
  pointer-events: none;
  background: linear-gradient(to left, rgba(17, 17, 17, 0) 0%, rgba(17, 17, 17, 1) 100%);
}
.calendarStrip--fit .calStripFadeLeft { display: none; }

.calendarToggle {
  display: block; width: auto; margin-top: 10px; padding: 5px 10px;
  background: none; border: 1px solid #333; color: #888; font-size: 11px;
}

.tournamentStartBig {
  display: block; width: 100%; margin-bottom: 16px; padding: 16px;
  background: #1c1c1c; color: #2ecc71; border: 1px solid #2a3f31; border-radius: 10px;
  font-size: 15px; font-weight: bold; letter-spacing: .01em;
}
.tournamentStartBig:hover { background: #222; }
.tournamentStartBtnCounter { display: block; font-size: 11px; font-weight: normal; opacity: .85; margin-top: 3px; }

/* max-width alone only caps the box's width — without its own auto margins
   it still sits flush against the page's left edge inside the wider body,
   which is why the whole section (title, cards, final list) read as
   left-shoved instead of centered on anything wider than ~480px. */
#tournamentSection { max-width: 480px; margin: 0 auto; }
#tournamentSection h2 { display: flex; align-items: baseline; justify-content: center; gap: 8px; flex-wrap: wrap; }

/* The header's right corner doubles as this page's exit control while a
   tournament is active — see renderTournamentState() in app.js, which swaps
   in this button in place of the days-left text and calendar icon instead
   of keeping a separate exit button on the page itself. */
.countdownExitBtn {
  background: none; border: 1px solid #2a3f31; color: #7fae8c;
  font-size: 12px; font-weight: bold; padding: 3px 10px; border-radius: 6px;
}
#tournamentStatus { font-size: 13px; font-weight: normal; }
.tournamentPair { display: flex; align-items: center; gap: 12px; margin-top: 10px; }
.tournamentCard {
  flex: 1; padding: 24px 12px; text-align: center;
  background: #1a1a1a; border: 1px solid #333; border-radius: 12px; color: #eee;
}
.tournamentCard:hover { border-color: #2ecc71; background: #16241c; }
.tournamentTitle { font-size: 15px; font-weight: bold; margin-bottom: 6px; }
.tournamentDur { font-size: 13px; color: #999; font-variant-numeric: tabular-nums; }
.tournamentVs { font-size: 12px; color: #666; flex-shrink: 0; }
.tournamentRank { width: 22px; color: #888; font-variant-numeric: tabular-nums; flex-shrink: 0; }
.tournamentDone { width: 100%; text-align: center; padding: 16px 0; color: #ccc; font-size: 14px; }
.tournamentDone p { margin: 0 0 10px; }

.tournamentGrid {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-top: 10px;
}
.tournamentGrid .tournamentCard { padding: 16px 10px; cursor: pointer; }
.tournamentWorseWord { color: #e74c3c; }
/* Elimination screen: hovering a still-active card is picking it as the
   WORSE of the group, so the cue is red and pulses instead of the plain
   green "this is the good choice" hover used on the merge screen. */
.tournamentGrid .tournamentCard:not(.tournamentCardOut):hover {
  border-color: #e74c3c;
  background: #2a1414;
  animation: tournamentWorseHoverPulse 0.7s ease-in-out infinite;
}
@keyframes tournamentWorseHoverPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(231, 76, 60, 0); }
  50% { box-shadow: 0 0 12px 2px rgba(231, 76, 60, 0.65); }
}
.tournamentCardOut {
  position: relative; opacity: 0.45; background: #141414; border-color: #262626; cursor: pointer;
}
.tournamentCardOut:hover { border-color: #555; background: #1a1a1a; opacity: 0.7; }
.tournamentCardOut .tournamentTitle { font-weight: normal; }
.tournamentRankBadge {
  position: absolute; top: 6px; right: 8px; font-size: 12px; color: #888;
  font-variant-numeric: tabular-nums;
}
.tournamentRestoreHint { font-size: 11px; margin: 14px 0 0; }
.tournamentFinal {
  list-style: none; margin: 10px auto 0; padding: 0; max-width: 360px;
  display: flex; flex-direction: column; gap: 2px; text-align: left;
}
.tournamentPoints { color: #2ecc71; font-variant-numeric: tabular-nums; font-size: 12px; font-weight: bold; flex-shrink: 0; }
/* Extra context (original likes, % of max possible points) — always its own
   line under the title rather than fighting for space in an already-full
   row of rank/title/points/duration, which made the wrap behavior fiddly
   to get right without being able to verify it pixel-by-pixel. Indented to
   line up under the title, not the rank number. */
.tournamentFinal .songRow { flex-wrap: wrap; row-gap: 1px; padding-bottom: 4px; }
.tournamentExtra { flex: 1 0 100%; font-size: 11px; color: #777; padding-left: 34px; }

.tournamentCard.tournamentWinner {
  border-color: #f1c40f;
  background: linear-gradient(100deg, #1a1a1a 0%, #f1c40f 45%, #1a1a1a 90%);
  background-size: 250% 100%;
  animation: tournamentWinnerSweep 0.5s ease-out;
}
@keyframes tournamentWinnerSweep {
  from { background-position: 130% 0; }
  to { background-position: -30% 0; }
}

/* The calendar widget being collapsed by default means there's usually
   nothing between the header and this — the generic `section{margin-top:
   24px}` below was sized for when the day strip filled that gap, and reads
   as an oversized empty space now that it usually doesn't. Halved, and
   zeroed on the columns themselves so it isn't applied twice. */
.columns { display: flex; gap: 32px; align-items: flex-start; flex-wrap: wrap; margin-top: 12px; }
.columns > section { margin-top: 0; }
#leftCol { flex: 1 1 420px; min-width: 320px; }
#rightCol {
  flex: 1 1 340px; min-width: 300px;
  position: sticky; top: 16px;
  background: #1a1a1a; border-radius: 12px; padding: 16px;
  display: flex; flex-direction: column;
}

.whoAmI { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: normal; color: #9fcbab; }
/* Negative vertical margin cancels .countdown's own 8px padding so the icon
   spans the bar's full height edge-to-edge instead of sitting shrunk inside
   it — a plain rectangle-and-arrow "log out" glyph reads better as a tall,
   obvious tap target than the old text button did. */
#logoutBtn {
  display: flex; align-items: center; justify-content: center;
  width: 30px; margin: -8px 0; padding: 0;
  background: none; border: 1px solid #2a3f31; border-radius: 6px; color: #7fae8c;
}
#logoutBtn svg { width: 16px; height: 16px; transform: scaleX(-1); }
#logoutBtn:hover { background: #1c2b21; }

/* Half as thin as a plain progress bar would be — it's a secondary cue,
   the time is already spelled out in #budgetLabel right above it. */
#budgetBar { background: #333; border-radius: 4px; height: 7px; overflow: hidden; margin-top: 4px; }
#budgetFill { height: 100%; width: 0%; background: #2ecc71; transition: width .3s; }
#budgetLabel { font-variant-numeric: tabular-nums; color: #aaa; font-size: 14px; font-weight: normal; }

.setlistHeader { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; border-bottom: 1px solid #333; padding-bottom: 4px; }
.setlistHeader h2 { border: none; padding-bottom: 0; }

section { margin-top: 24px; }
h2 { font-size: 16px; color: #ccc; border-bottom: 1px solid #333; padding-bottom: 4px; }
h3 { font-size: 13px; color: #888; margin: 8px 0 6px; text-transform: uppercase; letter-spacing: .03em; }

.candidateDivider { border: none; border-top: 1px dashed #444; margin: 18px 0 0; }

.candidatesHeader { display: flex; align-items: center; justify-content: space-between; }
.candidatesHeader h3 { margin: 8px 0 6px; }
.candidatesToggleBtn {
  width: auto; padding: 2px 8px; margin: 4px 0 0;
  background: none; border: 1px solid #333; color: #888; font-size: 12px;
}

.leftColHeader { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.leftColHeader h2 { flex: 1 1 auto; }
#participationCounter { font-size: 13px; }
#participationCounter.done { color: #2ecc71; font-weight: bold; }

/* The setlist and candidates lists scroll on their own instead of growing
   the whole page — matters most on mobile, where #rightCol moves to the
   top (below) and an unbounded list would push the search box way down.
   The scrollbar itself is styled to melt into #rightCol's background
   (#1a1a1a) rather than the harsh default bar, with a little breathing
   room so it doesn't sit flush against the duration text. */
#setlistList, #candidatesList {
  max-height: 38vh;
  overflow-y: auto;
  padding-right: 8px;
  scrollbar-width: thin;
  scrollbar-color: #3a3a3a transparent;
}
#candidatesList { max-height: 22vh; }
#setlistList::-webkit-scrollbar, #candidatesList::-webkit-scrollbar { width: 6px; }
#setlistList::-webkit-scrollbar-track, #candidatesList::-webkit-scrollbar-track { background: transparent; }
#setlistList::-webkit-scrollbar-thumb, #candidatesList::-webkit-scrollbar-thumb {
  background: #3a3a3a; border-radius: 3px;
}
#setlistList::-webkit-scrollbar-thumb:hover, #candidatesList::-webkit-scrollbar-thumb:hover { background: #4a4a4a; }

/* Once locked in, there's no catalog to browse anymore — the setlist (now
   numbered) and its candidates are the whole page, so they grow with it
   and the page itself scrolls, instead of being boxed into their own
   small scrolling panes. */
.restrictedView #setlistList, .restrictedView #candidatesList {
  max-height: none; overflow-y: visible; padding-right: 0;
}
#restrictedSortSelect { width: auto; padding: 3px 6px; font-size: 12px; }

@media (max-width: 760px) {
  #rightCol { position: static; }
  /* Short enough that a cut-off row is obviously a row, not the end of the
     list — the previous 32vh comfortably fit ~5 rows with nothing implying
     there was more below. */
  #setlistList { max-height: 22vh; }
  #candidatesList { max-height: 18vh; }
  /* The rotateX flip that moves the scrollbar to the top is a desktop-only
     nicety — on a touchscreen there's no persistent scrollbar to reposition
     in the first place, and the 3D transform was breaking native touch-drag
     scrolling outright on real phones. Reset it (both the container and the
     per-child counter-rotation) so this is a plain, ordinary horizontally
     scrolling strip with normal touch behavior. */
  .calendarStrip { transform: none; padding-bottom: 0; touch-action: pan-x pan-y; }
  .calendarStrip > * { transform: none; }
}

ol, ul { list-style: none; padding: 0; margin: 0; }
li { padding: 6px 0; border-bottom: 1px solid #222; }

.songRow { display: flex; align-items: center; gap: 12px; }
.songRow .title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.songNum { color: #666; font-variant-numeric: tabular-nums; font-size: 12px; flex-shrink: 0; width: 16px; text-align: right; }
.titleLink { color: inherit; text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 2px; cursor: pointer; }
.titleLink:hover { color: #e74c3c; }
.dur { color: #999; font-variant-numeric: tabular-nums; font-size: 13px; flex-shrink: 0; }
.durLink { text-decoration: underline; text-decoration-style: dotted; cursor: pointer; }
.durLink:hover { color: #ccc; }
.muted { color: #666; border: none; }

.heartBtn {
  display: flex; align-items: center; gap: 5px;
  background: none; border: none; padding: 4px 6px;
  color: #555; font-size: 19px; cursor: pointer; flex-shrink: 0;
}
.heartBtn .heartCount { font-size: 13px; font-variant-numeric: tabular-nums; color: #888; }
.heartBtn.active { color: #e74c3c; }
.heartBtn.active .heartCount { color: #e74c3c; }
.heartBtn.big { font-size: 30px; }
.heartBtn.big .heartCount { font-size: 16px; }

#filterInput { width: 100%; padding: 8px 10px; font-size: 13px; }
.listControls { display: flex; align-items: center; gap: 8px; margin-top: 8px; flex-wrap: wrap; }
#sortSelect { padding: 5px 6px; font-size: 12px; flex: 1 1 auto; min-width: 0; }
.songGroup { margin-bottom: 4px; }

.addSongBtn {
  width: auto; padding: 5px 10px; font-size: 12px; flex-shrink: 0;
  background: #2ecc71; color: #111; border: none; font-weight: bold;
}

/* A checkbox styled as a compact "только ♥" pill instead of a plain
   checkbox+label pair — outlined when off, red when on. */
.onlyLikedToggle {
  display: flex; align-items: center; justify-content: center; gap: 4px;
  width: auto; height: 30px; flex-shrink: 0; padding: 0 10px;
  border: 1px solid #444; border-radius: 6px; font-size: 12px; color: #999;
  cursor: pointer; position: relative; white-space: nowrap;
}
.onlyLikedToggle input { position: absolute; inset: 0; opacity: 0; cursor: pointer; margin: 0; }
.onlyLikedToggle:has(input:checked) { color: #e74c3c; border-color: #e74c3c; }

/* Modals default to hidden — JS flips display to "flex" to open one.
   z-index matters here: #rightCol uses position:sticky (its own stacking
   context), and without an explicit z-index a later-in-DOM positioned
   sibling can paint over an earlier fixed one regardless of "on top" intent. */
.modal {
  display: none;
  position: fixed; inset: 0; background: rgba(0,0,0,.8);
  align-items: center; justify-content: center;
  z-index: 1000;
}
.modal-box {
  background: #1c1c1c; padding: 24px; border-radius: 12px; width: 300px;
}
.modal-box input, .modal-box select { width: 100%; padding: 8px; margin: 8px 0; }
.modal-box input[readonly] { background: transparent; border-color: transparent; color: #ddd; font-weight: bold; padding-left: 0; }
.modal-box button { width: 100%; padding: 10px; background: #2ecc71; border: none; border-radius: 6px; font-weight: bold; }
.modal-box button.secondary { background: #333; color: #ccc; margin-top: 8px; }
.modal-box button.secondary-green { background: transparent; color: #2ecc71; border: 1px solid #2ecc71; margin-top: 8px; }
.modal-box p { font-size: 13px; line-height: 1.4; color: #ccc; }
.modal-box .sourceLink { color: #6cb4ff; }
.error { color: #e74c3c; font-size: 13px; margin-top: 8px; min-height: 16px; }

/* ".modal-box button" (class+tag) otherwise outranks a single-class
   selector like ".iconBtn" or ".suggestItem" in specificity, which is why
   these need the ".modal-box X" form too — not just their own class. */
.sourceRow { display: flex; gap: 6px; align-items: center; margin: 8px 0; }
.sourceRow input { margin: 0; flex: 1; }
.modal-box .iconBtn {
  width: auto; flex: 0 0 auto; padding: 8px 14px;
  background: #333; color: #ccc; font-weight: normal;
}

#songModalSuggestions:empty { display: none; }
#songModalSuggestions { margin: -4px 0 8px; }
.suggestLabel { font-size: 12px; color: #888; margin-bottom: 4px; }
.modal-box .suggestItem {
  display: block; width: 100%; text-align: left; margin: 2px 0; padding: 6px 8px;
  background: #2a2a2a; border: 1px solid #3a3a3a; color: #ccc; font-size: 13px; font-weight: normal;
}
.modal-box .suggestItem:hover { background: #333; }

input, select, button {
  background: #222; color: #eee; border: 1px solid #444; border-radius: 6px;
}
button { cursor: pointer; }

/* Long catalog list on mobile means the confirm button (top of #rightCol)
   and the search box (top of #leftCol) can both scroll far out of view —
   these jump straight to one or the other instead of requiring a manual
   scroll back. Shown only once there's actually somewhere to jump from —
   see the scroll listener in app.js. */
.floatingScrollBtn {
  position: fixed; left: 50%; transform: translateX(-50%);
  width: 44px; height: 44px; border-radius: 50%; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: #1c2b21; border: 1px solid #2a3f31; color: #2ecc71;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .4); z-index: 500;
}
.floatingScrollBtn svg { width: 22px; height: 22px; }
.floatingScrollBtn--bottom { bottom: 16px; }
.floatingScrollBtn--top { top: 16px; }
