.gigs-page__header {
  padding: var(--space-4) var(--space-4) var(--space-3);
  max-width: var(--max-width);
  margin: 0 auto;
  width: 100%;
  box-sizing: border-box;
}

.gigs-page__stats-link {
  margin-top: var(--space-2);
}

.gigs-page__stats-link a {
  color: var(--color-text-muted);
  font-family: var(--font-body-light);
  font-size: 0.9rem;
  transition: color 0.4s ease 0.1s;
}

.gigs-page__stats-link a:hover {
  color: var(--color-accent);
  transition-duration: 0.2s;
  transition-delay: 0s;
}

/* Sticky, not fixed — "always visible at the top" while browsing a single (possibly very long,
   e.g. 2005 has 178 shows) year's content, but still part of normal document flow so it doesn't
   need its own collision-avoidance logic the way a page-independent fixed element would (see
   All Songs' jump nav for exactly that problem, solved there because that panel has no natural
   parent in the flow to stick within — this one does: it sits right below the page header,
   inside the normal flow, so position:sticky's own containing-block rules just work).
   top:0 stuck *underneath* the nav bar once .site-nav itself actually became sticky and started
   permanently occupying that space (css/base.css) — previously nav quietly failed to stick at
   all, so top:0 here happened to be correct by accident. --nav-height (js/nav.js, measured live
   off the real element since it differs by breakpoint/device) is what actually keeps this sitting
   just below nav now, rather than needing a hardcoded px value kept in sync with nav's own CSS by
   hand. */
.gigs-timeline {
  position: sticky;
  top: var(--nav-height, 0px);
  z-index: 5;
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  padding: var(--space-3) var(--space-4);
  /* Missing this was the bug: .gigs-page__header and main both cap at var(--max-width) and
     centre via margin:0 auto, but this row never got the same treatment, so on any viewport
     wider than that cap it sat flush against the left edge while the header/title above it
     stayed centred — confirmed directly (header measuring left:135/right:1335 on a 1470px
     viewport, capped at the site's 1200px max-width, versus this row spanning the full
     0-1470 with no cap at all). Its own background/border-bottom staying full-bleed wasn't
     actually buying anything either: --color-bg matches body's own background exactly, so
     capping this element too is visually identical outside the cap, while fixing the
     alignment inside it. overflow-x:auto still scrolls fine within the now-capped box when a
     year has enough shows to need many tabs. */
  max-width: var(--max-width);
  margin: 0 auto;
  width: 100%;
  box-sizing: border-box;
  background: var(--color-bg);
  border-bottom: 1px solid var(--color-border);
  scrollbar-width: thin;
}

.gigs-year-tab {
  flex-shrink: 0;
  position: relative;
  padding: var(--space-1) var(--space-2);
  background: none;
  border: none;
  border-radius: 4px;
  font-family: var(--font-body);
  font-size: 0.95rem;
  color: var(--color-text-muted);
  cursor: pointer;
  white-space: nowrap;
  transform: scale(1);
  transform-origin: center bottom;
  /* cubic-bezier with an overshoot past 1 (the 1.56 term) is what gives the "expands, then
     settles back with a little rubber-band bounce" feel on the way back to scale(1) — a plain
     ease would just shrink smoothly with no springiness, which reads as far less alive. Same
     curve drives the grow-in too, so hovering and un-hovering feel like one consistent motion.
     color gets its own fast-in/slow-delayed-out timing (matching the brand/social links in
     base.css) — snaps to red immediately on hover, but lingers there for a moment after the
     cursor leaves before fading back, rather than blinking off at the same speed it appeared. */
  transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), color 0.4s ease 0.1s;
}

/* (hover: hover) guard, not just a bare :hover — on a touch device, tapping a button can leave
   it "stuck" in :hover state (there's no real pointer to actually leave once the finger lifts),
   so a tapped year stayed magnified at scale(1.35) instead of settling back to its resting size.
   Restricting the whole dock-magnify effect to devices with genuine hover capability means a tap
   just selects the year (.is-active below still applies identically either way) without ever
   entering that stuck-large state. */
@media (hover: hover) {
  .gigs-year-tab:hover {
    color: var(--color-accent);
    transform: scale(1.35);
    z-index: 1;
    transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), color 0.2s ease;
  }

  /* Dock-style falloff — the hovered year's immediate neighbours grow a little too, tapering to
     nothing past that, rather than only the exact button under the cursor reacting. :has() covers
     the "previous sibling" direction (no native prior-sibling selector exists otherwise); the
     adjacent-sibling combinator covers "next". */
  .gigs-year-tab:has(+ .gigs-year-tab:hover),
  .gigs-year-tab:hover + .gigs-year-tab {
    transform: scale(1.15);
  }
}

.gigs-year-tab.is-active {
  color: var(--color-text);
  background: var(--color-border);
  font-weight: 700;
}

main {
  padding-top: var(--space-4);
}

.gigs-empty-message {
  color: var(--color-text-muted);
  font-family: var(--font-body-light);
  text-align: center;
  padding: var(--space-6) var(--space-4);
  line-height: 1.6;
}

.gigs-month {
  font-family: var(--font-heading);
  text-transform: uppercase;
  color: var(--color-text-muted);
  font-size: 1.4rem;
  margin: var(--space-5) 0 var(--space-2);
}

.gigs-year-section > .gigs-month:first-of-type {
  margin-top: 0;
}

.gigs-show-list {
  border-top: 1px solid var(--color-border);
}

