/*
 * SPDX-FileCopyrightText: 2026-present TeamCalendar
 * SPDX-License-Identifier: LicenseRef-Proprietary
 *
 * The site's stylesheet: the tokens, the reset, and the four regions the shell
 * declares — header, rail, main, footer (PRD 170, build item 3).
 *
 * It is the LAYOUT's stylesheet, not the picker's. `picker.css` keeps what
 * renders inside the main region and nothing else, so the twelve static pages
 * build item 8 lands can link this one alone and inherit the same chrome
 * without carrying a picker they do not have.
 *
 * Served from /assets, which Program.cs caches for an hour with revalidation
 * and CalendarFeedEndpoints reserves against the feed catch-all. The filename
 * is conventional rather than fingerprinted, for the reason picker.css records.
 */

:root {
    color-scheme: light dark;

    --ink: #16181d;
    --ink-soft: #5b6270;
    --page: #f7f7f5;
    --panel: #ffffff;
    --rule: #e2e2dd;
    /* One accent, borrowed from nothing: every league mark on this page brings
       its own colour, so the product's own must not compete with them. */
    --accent: #1d5fd8;
    /* The selected rail row (build item 10). A token rather than a literal
       because it has to have a dark answer: a translucent black reads as
       "selected" on the light page and as nothing at all on the dark one, which
       is a selection state that exists for half the visitors. */
    --selected: #e8eaf0;

    --measure: 62rem;
    --step: 1rem;
    --radius: 0.5rem;

    --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

    /* The header's height is a NUMBER rather than something measured from its
       contents, and that is the whole of what makes "the viewport minus the
       header" a stylesheet fact: `calc` cannot subtract a box it has not been
       told about, so the alternative is a script observing the header and
       writing this property back — which is exactly what PRD 170 says the rail's
       height must not depend on. The cost is that changing what the header holds
       means changing this with it. */
    --header-height: 3.5rem;

    /* The rail's two widths. Collapsed is about 56px — a mark and its padding,
       and the same box the header is tall — which is what makes the marks
       legible as navigation with no name beside them. */
    --rail-width: 13rem;
    --rail-collapsed-width: 3.5rem;
}

@media (prefers-color-scheme: dark) {
    :root {
        --ink: #eceef2;
        --ink-soft: #9aa1af;
        --page: #101216;
        --panel: #191c22;
        --rule: #2b2f38;
        --accent: #7aa5ff;
        --selected: #262b34;
    }
}

*,
*::before,
*::after {
    box-sizing: border-box;
}

/* Flow rather than grid, deliberately. A sticky element is confined to its
   containing block, and under `display: grid` each region's containing block is
   its own row — so a header in a one-row grid area cannot travel, and both
   sticky rules below would silently do nothing. In a flex column the containing
   block is the body, which spans the document. */
body {
    margin: 0;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    background: var(--page);
    color: var(--ink);
    font-family: var(--sans);
    font-size: 1rem;
    line-height: 1.5;
    -webkit-text-size-adjust: 100%;
}

/* ── Region 1: the header ─────────────────────────────────────────────────── */

/* Full width, and sticky at the top. Sticky is not decoration here: the rail
   below claims the viewport minus this box at EVERY scroll position, and that
   arithmetic only stays true while this box is still on screen. */
.site-header {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    flex: none;
    align-items: baseline;
    gap: 0.75rem;
    height: var(--header-height);
    padding-inline: var(--step);
    border-bottom: 1px solid var(--rule);
    background: var(--panel);
}

.wordmark {
    margin: 0;
    font-size: 1.25rem;
    font-weight: 650;
    /* Tight tracking is the whole of the wordmark's character — there is no
       logotype file, and inventing one is not this build's business. */
    letter-spacing: -0.03em;
}

/* Kept from the masthead this header replaces rather than dropped: it is the
   one line that says what the product does. It goes first on a narrow screen,
   where the hamburger build item 7 adds needs the room more than it does. */
.tagline {
    margin: 0;
    color: var(--ink-soft);
    font-size: 0.875rem;
}

