@layer components {
  .league-group {
    margin-bottom: 12px;
    background: var(--color-bg);
    border-radius: var(--radius-lg);
    overflow: hidden;

    /* Off-screen groups skip layout/paint until scrolled into view.
     * Intrinsic-size = league-header (44) + ~8 cards (48 each) ≈ 420px.
     * Cuts INP cost on swipe between days with 8+ groups loaded. */
    content-visibility: auto;
    contain-intrinsic-size: 0 420px;
  }

  .league-group__header {
    display: flex;
    align-items: center;
    padding: 7px 16px 0;
  }

  .league-group__info {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1;
    min-width: 0;
    color: inherit;
    text-decoration: none;
  }

  .league-group__logo {
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    object-fit: contain;
    border-radius: 6px;
  }

  .league-group__text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    gap: 1px;
  }

  .league-group__name {
    font-family: var(--font-title);
    font-size: 13px;
    font-weight: var(--weight-semibold);
    color: var(--color-text);
    text-transform: uppercase;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  a.league-group__info:hover .league-group__name {
    text-decoration: underline;
  }

  .league-group__country {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    color: var(--color-text-muted);
    font-weight: var(--weight-normal);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 1.2;
  }

  .league-group__country-flag {
    width: 14px;
    height: 10px;
    object-fit: cover;
    border-radius: 1px;
    flex-shrink: 0;
  }

  /* Emoji variant — used when the data layer ships a unicode glyph
     instead of a CDN URL (World Cup / supranationals where no flag
     image exists). Auto-sized so the emoji isn't squashed to 14×10
     px, with a tweaked font-size that matches the surrounding country
     text height. */
  .league-group__country-flag--emoji {
    width: auto;
    height: auto;
    font-size: 13px;
    line-height: 1;
    border-radius: 0;
    object-fit: initial;
  }

  /* Star sits right after the text block */
  .league-group__star {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
    margin-left: 6px;
  }

  .league-group__star svg {
    width: 18px;
    height: 18px;
    stroke: var(--color-text-muted);
  }

  /* Chevron pushed to far right */
  .league-group__toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
    margin-left: auto;
  }

  .league-group__toggle svg {
    width: 20px;
    height: 20px;
    stroke: var(--color-text-muted);
    transition: transform var(--duration-fast) var(--easing-default);
  }

  .league-group__matches {
    padding: 5px 16px;
  }

  /* Wrapper around a group's header + match cards. display:contents keeps
   * it layout-invisible by default — the cards lay out exactly like before
   * the wrapper existed. Toggling .is-hidden on this wrapper collapses the
   * whole group (header included) when the live or quick filter empties it,
   * so we don't get a stray "Grupo B" sitting on top of zero cards. */
  .league-group__group {
    display: contents;
  }

  .league-group__group.is-hidden {
    display: none;
  }

  /* Smaller sub-header used inside a stage when several groups share
   * the same day (e.g. WC group stage matchday 1 across all 12 groups).
   * Visible enough to anchor each bucket — text-secondary, not muted —
   * but still smaller weight than the league header. */
  .league-group__group-header {
    margin: 6px 0 2px;
    font-family: var(--font-body);
    font-size: 11px;
    font-weight: var(--weight-semibold);
    color: var(--color-text-secondary);
  }

  .league-group__matches > .league-group__group-header:first-child {
    margin-top: 0;
  }

  /* Collapsed state */
  .league-group--collapsed .league-group__toggle svg {
    transform: rotate(-90deg);
  }

  .league-group--collapsed .league-group__matches {
    display: none;
  }

  /* Following pseudo-group: synthetic group at the top with followed-team matches */
  .league-group--following .league-group__logo--following {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    color: var(--color-accent-fg);
    background: none;
    border-radius: 0;
  }

  .league-group--following .league-group__logo--following svg {
    width: 18px;
    height: 18px;
  }

  /* Followed leagues: star filled in accent color */
  .league-group--followed .league-group__star svg {
    stroke: var(--color-accent);
    fill: var(--color-accent);
  }

  /* Time-sort flat list (no group headers) */
  .time-sort {
    display: block;
    padding: 8px 16px 0;
  }

  /* Match-list root: declare touch-action so the browser knows it can do
   * native vertical scroll without consulting JS. The swipe handler in
   * match_list_controller still works because we manually check dx > dy. */
  [data-controller~="match-list"] {
    touch-action: pan-y pinch-zoom;
  }

  /* Error state — surfaced when the matches API or the demo JSON fail
   * to load. Visual cue ported from v1: a red card swaying back and
   * forth like the ref just lifted it. The retry button calls back
   * into the controller (match-list#retry) so the user can try again
   * without a hard reload. Reduced motion drops the animation but
   * keeps the card. */
  .match-list-error {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: var(--space-12) var(--space-6) var(--space-8);
    color: var(--color-text-muted);
    gap: var(--space-3);
  }

  .match-list-error__icon {
    width: 84px;
    height: 84px;
    margin-bottom: var(--space-2);
  }

  .match-list-error__card {
    transform-origin: center bottom;
    animation: match-list-error-card-wave 2s ease-in-out infinite;
  }

  @keyframes match-list-error-card-wave {
    0%,
    100% {
      transform: rotate(-5deg);
    }

    50% {
      transform: rotate(5deg);
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .match-list-error__card {
      animation: none;
    }
  }

  .match-list-error__title {
    margin: 0;
    font-family: var(--font-title);
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--color-text);
  }

  .match-list-error__subtitle {
    margin: 0;
    font-size: var(--text-sm);
    max-width: 32ch;
    line-height: 1.45;
  }

  .match-list-error__retry {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-top: var(--space-3);
    padding: 10px 18px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    color: var(--color-text);
    font-family: var(--font-body);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    cursor: pointer;
    transition:
      background var(--duration-fast) var(--easing-default),
      border-color var(--duration-fast) var(--easing-default);
  }

  .match-list-error__retry:hover {
    background: var(--color-surface-hover, var(--color-surface));
    border-color: var(--color-accent);
  }

  .match-list-error__retry:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
  }

  /* Live event highlight on a match card — fired when poll detects a
   * goal or red card. Companion to the Toast: the toast tells you
   * "something happened, click to scroll", the glow tells your
   * peripheral vision "it happened on this card right here". One-shot,
   * cleaned up by JS on `animationend`. Reduced-motion bypasses the
   * effect entirely. */
  @keyframes match-card-event-goal {
    0% {
      box-shadow: 0 0 0 0 color-mix(in oklab, var(--color-accent) 0%, transparent);
    }

    25% {
      box-shadow:
        0 0 0 3px color-mix(in oklab, var(--color-accent) 45%, transparent),
        0 0 22px 4px color-mix(in oklab, var(--color-accent) 30%, transparent);
    }

    100% {
      box-shadow: 0 0 0 0 color-mix(in oklab, var(--color-accent) 0%, transparent);
    }
  }

  @keyframes match-card-event-red-card {
    0% {
      box-shadow: 0 0 0 0 color-mix(in oklab, var(--color-live) 0%, transparent);
    }

    25% {
      box-shadow:
        0 0 0 3px color-mix(in oklab, var(--color-live) 45%, transparent),
        0 0 22px 4px color-mix(in oklab, var(--color-live) 30%, transparent);
    }

    100% {
      box-shadow: 0 0 0 0 color-mix(in oklab, var(--color-live) 0%, transparent);
    }
  }

  .match-card.event-goal {
    animation: match-card-event-goal 1.6s ease-out;
    border-radius: var(--radius-md);
  }

  .match-card.event-red-card {
    animation: match-card-event-red-card 1.6s ease-out;
    border-radius: var(--radius-md);
  }

  @media (prefers-reduced-motion: reduce) {
    .match-card.event-goal,
    .match-card.event-red-card {
      animation: none;
    }
  }

  /* Empty state — shown when the day has zero matches.
   * Copy is rendered by match_list_controller's #renderEmptyState (today
   * vs other-day variants), markup is consistent across both. */
  .match-list-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: var(--space-12) var(--space-6) var(--space-8);
    color: var(--color-text-muted);
    gap: var(--space-3);
  }

  /* `display: flex` above overrides the default `display: none` that
   * the user-agent stylesheet attaches to `[hidden]`, so flipping the
   * attribute alone wouldn't hide the element. Pin display:none here
   * so the controller can toggle the live-filter empty via [hidden].
   * (Same gotcha as feedback_hidden_attribute_display_override.) */
  .match-list-empty[hidden] {
    display: none;
  }

  .match-list-empty__icon {
    color: var(--color-text-subtle, var(--color-text-muted));
    opacity: 0.6;
  }

  .match-list-empty__title {
    margin: 0;
    font-family: var(--font-title);
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--color-text);
  }

  .match-list-empty__subtitle {
    margin: 0;
    font-size: var(--text-sm);
    max-width: 32ch;
    line-height: 1.45;
  }
}