/* Scroll-reveal, a vanilla port of reactbits.dev's "Animated List" (confirmed its actual mechanic
   directly from the live component's own inline styles: items outside the viewport sit at
   opacity:0/scale(0.7), animating to opacity:1/scale(1) as they're scrolled into view) — driven
   by js/gigs.js's own IntersectionObserver rather than that component's React state, and a
   one-time reveal (unobserved once shown) rather than the demo's own live re-hide/re-show on
   every scroll, since a real content list you're reading shouldn't keep popping in and out every
   time you scroll back up past something you've already seen. */
.gig {
  border-bottom: 1px solid var(--color-border);
  opacity: 0;
  transform: scale(0.7);
  transition: opacity 0.4s ease, transform 0.4s ease;
}

.gig.is-revealed {
  opacity: 1;
  transform: none;
}

/* align-items:center (not baseline) + the badge living outside .gig__toggle-text's own wrapping
   box is what keeps every badge in one consistent right-hand column regardless of how long a
   given row's date+place label is — previously the badge was just another item in the same
   wrapping flex row as the label, so a long label pushed its badge onto its own line (still
   right-aligned there via margin-left:auto, but no longer at the same vertical position as
   shorter rows' badges sitting flush against their single line of text). Splitting the wrapping
   text into its own flex child means the badge's column position never depends on label length. */
.gig__toggle {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-3) 0;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
  font-family: var(--font-body);
  color: var(--color-text);
  transition: color 0.4s ease 0.1s;
}

.gig__toggle:hover,
.gig__toggle[aria-expanded="true"] {
  color: var(--color-accent);
  transition-duration: 0.2s;
  transition-delay: 0s;
}

.gig__toggle-text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.gig__label {
  font-size: 1.1rem;
}

/* Only rendered at all when two different shows would otherwise share an identical row label
   (same date, venue, city, and country — e.g. a matinee/evening pair at the same venue) — most
   rows never get one, keeping "date + place" the only thing on the row per the stated priority. */
.gig__hint {
  font-family: var(--font-body-light);
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

.gig__badge {
  flex-shrink: 0;
  font-family: var(--font-body-light);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.15rem 0.5rem;
  border-radius: 3px;
}

.gig__badge--upcoming {
  color: var(--color-accent);
  border: 1px solid var(--color-accent);
}

.gig__badge--soldout {
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
}

.gig__toggle::after {
  content: '';
  display: inline-block;
  width: 0.85em;
  height: 0.85em;
  flex-shrink: 0;
  margin-left: auto;
  background-color: var(--color-text-muted);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  transition: transform 0.25s ease;
}

.gig__toggle[aria-expanded="true"]::after {
  transform: rotate(180deg);
}

.gig__body {
  padding: 0 0 var(--space-4) 0;
  max-width: 700px;
}

.gig__body[hidden] {
  display: none;
}

.gig__meta {
  margin-bottom: var(--space-3);
}

.gig__meta p {
  margin: 0 0 var(--space-1);
}

.gig__region {
  font-size: 1rem;
}

.gig__tour,
.gig__info {
  font-family: var(--font-body-light);
  font-size: 0.9rem;
  color: var(--color-text-muted);
}

.gig__tickets-btn {
  display: inline-block;
  padding: 0.5rem 1.25rem;
  border: 1px solid var(--color-accent);
  border-radius: 6px;
  color: var(--color-accent);
  font-family: var(--font-body);
  font-size: 0.9rem;
  margin-bottom: var(--space-3);
  transition: background 0.2s ease, color 0.4s ease 0.1s;
}

.gig__tickets-btn:hover {
  background: var(--color-accent);
  color: #fff;
  transition-duration: 0.2s;
  transition-delay: 0s;
}

/* Same box shape as .gig__tickets-btn (padding, border-radius, font-size) rather than a plain
   caption — reads as "the tickets button, now disabled" per the user's own wording, not an
   unrelated status line. */
.gig__soldout-label {
  display: inline-block;
  padding: 0.5rem 1.25rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  color: var(--color-text-muted);
  font-family: var(--font-body);
  font-size: 0.9rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: var(--space-3);
}

/* Deliberately the most de-emphasised block in the drawer — the setlist is real, useful data
   when present, but with 260 of 1,178 shows missing one entirely, it must never read as "the
   point" of this page the way it would on the Lyrics side. Smaller text, muted label, and a
   plain factual note (not an apology or a call-to-action) when there's nothing to show. */
.gig__setlist {
  padding-top: var(--space-2);
}

.gig__setlist-label {
  font-family: var(--font-body-light);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-2);
}

.gig__set-label {
  font-family: var(--font-body-light);
  font-size: 0.8rem;
  color: var(--color-text-muted);
  margin: var(--space-2) 0 var(--space-1);
}

.gig__setlist-set {
  margin: 0;
  padding-left: 1.4rem;
  font-size: 0.95rem;
}

.gig__setlist-set li {
  margin-bottom: 0.2rem;
}

.gig__song-note {
  color: var(--color-text-muted);
  font-size: 0.85rem;
}

.gig__setlist-empty {
  font-family: var(--font-body-light);
  font-size: 0.9rem;
  color: var(--color-text-muted);
  font-style: italic;
  margin: 0;
}

.gig__attribution {
  margin: var(--space-3) 0 0;
  font-size: 0.8rem;
}

.gig__attribution a {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  color: var(--color-text-muted);
  transition: color 0.4s ease 0.1s;
}

.gig__attribution-logo {
  height: 1.1em;
  width: auto;
  display: inline-block;
}

.gig__attribution a:hover {
  color: var(--color-accent);
  transition-duration: 0.2s;
  transition-delay: 0s;
}

