/* Layout — body, app shell, content area.
 *
 * Wrapped in `@layer layout` so that component-level overrides win in the
 * cascade. AssetMapper doesn't auto-wrap when CSS is imported from JS.
 */

@layer layout {
  body {
    font-family: var(--font-body);
    font-size: var(--text-base);
    font-weight: var(--weight-normal);
    color: var(--color-text);
    background: var(--color-bg);
  }

  /* Scroll anchoring kept disabled out of caution — it was originally
     a fix for the match-detail `.compact` toggle's "tennis match"
     oscillation. The compact toggle is gone (B.2 sticky-only model,
     2026-05-16), but keeping the rule avoids surprise reflows in any
     other future sticky-with-changing-height interactions. */
  html {
    overflow-anchor: none;
  }

  .app {
    display: flex;
    flex-direction: column;
    min-height: 100dvh;
  }

  /* Page-level sticky wrapper — pins the whole `block header` (app
     header on home) to the top of the viewport. Entity pages
     (team / league / player / match) opt out below: those pages use
     the B.2 sticky-only cascade (slim hero-nav sticks at top, tabs
     stick below it, big hero scrolls away) — see entity-sticky.css. */
  .app__sticky-header {
    position: sticky;
    top: 0;
    z-index: 100;
    background: var(--color-bg);
  }

  /* Desktop shell pages flatten the sticky-header wrapper so the
     `{% block subheader %}` (e.g. home's calendar-strip) flows normally
     below the sticky desktop-header instead of being pinned with it.
     `display: contents` removes the wrapper from the layout tree — its
     children become direct children of `.app`, and the `.desktop-header`
     owns its own `position: sticky` + 1280 centering (see
     components/desktop-header.css). Mobile and non-shell pages keep the
     full-bleed sticky wrapper 1:1. Same flatten trick used by entity
     pages below for the B.2 sticky cascade. */
  @media (width >= 1200px) {
    .has-desktop-shell .app__sticky-header {
      display: contents;
    }
  }

  body.team-page .app__sticky-header,
  body.league-page .app__sticky-header,
  body.player-page .app__sticky-header,
  body.match-page .app__sticky-header {
    /* `display: contents` removes the box from the layout tree so its
       children behave as direct children of `.app`. Without it, the
       sticky `.hero-nav` would be constrained to the wrapper's height
       (~360 px sum of hero + tabs) and would unstick once the page
       scrolled past the wrapper. With contents, the sticky element's
       containing block becomes `.app` (full page height). */
    display: contents;
  }

  .app__content {
    flex: 1;
    width: 100%;
    max-width: var(--content-max-width);
    margin-inline: auto;
    padding-top: 10px;
    padding-bottom: calc(var(--nav-height) + 20px + env(safe-area-inset-bottom, 0px));
  }

  /* Typography — Oswald for titles, scores, headings */
  h1,
  h2,
  h3,
  h4,
  h5,
  h6,
  .match-card__score-home,
  .match-card__score-away,
  .league-group__name,
  .calendar-strip__day-number {
    font-family: var(--font-title);
  }

  /* Desktop: widen content area */
  @media (width >= 768px) {
    :root {
      --content-max-width: 720px;
    }
  }

  @media (width >= 1440px) {
    :root {
      --content-max-width: 960px;
    }
  }

  /* ─── Desktop shell (sidebars left/right + main) ────────────────────────
   * Opt-in via `body.has-desktop-shell`. Pages that don't opt in keep their
   * mobile-style single-column flow intact — the `.app__shell` wrapper is
   * `display: contents` by default and the sidebars are `display: none`,
   * so non-opted pages see no DOM/layout change.
   *
   * Why opt-in (not automatic for every page): not every desktop page wants
   * sidebars yet (Phase 3 is gradual). Once a page declares
   * `{% block body_class %}has-desktop-shell{% endblock %}` it gets the
   * 3-col grid at ≥1200px and the wider variant at ≥1600px. Match-detail
   * and entity pages will opt in at their own sprint, with sidebar
   * content tailored per page.
   *
   * The `display: contents` on `.app__shell` survives the cascade — same
   * trick the entity sticky-cascade uses (`body.team-page .app__sticky-header`)
   * to flatten a wrapper so children behave as direct children of the
   * parent. In mobile, the sidebars are display:none, so the only visible
   * child is `.app__main` — which renders exactly as before.
   */

  .app__shell {
    display: contents;
  }

  .app__sidebar {
    display: none;
  }

  @media (width >= 1200px) {
    .has-desktop-shell .app__content {
      max-width: var(--shell-max-width-laptop);
      padding-inline: var(--shell-padding-inline);
    }

    .has-desktop-shell .app__shell {
      display: grid;
      grid-template-columns:
        var(--shell-sidebar-left)
        minmax(0, 1fr)
        var(--shell-sidebar-right-laptop);
      gap: var(--shell-gap-laptop);
      align-items: start;
    }

    .has-desktop-shell .app__sidebar {
      display: block;

      /* Sidebars scroll WITH the page now (only the desktop-header
         remains sticky at top). The `position: sticky` + bounded
         max-height variant was confusing — the page felt split into
         three independent scrolls. A single page scroll is the
         expected behaviour for content sites. */
    }

    /* Above-shell slot for hero carousel / entity hero. Full width of the
       shell container, NOT viewport-wide — keeps 4K screens readable. */
    .has-desktop-shell .app__above-shell {
      margin-bottom: var(--shell-gap-laptop);
    }
  }

  @media (width >= 1600px) {
    .has-desktop-shell .app__content {
      max-width: var(--shell-max-width-wide);
    }

    .has-desktop-shell .app__shell {
      grid-template-columns:
        var(--shell-sidebar-left)
        minmax(0, 1fr)
        var(--shell-sidebar-right-wide);
      gap: var(--shell-gap-wide);
    }

    .has-desktop-shell .app__above-shell {
      margin-bottom: var(--shell-gap-wide);
    }
  }
}