@media (max-width: 40rem), (max-height: 30rem) {
    .tagline {
        display: none;
    }
}

/* The hamburger's region, pushed to the right of everything else in the header.
   `align-self` rather than a change to the header's own `align-items: baseline`:
   the wordmark and the tagline are text and share a baseline, and a button is
   not text. */
.header-menu {
    display: flex;
    align-self: center;
    margin-inline-start: auto;
}

.menu-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: var(--radius);
    background: none;
    color: var(--ink);
    font: inherit;
    cursor: pointer;
}

.menu-toggle:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.menu-glyph {
    font-size: 1.25rem;
    line-height: 1;
}

/* The panel, and the DESKTOP rendering of it: a dropdown hung under the header,
   flush with the edge the hamburger sits on.

   It is positioned against `.site-header`, which is `position: sticky` and
   therefore already a containing block — so the dropdown travels with the header
   at every scroll position without a second sticky box or a script. The phone
   rule at the foot of this section re-anchors the same element to the viewport;
   nothing else about it changes, which is what makes the two renderings one
   mechanism (PRD 170: a dropdown on a desktop, a right-side flyout on a phone). */
.site-menu {
    position: absolute;
    top: var(--header-height);
    inset-inline-end: 0;
    z-index: 3;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    width: min(22rem, 92vw);
    max-height: calc(100dvh - var(--header-height) - var(--step));
    overflow-y: auto;
    padding: var(--step);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--panel);
}

/* Not decoration, and not redundant with the attribute either: `hidden` is a
   `display: none` the USER AGENT applies, and every author `display` above beats
   it. Without this line the panel is open on every page load, at every width,
   and `aria-expanded` says otherwise. */
.site-menu[hidden] {
    display: none;
}

