/*
 * /banlist/ — the public ban list.
 *
 * Additive to main.css, which is loaded first and supplies the palette, .section,
 * .section--panel, .btn, .wrap, .sr-only and the one focus ring. Nothing here restates
 * those; a second copy of a colour is a colour that drifts.
 *
 * THE COLOUR RULE, unchanged from main.css and the reason it is restated as a pointer
 * rather than as values: purple (#6900ff) carries this page as a BACKGROUND and BORDER
 * colour only — as text on these grounds it measures 2.23:1 and fails AA at every size.
 * Green (#00ff00) is the focus ring and nothing else here. Text is #f5f0ff or #c9aeff.
 *
 * MEASURED, NOT EYEBALLED. Every pair was composited against both ends of the body
 * gradient (#2e0073 at the top, #1a0033 below) and scored; the WORST end is the figure
 * that counts, and for these surfaces that is always the lighter one. 26 pairs pass,
 * with five controls that are asserted to FAIL so the check cannot go green by accident.
 *
 * The card's fill is rgba(46, 0, 115, 0.55) — the same purple as the gradient's top end,
 * so at that end the card composites to exactly the backdrop and "on the card" and "on
 * the body" are one column rather than two.
 *
 *                              card / body   input & chip   purple fill
 *   #f5f0ff body text            13.78:1       16.26:1         —
 *   #c9aeff muted                 8.05:1        9.50:1         —
 *   #ffa8bd permanent             8.50:1        —              —
 *   #5ef2d0 lifted               11.06:1       13.05:1         —
 *   #ffffff on a purple fill      —             —             6.90:1
 *   #a97dff structural            5.18:1        —              —
 *   #00ff00 focus ring           11.22:1       13.24:1        5.03:1
 *
 * Recompute rather than eyeball if any of these change.
 *
 * NO STATE IS CONVEYED BY COLOUR ALONE. Every badge carries its word — Active, Expired,
 * Lifted — and a permanent ban says "Permanent"; the colour sits on top of the word.
 *
 * Responsive with grid and flex, no framework and no media query below 40rem: the
 * layout starts as one column and gains columns when there is room. Checked at 360px.
 */

/* -------------------------------------------------------------------------
 * The two inks this page adds, and what they mean
 *
 * Both are already in the site's vocabulary — the staff punishment calculator uses
 * #ffa8bd for a permanent punishment and #5ef2d0 for one that is not a duration. Reusing
 * them means a moderator reads the same colour for the same idea on both pages.
 * ---------------------------------------------------------------------- */

.ban {
    --ban-permanent: #ffa8bd;
    --ban-lifted: #5ef2d0;
    --ban-structural: #a97dff;
}

/* -------------------------------------------------------------------------
 * Headings and notices
 * ---------------------------------------------------------------------- */

.banlist-heading {
    font-size: clamp(1.25rem, 3vw, 1.75rem);
    font-weight: 800;
}

/*
 * THE PAGE'S SPOKEN ANSWERS — no bans yet, nothing matched, the database is not set up.
 * All of them are a sentence a visitor can read, and all of them look the same on
 * purpose: which of those it is belongs in the words, not in a colour that would make
 * "no bans yet" look like an error.
 */
.banlist-notice {
    max-width: 44rem;
    margin: 0;
    padding: 1rem 1.25rem;
    border: 1px solid rgba(105, 0, 255, 0.5);
    border-radius: var(--radius);
    background: rgba(46, 0, 115, 0.72);
    color: var(--text-primary);
}

.banlist-notice a {
    color: var(--text-primary);
}

/* The search term echoed back inside a notice. Quoted in the markup as well, so it is
 * still marked off as the visitor's own words with the stylesheet missing. */
.banlist-term {
    font-weight: 700;
    overflow-wrap: anywhere;
}

/* -------------------------------------------------------------------------
 * Search
 * ---------------------------------------------------------------------- */

.banlist-search {
    margin: 0 0 1.5rem;
}

/* A REAL, VISIBLE LABEL. A placeholder is not a label: it vanishes the moment somebody
 * types, and it is not reliably announced. */
.banlist-search__label {
    display: block;
    margin-bottom: 0.35rem;
    font-weight: 700;
}

.banlist-search__row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: stretch;
}

