@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;
  }

  /* Per-competition display cap (match-render.js#renderCappedMatches).
   * Overflow cards beyond HOME_MATCHES_CAP sit here. display:contents keeps
   * them laying out as direct children of .league-group__matches (same trick
   * as .league-group__group), so a capped competition is visually identical
   * to an uncapped one once expanded. */
  .league-group__overflow {
    display: contents;
  }

  /* display:contents above overrides the UA `[hidden]{display:none}`, so the
   * hidden attribute alone wouldn't collapse the overflow — pin display:none
   * explicitly (same gotcha as .match-list-empty[hidden], see
   * feedback_hidden_attribute_display_override). */
  .league-group__overflow[hidden] {
    display: none;
  }

  /* While any filter (quick text or LIVE) is active the container gets
   * .match-list--filtering: force every capped overflow open so the filter
   * sees the whole set, and hide the "Ver más" toggles (they're meaningless
   * mid-filter). Higher specificity than the [hidden] rule above so it wins
   * even on a still-collapsed overflow. */
  .match-list--filtering .league-group__overflow[hidden] {
    display: contents;
  }

  /* "Ver más (N)" / "Ver menos" toggle for a capped competition. */
  .league-group__more {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin: 4px 0 2px;
    padding: 6px 2px;
    background: none;
    border: none;
    cursor: pointer;
    font-family: var(--font-body);
    font-size: 12px;
    font-weight: var(--weight-semibold);
    color: var(--color-text-muted);
    transition: color var(--duration-fast) var(--easing-default);
  }

  .league-group__more:hover {
    color: var(--color-text);
  }

  .league-group__more svg {
    width: 16px;
    height: 16px;
    stroke: currentcolor;
    transition: transform var(--duration-fast) var(--easing-default);
  }

  .league-group__more[aria-expanded="true"] svg {
    transform: rotate(180deg);
  }

  .league-group__more:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
    border-radius: var(--radius-md);
  }

  /* While any filter (quick text or LIVE) is active the container gets
   * .match-list--filtering: hide the "Ver más" toggles (meaningless mid-filter;
   * the overflow they gate is force-opened by the rule above). Placed after the
   * base .league-group__more rules to keep specificity non-descending. */
  .match-list--filtering .league-group__more {
    display: none;
  }

  /* 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;
  }

  /* "Top 5" pseudo-group: same chrome as the following block so both pinned
     sections read as one family, but the flame carries its own colour — they
     sit next to each other and must not look like the same thing. */
  .league-group--top .league-group__logo--top {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    color: var(--color-live);
    background: none;
    border-radius: 0;
  }

  .league-group--top .league-group__logo--top 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;
  }
}