.menu-heading {
    margin: 0 0 0.5rem;
    font-size: 0.8125rem;
    font-weight: 650;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

.menu-entry {
    margin: 0;
}

.menu-link {
    padding: 0.375rem 0;
    color: var(--ink);
    text-decoration: none;
}

.menu-link:hover,
.menu-link:focus-visible {
    text-decoration: underline;
}

.menu-preference {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 0.5rem;
}

.menu-label {
    flex: none;
    color: var(--ink-soft);
    font-size: 0.875rem;
}

/* The zone list is the runtime's own and runs to several hundred names, so the
   control has to be allowed to shrink rather than set the panel's width. */
.menu-zone {
    flex: 1;
    min-width: 0;
    font: inherit;
}

.menu-check {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.875rem;
    cursor: pointer;
}

.menu-action {
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    color: var(--accent);
    font: inherit;
    text-align: start;
    cursor: pointer;
}

.menu-status {
    margin: 0;
    color: var(--ink-soft);
    font-size: 0.8125rem;
}

/* The PHONE rendering of the same panel: a right-side flyout, running the full
   height below the header and anchored to the edge the hamburger is on. Only the
   positioning changes — same markup, same `hidden`, same `aria-expanded` — and
   there is deliberately no `display: none` here, because hiding the menu on the
   screens with the least room is where the navigation would actually be lost. */
@media (max-width: 40rem), (max-height: 30rem) {
    .site-menu {
        position: fixed;
        inset-block: var(--header-height) 0;
        inset-inline-end: 0;
        width: min(20rem, 85vw);
        max-height: none;
        border-radius: 0;
        border-inline-start: 1px solid var(--rule);
    }
}

/* ── Regions 2 and 3: the rail and the main region ────────────────────────── */

/* The wrapper the criterion is about. The rail and the main region are siblings
   inside it and the header and footer are outside it, which is what lets the
   rail be measured against the viewport instead of against the document. */
.shell {
    flex: 1;
    display: grid;
    /* The rail sizes ITSELF and this track follows it, which is what lets the
       collapsed state live on the rail rather than on this wrapper: a track
       written as `var(--rail-width)` reads the value declared on `:root`, and
       a property the rail set on itself would never reach it.
       `minmax(0, 1fr)` and not `1fr`: a grid track's automatic minimum is its
       content, so a wide table or a long unbroken address in the main region
       would otherwise push the column past the viewport and take the rail's
       width with it. */
    grid-template-columns: auto minmax(0, 1fr);
    align-items: start;
}

/* The frame. It does not scroll — `.rail-list` inside it does — because five
   leagues fit any viewport today and a college division would not fit a short
   laptop screen, and retrofitting this later means moving the sticky box. */
.rail {
    position: sticky;
    top: var(--header-height);
    height: calc(100dvh - var(--header-height));
    width: var(--rail-width);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border-inline-end: 1px solid var(--rule);
}

/* The collapsed state, as an attribute carrying a VALUE rather than as a bare
   flag (build item 4). Three states exist and only two of them are the
   script's: the document is served before any preference has been read, so
   "not decided yet" has to be distinguishable from "expanded" — otherwise the
   phone default at the foot of this file could not be stated without narrowing
   a phone visitor who had expanded the rail. Both rules below outrank that one
   on specificity, at every width, which is the "one mechanism at every width"
   the PRD asks for. */
.rail[data-rail-state="expanded"] {
    width: var(--rail-width);
}

.rail[data-rail-state="collapsed"] {
    width: var(--rail-collapsed-width);
}

.rail-list {
    flex: 1;
    overflow-y: auto;
    list-style: none;
    margin: 0;
    padding: 0.5rem;
}

.rail-row {
    display: flex;
    align-items: center;
}

/* The row's whole hit area, and a real link (build item 10): `/{league}` is an
   address since PRD 160 freed the first segment, so the row is an `<a href>` and
   the click handler intercepts it rather than inventing a control. The padding
   lives HERE rather than on the row so the target is the row and not the text
   inside it — a 28px mark with a name beside it is otherwise a thin strip to
   hit on a phone. */
.rail-link {
    flex: 1;
    display: flex;
    min-width: 0;
    align-items: center;
    gap: 0.625rem;
    padding: 0.5rem 0.625rem;
    /* The site's own radius rather than a smaller one of this row's invention:
       the selected row is a filled block and it should be the same corner as
       every other filled block on the page. */
    border-radius: var(--radius);
    color: inherit;
    text-decoration: none;
}

/* The name and what the visitor has picked in that league, stacked. `min-width`
   is the flex-item guard the name's ellipsis needs: without it the block's
   minimum is its content and a long competition name pushes the row wider
   instead of clipping. */
.rail-text {
    display: flex;
    min-width: 0;
    flex-direction: column;
    gap: 0.0625rem;
}

/* Only ever a count, and only ever of this visitor's own picks — so it is the
   accent, which on this page means "yours". A league they have not reached
   renders it empty rather than "0 picked", and an empty span takes no line. */
.rail-meta {
    overflow: hidden;
    color: var(--accent);
    font-size: 0.75rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.rail-meta:empty {
    display: none;
}

/* The selected league — the row whose address the visitor is at. `aria-current`
   is the selector as well as the announcement, so there is one state rather than
   a stamped attribute and a class that has to be kept in step with it.
   
   Not colour alone, and not weight alone: the fill reads at a glance, the weight
   survives a display that cannot separate the fill from the page behind it, and
   `aria-current` is what a screen reader gets instead of either. The weight is
   set here and inherited rather than aimed at `.rail-name`, because a descendant
   rule ending in that class is the first thing `styles.mjs#declarations` finds
   when a test asks about the name itself. Measured. */
.rail-link[aria-current="page"] {
    background: var(--selected);
    font-weight: 600;
}

.rail-link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.rail-mark {
    flex: none;
    width: 28px;
    height: 28px;
    object-fit: contain;
}

/* One line, clipped rather than wrapped. The frame is the viewport minus the
   header and it is what must not scroll, so a long competition name has to cost
   the row its width and never its height. */
.rail-name {
    overflow: hidden;
    color: var(--ink);
    font-size: 0.875rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.rail-status {
    margin: 0;
    padding: 0.5rem;
    overflow: hidden;
    color: var(--ink-soft);
    font-size: 0.875rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Outside the list on purpose: it belongs to the frame, so it stays put while
   the list scrolls under it. `flex: none` after a `flex: 1` list is what puts
   it at the bottom without a position of its own. */
.rail-toggle {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 2.25rem;
    margin: 0;
    padding: 0;
    border: 0;
    border-top: 1px solid var(--rule);
    background: none;
    color: var(--ink-soft);
    font: inherit;
    cursor: pointer;
}

.rail-toggle:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.rail-toggle-mark {
    font-size: 1.25rem;
    line-height: 1;
}

.site-main {
    /* The same automatic-minimum guard as the track above, on the item this
       time: a grid ITEM's minimum is also its content. */
    min-width: 0;
    max-width: var(--measure);
    padding: calc(var(--step) * 2) var(--step);
}

/* ── The step bar, and the three steps (PRD 170 as amended 2026-09-16) ────── */

/* Not rendered above the phone breakpoint, and that is the whole desktop story:
   the rail, the teams and the calendar panel are all on screen at once up there,
   so a control announcing "step 2 of 3" beside them would describe something
   that is not happening. `display: none` rather than `visibility` because it
   must take no space and reach no screen reader — there is no step to be on. */
.step-bar {
    display: none;
    grid-column: 1 / -1;
}

.steps {
    display: flex;
    list-style: none;
    margin: 0;
    padding: 0;
    border-bottom: 1px solid var(--rule);
    background: var(--panel);
}

.step {
    flex: 1;
}

.step-link {
    width: 100%;
    /* The 44px floor. A step bar is the page's navigation on a phone and every
       one of the three is a thumb target. */
    min-height: 2.75rem;
    padding: 0.5rem;
    border: 0;
    background: none;
    color: var(--ink-soft);
    font: inherit;
    font-size: 0.875rem;
    cursor: pointer;
}

/* The one piece of state, and it is drawn twice — weight and a rule — so the
   current step survives a display that cannot separate the two colours. */
.step-link[aria-current="step"] {
    color: var(--ink);
    font-weight: 600;
    box-shadow: inset 0 -2px 0 0 var(--accent);
}

.step-link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

/* ── One region at a time, on a phone ─────────────────────────────────────── */

/* This is the amendment, as a stylesheet. PRD 170 §3 put the rail beside the
   grid at every width, collapsed to marks on a phone; §4b put the subscribe
   panel below the grid. On a 402px screen that stacked four things nobody asked
   for into one scroll.

   Now the phone shows exactly one of the three, and `body[data-step]` says
   which. `picker.js#showStep` is the only writer of that attribute, and a
   document with no script keeps every region — which is what the `<noscript>`
   in the main region is for and why nothing here hides anything by default. */
@media (max-width: 40rem), (max-height: 30rem) {
    .step-bar {
        display: block;
    }

    /* The two columns stop being columns. One region is on screen at a time, so
       there is nothing to put beside anything — and a grid whose second track is
       hidden still sizes the first to its content, which is how the rail ended
       up 110px wide on a 390px screen the first time this was built. */
    .shell {
        display: block;
    }

    /* Step 1 IS the rail: full width, on its own screen, showing names rather
       than five unlabelled crests. It stops being sticky and stops being a
       viewport tall — it is the page now, and it scrolls with the document like
       one. The collapse control has nothing to do here: there is no second
       column for it to buy room from. */
    body[data-step="1"] .rail {
        position: static;
        width: 100%;
        height: auto;
        overflow: visible;
    }

    body[data-step="1"] .rail-list {
        overflow-y: visible;
    }

    body[data-step="1"] .rail-toggle {
        display: none;
    }

    /* One rule each rather than a selector list, so a test can name the step
       it is asserting about instead of matching whichever spelling came first. */
    /* Half the vertical padding. `--step * 2` above and below is right where the
       page is 844px tall and wasteful where it is 390: on a rotated phone those
       64px were a third of what was left after the header, the step bar and the
       tally bar had taken their 178. */
    .site-main {
        padding-block: var(--step);
    }

    body[data-step="2"] .rail {
        display: none;
    }

    body[data-step="3"] .rail {
        display: none;
    }
}

/* ── Region 4: the footer ─────────────────────────────────────────────────── */

/* Production's footer, ported (build item 8): four link columns, then the social
   links and the copyright. It is the one region that renders identically on the
   layout and on the eleven static pages, which is why it is here rather than in
   pages.css — the pages link this file and the layout does not link theirs. */
.site-footer {
    flex: none;
    border-top: 1px solid var(--rule);
    padding: calc(var(--step) * 1.5) var(--step);
    color: var(--ink-soft);
    font-size: 0.875rem;
}

.site-footer p {
    margin: 0;
}

/* `auto-fit` with a floor rather than a column count: the four columns become
   two and then one as the viewport narrows, with no breakpoint to keep in step
   with the ones the header and the rail already declare. */
.footer-columns {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: var(--step) calc(var(--step) * 2);
    max-width: var(--measure);
}

.footer-heading {
    margin: 0 0 0.5rem;
    color: var(--ink);
    font-size: 0.8125rem;
    font-weight: 650;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.footer-links,
.footer-social {
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
    list-style: none;
    margin: 0;
    padding: 0;
}

.footer-social {
    flex-direction: row;
    gap: var(--step);
}

/* The Supported Leagues column, which `footer.js` fills from `/api/leagues`
   rather than the document carrying it. The mark is what production's emoji
   prefix was, so the row is an image beside a word at every width. */
.footer-league {
    display: flex;
    align-items: center;
}

/* The entry, as a link (build item 10): production ships all five as `href="#"`
   and the addresses they were waiting for are the `/{league}` ones this PRD
   creates. Flex on the ANCHOR rather than the row so the mark and the words are
   one target — `.site-footer a` below already settles its colour and
   decoration. */
.footer-league-link {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.footer-mark {
    flex: none;
    width: 20px;
    height: 20px;
    object-fit: contain;
}

/* What the column shows before the read returns, and after one that refused.
   A visitor with no JavaScript keeps this line — which is the honest rendering
   of a list this document does not know. */
.footer-status {
    color: var(--ink-soft);
}

.site-footer a {
    color: inherit;
    text-decoration: none;
}

.site-footer a:hover,
.site-footer a:focus-visible {
    color: var(--ink);
    text-decoration: underline;
}

.footer-base {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem var(--step);
    max-width: var(--measure);
    margin-top: calc(var(--step) * 1.5);
    padding-top: var(--step);
    border-top: 1px solid var(--rule);
}

.footer-copyright {
    font-size: 0.8125rem;
}

/* ── The ad slot ──────────────────────────────────────────────────────────── */

/* One slot in the shared layout (build item 9), inserted by `ads.js` between the
   content and the footer on every document that does not hold a capability. It
   is styled here rather than in `picker.css` or `pages.css` for the footer's
   reason: it is the same band on both families of document, and two copies would
   be two places to change a provider's box. */
.ad-slot {
    /* A flex item on the layout, whose body is a column; a block on the static
       pages. Both want the same thing — it takes the height of what it holds and
       never competes with the main region for the rest. */
    flex: none;
    width: 100%;
    max-width: var(--measure);
    margin: 0 auto;
    padding: var(--step);
}

/* Empty until Band R4 puts a tag behind the hook, and collapsed while it is.
   Nothing is reserved for an advertisement that does not exist, so today's
   twelve pages render exactly as they did before this slot landed — and the
   `aria-label` above does not announce a landmark holding nothing. */
.ad-slot:empty {
    display: none;
}

/* ── Shared ───────────────────────────────────────────────────────────────── */

/* The wordmark is a link on the eleven static pages and plain text on the
   layout, because on the layout it already names the page you are on. The rule
   is here rather than in pages.css so the two renderings cannot drift apart. */
.wordmark-link {
    color: inherit;
    text-decoration: none;
}

/* Present for a screen reader, absent for everyone else. Production's own class
   name, kept because it is the one thing in the captured copy that is
   behaviour rather than styling — the footer's heading and the contact page's
   `<dt>` labels both depend on it. Clip-based rather than `display: none`,
   which would take it out of the accessibility tree as well. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* Respect a visitor who has asked for less motion before any later build item
   introduces some. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}