.banlist-search__input {
    flex: 1 1 14rem;
    /* 0 lets the flex item shrink below its content width, which is what stops a long
     * value in the box from forcing the page sideways at 360px. */
    min-width: 0;
    padding: 0.65rem 0.9rem;
    border: 2px solid rgba(105, 0, 255, 0.6);
    border-radius: var(--radius);
    background: rgba(0, 0, 0, 0.35);
    color: var(--text-primary);
    font-family: inherit;
    font-size: 1rem;
    /* 44px minimum target: padding + border + line box. */
    min-height: 44px;
}

.banlist-search__input::placeholder {
    color: var(--text-muted);
}

.banlist-search__hint {
    max-width: 44rem;
    margin: 0.6rem 0 0;
    color: var(--text-muted);
    font-size: 0.9375rem;
}

/* The `STEAM_0:0:00000` example. A monospace run inside a sentence, not a code block. */
.banlist-code {
    padding: 0.1rem 0.35rem;
    border-radius: 4px;
    background: rgba(0, 0, 0, 0.35);
    color: var(--text-primary);
    font-family: ui-monospace, "Cascadia Mono", Consolas, "Courier New", monospace;
    font-size: 0.9em;
    overflow-wrap: anywhere;
}

/* -------------------------------------------------------------------------
 * The state filter
 * ---------------------------------------------------------------------- */

.banlist-tabs__list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.banlist-tab {
    /* inline-FLEX, not inline-block: the 44px minimum target below makes the box taller
     * than its text, and flex centring is what keeps the label in the middle of it. */
    display: inline-flex;
    align-items: center;
    padding: 0.5rem 1rem;
    border: 1px solid rgba(105, 0, 255, 0.5);
    border-radius: var(--radius-pill);
    background: rgba(0, 0, 0, 0.25);
    color: var(--text-primary);
    text-decoration: none;
    font-size: 0.9375rem;
    min-height: 44px;
}

.banlist-tab:hover {
    background: rgba(105, 0, 255, 0.3);
}

/*
 * The selected tab: an opaque brand fill with white on it, 6.90:1. It also carries
 * aria-current="page" in the markup, so which tab is on is not a colour-only fact.
 */
.banlist-tab--on {
    border-color: var(--purple-brand);
    background: var(--purple-brand);
    color: var(--white);
    font-weight: 700;
}

.banlist-tab--on:hover {
    background: var(--purple-deep);
    border-color: var(--purple-deep);
}

/* -------------------------------------------------------------------------
 * The list
 * ---------------------------------------------------------------------- */

/*
 * An <ol> because the order is the content — newest first — and a numbered marker beside
 * each card would read as a rank, which it is not. So the marker goes and the ordering
 * stays in the element.
 */
.banlist {
    margin: 0;
    padding: 0;
    list-style: none;
    display: grid;
    gap: 1rem;
}

.ban {
    padding: 1.1rem 1.25rem;
    border: 1px solid rgba(105, 0, 255, 0.4);
    /* Every card has the thick left edge, so the WIDTH is card decoration and the
     * geometry does not shift between states. Only the COLOUR carries anything. */
    border-left-width: 4px;
    border-radius: var(--radius);
    background: rgba(46, 0, 115, 0.55);
}

/*
 * THE STATE STRIPE — a scanning aid down a long list, and never the only signal: every
 * card's badge says the word.
 *
 * TWO STRIPES, NOT THREE, AND THAT IS THE MEASUREMENT'S DOING. A mark that carries state
 * has to clear 3:1 against what it sits on, and the first draft's three stripes did not:
 * the brand purple measures 2.23:1 on the body's lighter end and a translucent lilac
 * 2.87:1. So active took the site's structural lilac (5.18:1) and lifted took the teal
 * (11.06:1), and EXPIRED KEEPS THE CARD'S OWN EDGE — the absence of a state colour, which
 * signals nothing by itself and so has no ratio to clear. Expired is the state where "no
 * emphasis" is the right emphasis anyway.
 *
 * Do not restore a dim expired stripe. Dim and 3:1 are in tension on this background:
 * anything faint enough to read as faded is under the bar by construction.
 */
.ban--active {
    border-left-color: var(--ban-structural);
}

.ban--lifted {
    border-left-color: var(--ban-lifted);
}

.ban__head {
    display: flex;
    flex-wrap: wrap;
    gap: 0.85rem;
    align-items: flex-start;
}

.ban__avatar {
    flex: none;
    width: 56px;
    height: 56px;
    border-radius: var(--radius);
    /* The box is reserved whether or not the picture arrives, so nothing reflows when a
     * remote avatar loads and nothing collapses when it does not. */
    background: rgba(0, 0, 0, 0.35);
    object-fit: cover;
}

.ban__who {
    /* flex: 1 with min-width: 0 — the pair that lets a 64-character name wrap inside the
     * card instead of pushing the card wider than the viewport. */
    flex: 1 1 12rem;
    min-width: 0;
}

.ban__name {
    margin: 0;
    font-size: 1.125rem;
    font-weight: 700;
    /* A Steam persona name has no spaces in it if the player did not put any there. */
    overflow-wrap: anywhere;
}

.ban__aka {
    margin: 0.15rem 0 0;
    color: var(--text-muted);
    font-size: 0.9375rem;
    overflow-wrap: anywhere;
}

.ban__ids {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.75rem;
    margin: 0.35rem 0 0;
    font-size: 0.9375rem;
}

.ban__id {
    font-family: ui-monospace, "Cascadia Mono", Consolas, "Courier New", monospace;
    overflow-wrap: anywhere;
}

/* The SteamID64 is a link to the profile; the STEAM_0 form is not, because there is
 * nothing to link it to. It is the form a moderator types into a console. */
.ban__id--legacy {
    color: var(--text-muted);
}

.ban__status {
    flex: none;
    margin: 0;
    margin-left: auto;
}

.ban-badge {
    display: inline-block;
    padding: 0.25rem 0.7rem;
    border: 1px solid transparent;
    border-radius: var(--radius-pill);
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    white-space: nowrap;
}

.ban-badge--active {
    border-color: var(--purple-brand);
    background: var(--purple-brand);
    color: var(--white);
}

.ban-badge--expired {
    border-color: rgba(201, 174, 255, 0.45);
    background: rgba(0, 0, 0, 0.35);
    color: var(--text-muted);
}

.ban-badge--lifted {
    border-color: rgba(94, 242, 208, 0.5);
    background: rgba(0, 0, 0, 0.35);
    color: var(--ban-lifted);
}

/* -------------------------------------------------------------------------
 * A ban's fields
 * ---------------------------------------------------------------------- */

/*
 * A real <dl>: each field is a term and its value, which is what these are. The <div>
 * wrapper around each dt/dd pair is valid HTML5 and is what makes them grid items.
 *
 * auto-fit with a 13rem floor, so this is ONE column at 360px and two or three when
 * there is room — no media query, and no width at which a field is squeezed narrower
 * than its content.
 */
.ban__fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    gap: 0.75rem 1.25rem;
    margin: 1rem 0 0;
    padding-top: 0.9rem;
    border-top: 1px solid rgba(105, 0, 255, 0.35);
}

/* Reason and Reason for lifting are prose up to 512 characters, so they take the whole
 * row rather than a 13rem column. */
.ban__field--wide {
    grid-column: 1 / -1;
}

.ban__field dt {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.8125rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.ban__field dd {
    margin: 0.15rem 0 0;
    overflow-wrap: anywhere;
}

/* PERMANENT is a word, never a date. */
.ban__permanent {
    color: var(--ban-permanent);
    font-weight: 700;
}

/* "7 days" beside the expiry instant — the duration a human reads, computed from the two
 * absolute times rather than stored. */
.ban__length {
    color: var(--text-muted);
}

.ban__length::before {
    /* Decoration only: the two values are separate elements, so with the stylesheet
     * missing they read as "17 Aug 2026, 20:04 UTC 7 days" rather than running together
     * into one word. */
    content: "· ";
}

.ban__staff {
    font-weight: 700;
}

.ban__rank,
.ban__sep,
.ban__console {
    color: var(--text-muted);
}

/* A field the game server did not record. Never used for attribution — a ban whose
 * admin resolves to nobody has no attribution field at all. */
.ban__none {
    color: var(--text-muted);
    font-style: italic;
}

/* -------------------------------------------------------------------------
 * Paging
 * ---------------------------------------------------------------------- */

.banlist-pager {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
    margin-top: 1.5rem;
}

/* Newer sits left and Older right when both are there; with only one of them present the
 * page number keeps its place in the middle rather than sliding across. */
.banlist-pager__at {
    order: 2;
    margin: 0;
    color: var(--text-muted);
}

.banlist-pager [rel="prev"] {
    order: 1;
}

.banlist-pager [rel="next"] {
    order: 3;
}

/* -------------------------------------------------------------------------
 * Motion
 * ---------------------------------------------------------------------- */

/*
 * There is no animation on this page and nothing auto-rotates, so there is nothing here
 * to switch off. main.css sets scroll-behavior: smooth on <html> and its own
 * prefers-reduced-motion block turns that off; this file adds no motion for that block
 * to have missed.
 */
