/* ---- palette ----
 *
 * One variable per shade the dark theme already used, so dark is unchanged to
 * the byte and light is the same ramp inverted. Naming is by role and depth
 * rather than by colour: --surface-4 is "a bit above the card", not "#282828".
 *
 * Three states, in this order, and the order is what makes them work:
 *   :root                    dark, the default
 *   @media light + :not(.dark)   follow the system, unless the reader pinned dark
 *   html.light               the reader pinned light, beats the media query
 *
 * style.css is on the server and cannot be edited, so the handful of colours it
 * sets are overridden at the bottom of this block rather than at their source.
 */
:root {
    --bg: #121212;
    --bg-alt: #181818;
    --surface: #1e1e1e;
    --line-faint: #1c1c1c;
    --surface-2: #232323;
    --surface-3: #262626;
    --surface-4: #282828;
    --surface-5: #2a2a2a;
    --border: #2b2b2b;
    --line: #2c2c2c;
    --surface-6: #333;
    --text-6: #3d3d3d;
    --scroll: #4a4a4a;
    --text-5: #4e4e4e;
    --text-4: #5f5f5f;
    --text-3: #6a6a6a;
    --text-3b: #858585;
    --text-2b: #8a8a8a;
    --text-2: #9a9a9a;
    --text-1c: #b8b8b8;
    --text-1b: #d0d0d0;
    --text-1d: #d2d2d2;
    --text: #E0E0E0;
    --text-0b: #e6e6e6;
    --text-0: #f0f0f0;
    --text-max: #fff;
    --accent: #3f9fff;
    --on-accent: #00121f;
    --shadow-text: rgba(0, 0, 0, .75);
    --scrim: rgba(0, 0, 0, .55);
    --shadow: rgba(0, 0, 0, .45);
    --flag-op: .45;
}

@media (prefers-color-scheme: light) {
    html:not(.dark) {
        --bg: #ffffff;
        --bg-alt: #f3f4f6;
        --surface: #f4f5f7;
        --line-faint: #ebedf0;
        --surface-2: #eceef1;
        --surface-3: #e8eaee;
        --surface-4: #e2e5e9;
        --surface-5: #dde0e5;
        --border: #d5d9df;
        --line: #e0e3e7;
        --surface-6: #ced3da;
        --text-6: #b4bac2;
        --scroll: #c0c6cd;
        --text-5: #a3aab3;
        --text-4: #949ba5;
        --text-3: #868e99;
        --text-3b: #78808b;
        --text-2b: #727a85;
        --text-2: #646c77;
        --text-1c: #4a515b;
        --text-1b: #373e48;
        --text-1d: #353c46;
        --text: #22282f;
        --text-0b: #1b212a;
        --text-0: #141a22;
        --text-max: #0a0f16;
        --accent: #0b63d6;
        --on-accent: #ffffff;
        --shadow-text: rgba(255, 255, 255, .85);
        --scrim: rgba(15, 20, 28, .38);
        --shadow: rgba(15, 20, 28, .16);
        --flag-op: .72;
    }
}

html.light {
    --bg: #ffffff;
    --bg-alt: #f3f4f6;
    --surface: #f4f5f7;
    --line-faint: #ebedf0;
    --surface-2: #eceef1;
    --surface-3: #e8eaee;
    --surface-4: #e2e5e9;
    --surface-5: #dde0e5;
    --border: #d5d9df;
    --line: #e0e3e7;
    --surface-6: #ced3da;
    --text-6: #b4bac2;
    --scroll: #c0c6cd;
    --text-5: #a3aab3;
    --text-4: #949ba5;
    --text-3: #868e99;
    --text-3b: #78808b;
    --text-2b: #727a85;
    --text-2: #646c77;
    --text-1c: #4a515b;
    --text-1b: #373e48;
    --text-1d: #353c46;
    --text: #22282f;
    --text-0b: #1b212a;
    --text-0: #141a22;
    --text-max: #0a0f16;
    --accent: #0b63d6;
    --on-accent: #ffffff;
    --shadow-text: rgba(255, 255, 255, .85);
    --scrim: rgba(15, 20, 28, .38);
    --shadow: rgba(15, 20, 28, .16);
    --flag-op: .72;
}

/* style.css hardcodes these and loads first. */
html, body {
    background-color: var(--bg);
    color: var(--text);
}

a {
    color: var(--accent);
}

hr {
    background-color: var(--line);
}

.left-col {
    background-color: var(--surface);
}

::-webkit-scrollbar-track {
    background: var(--surface-2);
}

::-webkit-scrollbar-thumb {
    background: var(--scroll);
}

/* Navigation rail, year cards, and the problem list.
 *
 * The only stylesheet we control: style.css lives on the server, predates this
 * repo and cannot be edited, so anything of its that has to change is
 * overridden from here. That is why some selectors below are heavier than they
 * would otherwise need to be, and why nav.css must stay after style.css in the
 * <head>.
 *
 * There is no top bar: the rail is the only chrome. It is pinned open above
 * 1200px and becomes a drawer over a dimmed page below that.
 *
 * Driven by a hidden checkbox, so no JavaScript is involved. The collapse
 * control lives inside the rail; a floating control appears over the content
 * when the rail is away. Both are <label for="nav">, as is the scrim, so
 * clicking outside the drawer closes it.
 *
 * The rail starts closed at every width, so the checkbox means one thing
 * throughout: checked is open. Above 1200px an open rail is pinned beside the
 * content; below, it floats over a dimmed page.
 *
 * No version to bump: build.py content-hashes this file into the URL it links,
 * so editing it busts the cache by itself. Only style.css, which lives on the
 * server, still needs STYLE_CSS_VERSION moved by hand.
 */

body {
    margin: 0;
}

.navtoggle {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

/* ---- controls ---- */

.iconbtn {
    cursor: pointer;
    width: 38px;
    height: 38px;
    border-radius: 9px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-2);
    font-size: 17px;
    transition: background .15s, color .15s;
    user-select: none;
    flex: none;
}

.iconbtn:hover {
    background: var(--surface-3);
    color: var(--text);
}

/* Panel-left glyph. Font Awesome 5 Free has no sidebar-toggle icon, so this is
   drawn here: a rounded rectangle with a divider. Masked rather than inlined so
   it picks up currentColor and follows the hover state. */
.iconbtn::before {
    content: "";
    width: 20px;
    height: 20px;
    background: currentColor;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='16' rx='2.5'/%3E%3Cline x1='9.5' y1='4' x2='9.5' y2='20'/%3E%3C/svg%3E") center / 20px no-repeat;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='16' rx='2.5'/%3E%3Cline x1='9.5' y1='4' x2='9.5' y2='20'/%3E%3C/svg%3E") center / 20px no-repeat;
}

/* Floating control over the content, shown whenever the rail is not there. */
.opener {
    position: fixed;
    top: 12px;
    left: 12px;
    z-index: 130;
    background: var(--surface);
    border: 1px solid var(--line);
    display: flex;
}

/* ---- the rail ---- */

aside.rail {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 260px;
    z-index: 110;
    background: var(--bg);
    border-right: 1px solid var(--line);
    display: flex;
    flex-direction: column;
    overflow-x: hidden;
    transform: translateX(-100%);
    transition: transform .22s ease;
}

.navtoggle:checked ~ .layout aside.rail {
    transform: none;
}

.railtop {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    /* Sits inside .railinner's 8px gutter, so 10px here puts the wordmark on
       the same 18px line as the item text below. */
    padding: 6px 0 4px 10px;
    flex: none;
}

/* Set all caps. The lowercase glyphs in this face are small capitals at 87% of
   cap height, so mixed case leaves a 19% step between the leading O and the
   letter beside it. Unaccented, unlike the main site's OLÍMPICOS -- these pages
   are in English and it matches the domain. */
.brandmark {
    color: var(--text);
    display: block;
    height: 22px;
    width: auto;
    flex: none;
}



/* Brand row, search and the switcher are all inside the scrolling area and all
   stick to the top of it as ONE block. Splitting them -- brand pinned outside,
   the rest sticky inside -- is what made them shift against each other while
   scrolling. One sticky element also avoids hard-coding each one's height as
   the next one's offset. */
.railhead {
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--bg);
}

/* Fixed width so the links do not re-wrap while the width animates. */
.railinner {
    width: 100%;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 0 8px;
}

.scrim {
    position: fixed;
    inset: 0;
    z-index: 100;
    background: var(--scrim);
    opacity: 0;
    pointer-events: none;
    transition: opacity .22s ease;
    cursor: pointer;
}

.navtoggle:checked ~ .scrim {
    opacity: 1;
    pointer-events: auto;
}

/* ---- rail contents ---- */

.subjbar {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
    padding: 2px 8px 16px;
}

/* Left aligned rather than centred: with the icons all one width the labels
   start on a common vertical line, so the column reads as a list instead of
   eight separately centred blobs. */
.subjbar .subj {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 8px;
    white-space: nowrap;
    font-size: 14px;
    padding: 8px 10px;
    border-radius: 8px;
    background: var(--surface);
    color: var(--text-2);
}

/* Solid weight, which the page already pays for: .railsearch::before pulls in
   fa-solid-900 for the magnifier, so these cost no extra request. Set on the
   pill rather than a wrapper element so the markup stays a bare span. */
.subjbar .subj::before {
    font-family: "Font Awesome 5 Free";
    font-weight: 900;
    font-size: 12px;
    width: 14px;
    text-align: center;
    flex: none;
}

.subjbar .subj[data-subj="Physics"]::before { content: "\f5d2"; }
.subjbar .subj[data-subj="Mathematics"]::before { content: "\f698"; }
.subjbar .subj[data-subj="Informatics"]::before { content: "\f121"; }
.subjbar .subj[data-subj="Chemistry"]::before { content: "\f0c3"; }
.subjbar .subj[data-subj="Biology"]::before { content: "\f471"; }
.subjbar .subj[data-subj="Astronomy"]::before { content: "\f753"; }
.subjbar .subj[data-subj="Science"]::before { content: "\f610"; }
.subjbar .subj[data-subj="Linguistics"]::before { content: "\f1ab"; }
.subjbar .subj[data-subj="Economics"]::before { content: "\f201"; }
.subjbar .subj[data-subj="Geography"]::before { content: "\f57d"; }

.subjbar .subj:hover {
    background: var(--surface-3);
}

.subjbar .subj.on {
    background: var(--accent);
    color: var(--on-accent);
    font-weight: 600;
}

.subjbar .subj.soon {
    color: var(--text-5);
    cursor: pointer;
}

.subjbar .subj.soon:hover {
    color: var(--text-2);
}

/* The 16px below has to match .subjbar's padding-bottom: the field should sit
   the same distance from the buttons above it as from the first category
   heading below. .cath has line-height 1 and no padding-top, so its box top is
   the cap line and the two numbers can be compared directly. */
.railsearch {
    --field: 36px;
    position: relative;
    padding: 0 8px 16px;
}

/* The header used to add its own 10px on top of the search block's, so the gap
   below the field was double the one above. Spacing between groups now lives on
   the group.

   It has to hang off the sibling combinator rather than :first-of-type. Every
   block opens with the .subjname div, so the first .catgrp is never the first
   div among its siblings and :first-of-type -- which keys on element type, not
   class -- matched nothing at all: the first group kept its 10px. */
.catgrp + .catgrp {
    padding-top: 13px;
}

.railsearch::before {
    font-family: "Font Awesome 5 Free";
    font-weight: 900;
    content: "\f002";
    position: absolute;
    left: 21px;
    /* Centred on the field, not on .railsearch: the block carries the 16px gap
       below it as padding, so a 50% offset lands well under the input. Matching
       the field's own height and centring inside that keeps the glyph put no
       matter what the spacing below becomes. */
    top: 0;
    height: var(--field);
    display: flex;
    align-items: center;
    font-size: 13px;
    color: var(--text-3);
    pointer-events: none;
}

/* Native search fields bring their own inner shadow and cancel button, which
   look wrong on a dark rail, so the appearance is reset and rebuilt. */
.railsearch input {
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    font-family: inherit;
    font-size: 15px;
    line-height: 1.2;
    box-sizing: border-box;
    height: var(--field);
    padding: 0 12px 0 32px;
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--text);
    border: 1px solid transparent;
    transition: background .15s, border-color .15s;
}

.railsearch input::placeholder {
    color: var(--text-3);
}

.railsearch input:hover {
    background: var(--surface-4);
}

.railsearch input:focus {
    outline: none;
    background: var(--bg);
    border-color: var(--accent);
}

.railsearch input::-webkit-search-cancel-button {
    -webkit-appearance: none;
    height: 13px;
    width: 13px;
    background: var(--text-3);
    cursor: pointer;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 4l8 8M12 4l-8 8' stroke='black' stroke-width='2'/%3E%3C/svg%3E") center/11px no-repeat;
}

.catgrp .cath {
    padding: 0 10px 4px;
    font-size: 11.5px;
    line-height: 1;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text-3);
}

/* Inset rounded rows rather than full-bleed with a left rule. */
aside.rail .exam {
    display: block;
    margin: 1px 0;
    padding: 7px 10px;
    border-radius: 8px;
    font-size: 15.5px;
    color: var(--text);
}

aside.rail a.exam:hover {
    background: var(--surface-3);
    color: var(--text);
}

aside.rail a.exam.on {
    background: var(--surface-5);
    color: var(--text-max);
    font-weight: 600;
}

aside.rail .exam.soon {
    color: var(--text-5);
    cursor: default;
}

aside.rail .exam.soon::after {
    content: " soon";
    font-size: 12px;
    color: var(--text-6);
}

/* Every subject is in the DOM but hidden, so search can reach across them
   without an index. The subject name is redundant when a single block is
   showing, so it only appears while searching. */
.subjname {
    display: none;
    padding: 10px 10px 2px;
    font-size: 12.5px;
    font-weight: 700;
    color: var(--accent);
    opacity: .8;
}

.raillist.searching .subjname {
    display: block;
}

.norail {
    padding: 10px 10px 12px;
    color: var(--text-3);
    font-size: 14px;
}

/* ---- page header ---- */
header.col-12 {
    margin: 30px 0 26px;
}

/* Bootstrap's hr margins are inherited; set them so the rhythm between year
   blocks is deliberate rather than whatever 1rem happens to give. */
main.col-12 hr {
    margin: 20px 0;
}

/* The 6px bottom margin here was holding the tagline off the title; with that
   gone it just made the block sit 8px lower than it looked. */
header.col-12 h1 {
    font-size: 34px;
    font-weight: 600;
    margin: 0;
}

/* A sentence or two saying what the competition is. Capped at a readable
   measure rather than the full container: centred text running the width of a
   1400px page is a paragraph nobody finishes. Quiet enough that the year cards
   below stay the thing the eye lands on -- this is context, not content. */
header.col-12 .about {
    max-width: 60ch;
    margin: 12px auto 0;
    font-size: 15px;
    line-height: 1.55;
    color: var(--text-2);
}

header.col-12 .about a {
    color: var(--text-1c);
    text-decoration: underline;
    text-underline-offset: 2px;
}

header.col-12 .about a:hover {
    color: var(--text-max);
}

/* Two lines: where the files came from, then who made it. The source stays on
   its own line because a few olympiads carry a sentence of explanation there. */
footer.col-12 {
    margin: 26px 0 30px;
}

footer .source,
footer .credits {
    font-size: 15px;
    line-height: 1.6;
    color: var(--text-2b);
}

footer .credits {
    margin-top: 2px;
}

footer .credits span {
    color: var(--text-5);
    padding: 0 3px;
}

/* ---- narrow: drawer ---- */
@media (max-width: 1199px) {
    .layout {
        display: block;
    }

    /* The floating control is the only way in, so it stays until the drawer
       is open. */
    .navtoggle:checked ~ .opener {
        display: none;
    }

    .content {
        padding-top: 10px;
    }
}

/* ---- wide: pinned ----
   A pinned rail cannot collapse with display:none, which is not animatable, so
   the width is animated instead and the content reflows alongside it. */
@media (min-width: 1200px) {
    .layout {
        display: flex;
        align-items: flex-start;
    }

    .content {
        flex: 1;
        min-width: 0;
    }

    /* Closed by default, same as narrow. The rail is pushed to zero width
       rather than display:none, which is not animatable. */
    aside.rail {
        position: sticky;
        top: 0;
        height: 100vh;
        width: 0;
        opacity: 0;
        border-right-color: transparent;
        transform: none;
        will-change: width;
        transition: width .26s cubic-bezier(.4, 0, .2, 1),
                    opacity .18s ease,
                    border-color .26s ease;
    }

    .navtoggle:checked ~ .layout aside.rail,
    html.nav-open .layout aside.rail {
        width: 260px;
        opacity: 1;
        border-right-color: var(--line);
    }

    .railinner {
        width: 260px;
    }

    .scrim {
        display: none;
    }

    .navtoggle:checked ~ .opener,
    html.nav-open .opener {
        display: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    aside.rail,
    .scrim {
        transition: none !important;
    }
}

/* ---- problem results ----
   The olympiad list answers "where do I go"; this answers "where is that
   problem". Loaded on the first keystroke, never on page load. */
.railprobs {
    display: none;
    border-top: 1px solid var(--line);
    margin-top: 10px;
    padding-top: 4px;
}

.raillist.searching ~ .railprobs {
    display: block;
}

.railprobs .ph {
    padding: 10px 10px 3px;
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text-3);
}

.railprobs a {
    display: block;
    margin: 1px 0;
    padding: 6px 10px;
    border-radius: 8px;
    color: var(--text);
}

.railprobs a:hover {
    background: var(--surface-3);
}

.railprobs .pt {
    display: block;
    font-size: 15px;
    line-height: 1.3;
}

.railprobs .pm {
    display: block;
    font-size: 12.5px;
    color: var(--text-3);
    margin-top: 1px;
}

.railprobs .pn {
    padding: 8px 10px 12px;
    color: var(--text-3);
    font-size: 13.5px;
}

/* ---- scrollbars ----
 * style.css sets a global 10px bar with a #3c3c3c track, lighter than the rail
 * it sits on. Styling ::-webkit-scrollbar at all also opts out of the overlay
 * scrollbars macOS would otherwise use, so the bar permanently occupies width
 * and the content shifts when it appears.
 *
 * The gutter is kept a constant width so nothing ever moves; only the thumb's
 * colour changes, so it fades in when you are actually using the area.
 */
.railinner {
    scrollbar-width: thin;
    scrollbar-color: transparent transparent;
}

.railinner:hover,
.railinner:focus-within {
    scrollbar-color: var(--scroll) transparent;
}

.railinner::-webkit-scrollbar {
    width: 8px;
}

.railinner::-webkit-scrollbar-track {
    background: transparent;
}

/* The transparent border plus background-clip leaves the thumb inset from the
   edge and rounded, rather than a full-width block. */
.railinner::-webkit-scrollbar-thumb {
    background: transparent;
    border: 2px solid transparent;
    border-radius: 999px;
    background-clip: content-box;
    transition: background-color .18s ease;
}

.railinner:hover::-webkit-scrollbar-thumb,
.railinner:focus-within::-webkit-scrollbar-thumb {
    background: var(--scroll);
    background-clip: content-box;
}

.railinner::-webkit-scrollbar-thumb:hover {
    background: var(--text-3);
    background-clip: content-box;
}

/* The page's own bar, toned down from the light track style.css gives it. */
body::-webkit-scrollbar {
    width: 10px;
}

body::-webkit-scrollbar-track {
    background: transparent;
}

body::-webkit-scrollbar-thumb {
    background: var(--surface-6);
    border: 2px solid transparent;
    border-radius: 999px;
    background-clip: content-box;
}

body::-webkit-scrollbar-thumb:hover {
    background: var(--scroll);
    background-clip: content-box;
}

/* ---- year cards (variant K) + flag chips (F5B) ----
 *
 * Desktop only. Below 992px the year panel is a full-width block above its
 * problems and the card framing would just add nesting to a narrow column, so
 * mobile keeps exactly what style.css already gives it.
 *
 * style.css lives only on the server and predates this repo, so these are
 * written here rather than edited there. That means overriding a few of its
 * declarations, which is why the specificity looks heavier than it needs to.
 */
@media (min-width: 992px) {
    /* The card edge separates the years now, so the rule between them is one
       divider too many. */
    main section > hr {
        display: none;
    }

    main article.row {
        background: var(--bg-alt);
        border-radius: 12px;
        margin-bottom: 22px;
        padding: 10px 0;
    }

    /* One step lighter than the card it sits in, so the year reads as inset
       rather than as another slab beside the problems. */
    main article.row .left-col {
        background-color: var(--surface-2);
        position: relative;
        overflow: hidden;
    }

    /* The year sits at the top of the panel, level with the first line of
       problems, and the flag has the bottom of the panel to itself. Centring
       it was fine while a year was a tall stack of full-width blocks; the grid
       made the cards short enough that the flag was crossing the country name.
       The floor is what a flag needs to sit under two lines of text: a year
       with one line of problems is shorter than that on its own. */
    main article.row .left-col {
        justify-content: flex-start;
        padding-top: 16px;
        min-height: 180px;
    }

    /* Above the flag, and legible even where a pale one sits behind them. */
    main article.row .left-col > p {
        position: relative;
        z-index: 1;
        text-shadow: 0 1px 6px var(--shadow-text);
    }

    /* Peeking out of the bottom-right corner, tilted.
       width:auto is deliberate: flags keep their natural aspect ratio, and
       Switzerland is square.
       clip-path is load-bearing rather than decorative -- at fractional pixel
       widths the SVG leaves a pale antialiased fringe along its edge, which is
       obvious against the dark panel. Insetting by a pixel eats the fringe,
       and the matching radius keeps the corners the border-radius rounded. */
    main article.row .left-col img.flag {
        position: absolute;
        right: -18px;
        bottom: -12px;
        /* Fixed, deliberately. Capping it against the panel height was tried
           and reverted: card heights vary with problem count, so a percentage
           made the same page show flags at several different sizes -- obvious
           on IJSO, where years have very different numbers of problems. One
           size everywhere reads as consistent, even where it fills a short
           card like a banner. */
        height: 100px;
        width: auto;
        border-radius: 10px;
        clip-path: inset(1px round 10px);
        transform: rotate(-8deg);
        opacity: var(--flag-op);
        box-shadow: 0 4px 16px var(--shadow);
    }
}

/* Decorative, and the panel is short here -- a tilted flag behind the year
   would be noise rather than charm. */
@media (max-width: 991px) {
    img.flag {
        display: none;
    }
}

/* ---- the problem list ----
 *
 * One band per round, in two views over ONE DOM. Cards makes a round a third
 * of the row; list makes it a full-width row with every title shown. Rendering
 * two markups would double the page weight, and the pages being tiny is what
 * makes the rail's prefetching feel instant, so the views are pure CSS over the
 * same elements and the switch only changes a class on <html>.
 *
 * style.css lives only on the server and predates this repo, so several rules
 * below override it from here rather than being fixed at the source. Those are
 * called out individually. This block must stay after the year-card block
 * above, which it also overrides at the same specificity.
 */

/* One type scale for both views. The grid and the bands had drifted -- 19px
   against 18px headings, and links a size apart. */
.rlabel {
    font-size: 18px;
    font-weight: 600;
    color: var(--text-0);
    line-height: 1.3;
    margin: 0;
}

.rcount {
    font-size: 13.5px;
    color: var(--text-3b);
    margin: 0;
}

/* min-width:0 so a long run of links can shrink instead of pushing the title
   out of its cell. */
.rlinks {
    display: flex;
    gap: 20px;
    align-items: baseline;
    flex-wrap: wrap;
    min-width: 0;
}

.rlinks a {
    font-size: 16px;
    white-space: nowrap;
}

.rlinks a::before {
    font-family: "Font Awesome 5 Free";
    font-weight: 900;
    margin-right: 8px;
    font-size: 14px;
}

.rlinks a.problem::before {
    content: "\f15c";
}

.rlinks a.solution::before {
    content: "\f0eb";
}

.none {
    color: var(--text-4);
    font-size: 14px;
    font-style: italic;
    white-space: nowrap;
}

/* ---- the year panel ----
 *
 * The min-height of 180px above gives a 100px flag room to sit under two lines
 * of text. That floor is what made a year with one row of problems render as a
 * 340px article holding 100px of content: the panel, not the problems, was
 * setting the height. The row is now as short as its content.
 *
 * The flag is NOT resized here, deliberately. It keeps the fixed 100px above at
 * every card height, so one page never shows two flag sizes. On a card only as
 * tall as its year and country the flag fills the panel like a banner and the
 * text sits over it -- that is the decided behaviour, not a bug, and the
 * text-shadow on .left-col > p is what keeps it legible. Do not "fix" it by
 * shrinking the flag or by sizing it as a percentage; both have been tried and
 * reverted. */
@media (min-width: 992px) {
    main article.row .left-col {
        min-height: 0;
    }

    /* The panel above also pins its text to the top with padding-top:16px,
       against the 5px style.css puts under .country. On a tall panel that reads
       as the year sitting level with the first line of problems; on a panel
       only as tall as its own text it read as a block shoved upwards against a
       big top margin. Matching the bottom padding to the top centres the short
       case without moving the year on the tall one. */
    main article.row .left-col {
        padding-bottom: 16px;
    }

    main article.row .left-col .country {
        padding-bottom: 0;
    }

    /* Symmetric: the card above has padding:10px 0, which put the first row of
       cells almost against the top edge. The bottom looked bigger than the top
       only because the 180px floor was padding the article out below the last
       row; with that gone this is even on both sides. */
    main article.row {
        padding: 14px 0;
    }
}

/* ---- list: bands, every title shown ---- */
.band {
    flex: 0 0 100%;
    max-width: 100%;
    min-width: 0;
    border-top: 1px solid var(--surface-2);
    padding: 13px 14px 11px 0;
}

.band:first-child {
    border-top: 0;
    padding-top: 2px;
}

.band:last-child {
    padding-bottom: 0;
}

.bandhead {
    display: flex;
    align-items: baseline;
    gap: 13px;
    flex-wrap: wrap;
    min-width: 0;
    text-align: left;
}

.bandhead .rlinks {
    margin: 0 0 0 auto;
    justify-content: flex-end;
}

/* text-align:left is load-bearing: style.css centres this whole column, so a
   wrapped title centres its second line under its first. */
.cols,
.rows {
    list-style: none;
    margin: 9px 0 0;
    padding: 0;
    text-align: left;
}

.cols {
    columns: 3;
    column-gap: 32px;
}

.cols li {
    font-size: 16px;
    line-height: 1.4;
    color: var(--text-1d);
    padding: 3px 0;
    break-inside: avoid;
    display: flex;
    gap: 10px;
}

/* A title under a shared paper has no links of its own to show, so the span is
   there only for the case where it does -- IOAA's Data Analysis, which shares a
   paper and publishes a solution per problem. */
.cols li .rlinks {
    display: none;
}

.cols li.own .rlinks {
    display: flex;
    margin-left: auto;
}

.rows li {
    display: flex;
    align-items: baseline;
    gap: 14px;
    padding: 7px 0;
    border-top: 1px solid var(--line-faint);
    min-width: 0;
    font-size: 16px;
}

.rows li:first-child {
    border-top: 0;
}

/* "Q1", not "1", everywhere the number is shown. On its own in a column a bare
   numeral is ambiguous -- it could be a year, a count, a rank -- and the letter
   costs one character. Cards hides the number entirely, so this lands on list
   view and on the rows a topic filter brings back. In CSS, not the data: the id
   underneath stays a plain number for sorting and anchors. */
.cols .n::before,
.rows .n::before {
    content: "Q";
}

.cols .n,
.rows .n {
    color: var(--text-3);
    /* 19px fitted two digits; the Q needs the room. */
    min-width: 26px;
    text-align: right;
    flex: none;
    font-size: 14px;
    font-variant-numeric: tabular-nums;
}

.rows .t {
    flex: 1 1 auto;
    min-width: 0;
    color: var(--text-0b);
    line-height: 1.35;
}

.rows .rlinks {
    margin: 0 0 0 auto;
    flex: none;
    justify-content: flex-end;
}

/* ---- cards: a real grid, not wrapped flex ----
 *
 * Flex wrapping sized each cell independently, so in a row of three a two-line
 * title pushed its own links down while its neighbours' stayed up. Grid rows are
 * one height by definition, and margin-top:auto then drops every cell's links
 * onto the same baseline.
 *
 * The gaps are row-gap/column-gap rather than per-cell padding, which is what
 * removes the dead strip under the last row: padding applies to every cell, a
 * gap only applies between them.
 *
 * display:contents on the band lets a per-problem round's rows become grid
 * cells themselves instead of stacking inside one third.
 */
html.v-cards .probs {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    column-gap: 26px;
    row-gap: 24px;
    margin: 0;
    align-items: stretch;
}

html.v-cards .cols {
    display: none;
}

html.v-cards .band {
    display: contents;
}

html.v-cards .bandhead {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    padding: 0;
    max-width: none;
}

/* A per-problem round's heading would only repeat what the cells beside it
   already say, and it would eat a third of the row to do it. */
html.v-cards .band.per > .bandhead {
    display: none;
}

html.v-cards .rows {
    display: contents;
}

html.v-cards .rows li {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    border-top: 0;
    padding: 0;
    font-size: inherit;
}

html.v-cards .rows li .n {
    display: none;
}

/* Same size and colour as .rlabel beside it, but NOT bold, and the weight is
   the whole distinction: a bold cell is a heading over several problems --
   "Theory", "Written Response Test", "Problems - 6 problems" -- and a regular
   one is a single problem's own name. In a row that mixes the two, that is the
   only cue telling you whether "Greenhouse Effect" names a paper or a problem.
   Cards only; in list a title sits in a row of its own and needs no such
   signal. */
html.v-cards .rows li .t {
    font-size: 18px;
    font-weight: 400;
    color: var(--text-0);
    line-height: 1.3;
    flex: 0 0 auto;
}

/* margin-top:auto is the whole trick: it pins links to the bottom of a cell
   that grid has already stretched to the row height. */
html.v-cards .bandhead .rlinks,
html.v-cards .rows li .rlinks {
    margin: auto 0 0;
    padding-top: 10px;
    justify-content: flex-start;
}

/* ...and a second auto margin above the title splits the leftover height in
   two: half above the title, half between the title and the links. A one-line
   title in a row whose tallest cell wraps to two therefore centres in the space
   over its links instead of hanging from the top with a hole under it.
   Do NOT swap this for justify-content:center -- auto margins are resolved
   before justify-content, so the links' margin-top:auto would eat all the free
   space and nothing would centre. */
html.v-cards .rows li .t,
html.v-cards .bandhead .rlabel {
    margin-top: auto;
}

/* Two lines, then an ellipsis. style.css gets this from .name being a
   fixed-height box, which these elements replaced and so lost. build.py pairs
   the clamp with a title attribute (TOOLTIP_OVER), which only makes sense
   because the visible text is clipped -- so the full title stays in the markup
   for Ctrl+F and for search. Cards only: in list view a title has the whole row
   and should not be cut. */
html.v-cards .rows li .t,
html.v-cards .bandhead .rlabel {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Links must not wrap inside a card. Wrapping was added to stop them
   overflowing into the neighbouring cell, but it trades an overlap for a taller
   cell: iGeo's "Problems + Marking scheme" wrapped while its neighbours did not,
   so the whole grid row lost its alignment. Keeping them on one line with a
   tighter gap fits every label we have. */
html.v-cards .rlinks {
    flex-wrap: nowrap;
    gap: 16px;
}

html.v-cards .rlinks a {
    white-space: nowrap;
}

/* ---- narrow: one column ----
 *
 * Below 992px the year panel is already a full-width block above its problems,
 * so the problems have the page width and nothing else. Three columns at that
 * width is ~110px a cell, and .rlinks a is white-space:nowrap, so the links
 * cannot fit and cannot wrap: measured at a 430px viewport with this override
 * removed, the document lays out 490px wide with 118 elements past the right
 * edge and "Problem"/"Solution" printed on top of each other. Both views
 * collapse to one column, which is also what makes them differ only in how much
 * they show rather than in how they fit.
 */
@media (max-width: 991px) {
    html.v-cards .probs {
        grid-template-columns: 1fr;
    }

    .cols {
        columns: 1;
    }

    /* .probs is a bootstrap .row, so it carries -15px side margins that a
       column's own 15px padding normally cancels. A band is not a column and
       has no such padding, which on a wide card only means the band starts
       level with the row -- but on a phone the row IS the page, so every title
       sat hard against the screen edge while the year panel above it was inset
       by 15px. Putting the gutter back on the band lines the two up. */
    .band {
        padding-left: 15px;
        padding-right: 15px;
    }
}

/* On a phone a title and its links no longer fit side by side, so the links
 * drop onto their own line under the title. Nothing overflows either way -- the
 * row shrinks the title instead -- but the links are a fixed cost repeated on
 * every row, and at 430px on iGeo they take 62% of it: "Fieldwork Exercise 1"
 * is squeezed into ~120px and breaks after "Fieldwork", while the two link
 * labels beside it sit on one comfortable line. The titles are the content.
 *
 * Not applied over the whole narrow range: a tablet row is wide enough for
 * both, and wrapping there only makes every problem twice as tall.
 *
 * The rule must not add a horizontal margin next to the 100% basis, hence
 * cancelling the auto margin .rows .rlinks carries for the wide case: a basis
 * of 100% plus a margin is wider than the row, and one element sticking out
 * sets the width of the whole document.
 *
 * List only, hence the :not(). In cards a row IS the cell -- flex-direction is
 * column there -- so wrapping it wraps along the vertical axis and starts a
 * second COLUMN, which put a cell's links to the right of its own title.
 * :not(.v-cards) rather than .v-list so it still holds if the boot script never
 * ran and no view class was set, which is the case list already renders. */
@media (max-width: 575px) {
    html:not(.v-cards) .rows li {
        flex-wrap: wrap;
    }

    html:not(.v-cards) .rows .rlinks {
        margin-left: 0;
        flex-basis: 100%;
    }
}

/* ---- the view switch ---- */
.viewbar {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 10px;
    margin: 0 0 16px;
}

/* The theme control. Round rather than a pill, so it reads as one button next
   to the two-position switch instead of a third view. The glyph is a mask on
   currentColor, like the rail's, which means the hover state colours it without
   a second copy of the icon. */
.modesw {
    width: 30px;
    height: 30px;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 99px;
    background: none;
    color: var(--text-2b);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    transition: background .15s, color .15s;
}

.modesw:hover {
    background: var(--surface-3);
    color: var(--text);
}

.modesw:focus {
    outline: none;
}

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

.modesw::before {
    /* Font Awesome 5 Free, the same library the subject chips and the
       problem/solution links already use, rather than a hand-drawn glyph that
       sits at a slightly different weight from everything around it. A sun
       while the page is dark and a moon while it is light: the icon is the
       destination, not the current state. */
    font-family: "Font Awesome 5 Free";
    font-weight: 900;
    font-size: 13px;
    line-height: 1;
    content: "\f185";
}

/* Same pairing as the palette: the media query covers "following a light
   system", html.light after it covers "pinned light". */
@media (prefers-color-scheme: light) {
    html:not(.dark) .modesw::before {
        content: "\f186";
    }
}

html.light .modesw::before {
    content: "\f186";
}

/* Cards hides the per-problem titles of a shared round, which is right until a
   filter is on: the card then says the year matched without saying what
   matched, and "which question is it" is the whole question a filter asks.
   While filtering, the matching rows come back in the space the count just
   vacated -- number and title, the same answer list gives, at a smaller size
   because the round heading above it is still the thing being read. */
html[class*="t-"].v-cards .cols {
    display: block;
    columns: 1;
    margin: 3px 0 0;
    padding: 0;
    list-style: none;
}

html[class*="t-"].v-cards .cols li {
    display: flex;
    gap: 8px;
    align-items: baseline;
    padding: 0;
    font-size: 13.5px;
    line-height: 1.45;
}

html[class*="t-"].v-cards .cols li .n {
    color: var(--text-3);
    font-variant-numeric: tabular-nums;
    flex: none;
}

html[class*="t-"].v-cards .cols li .t {
    color: var(--text-2);
    /* Not clamped: a filtered card shows one or two rows, so there is room, and
       a truncated answer to "which question" is not much of an answer. */
    -webkit-line-clamp: none;
    display: block;
}


/* ---- topic filter ----
 *
 * One topic at a time, set as a class on <html>, so this is pure CSS: rows that
 * do not carry the topic go, then any band left with none of them, then any
 * year left with no bands. :has() is what makes the last two possible without
 * JavaScript walking the tree.
 *
 * A problem with no data-t is untagged and disappears under every filter, which
 * is the honest behaviour: we do not know what it is, so we cannot claim it is
 * the thing you asked for.
 */
.topicsw {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 20px;
    margin-right: auto;
}

/* Words, not pills. Two rows of bordered chips was more chrome than a control
   most visitors never touch deserves, sitting directly between the title and
   the content. Weight and brightness carry the active state, which needs no
   extra colour and survives both themes unchanged. */
.topicsw button {
    font: inherit;
    font-size: 13.5px;
    padding: 5px 0;
    color: var(--text-4);
    background: none;
    border: 0;
    cursor: pointer;
    letter-spacing: .01em;
    transition: color .15s;
}

.topicsw button:hover {
    color: var(--text-1b);
}

.topicsw button:focus {
    outline: none;
}

/* Offset outwards: with no border or padding the ring would otherwise sit on
   the letters themselves. */
.topicsw button:focus-visible,
.subsw button:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    border-radius: 3px;
}

.topicsw button.on {
    color: var(--text-max);
    font-weight: 650;
}

/* The round header counts every problem in the round, which stops being true
   the moment a filter hides some of them. Rather than recount in JavaScript,
   the count goes away while filtering: the rows on screen are the answer. */
html[class*="t-"] .rcount {
    display: none;
}

html.t-mechanics .rows li:not([data-t~="mechanics"]),
html.t-mechanics .cols li:not([data-t~="mechanics"]) {
    display: none;
}

html.t-mechanics .band:not(:has(li[data-t~="mechanics"])),
html.t-mechanics article.row:not(:has(li[data-t~="mechanics"])) {
    display: none;
}

html.t-electromagnetism .rows li:not([data-t~="electromagnetism"]),
html.t-electromagnetism .cols li:not([data-t~="electromagnetism"]) {
    display: none;
}

html.t-electromagnetism .band:not(:has(li[data-t~="electromagnetism"])),
html.t-electromagnetism article.row:not(:has(li[data-t~="electromagnetism"])) {
    display: none;
}

html.t-thermodynamics .rows li:not([data-t~="thermodynamics"]),
html.t-thermodynamics .cols li:not([data-t~="thermodynamics"]) {
    display: none;
}

html.t-thermodynamics .band:not(:has(li[data-t~="thermodynamics"])),
html.t-thermodynamics article.row:not(:has(li[data-t~="thermodynamics"])) {
    display: none;
}

html.t-waves-optics .rows li:not([data-t~="waves-optics"]),
html.t-waves-optics .cols li:not([data-t~="waves-optics"]) {
    display: none;
}

html.t-waves-optics .band:not(:has(li[data-t~="waves-optics"])),
html.t-waves-optics article.row:not(:has(li[data-t~="waves-optics"])) {
    display: none;
}

html.t-modern-quantum .rows li:not([data-t~="modern-quantum"]),
html.t-modern-quantum .cols li:not([data-t~="modern-quantum"]) {
    display: none;
}

html.t-modern-quantum .band:not(:has(li[data-t~="modern-quantum"])),
html.t-modern-quantum article.row:not(:has(li[data-t~="modern-quantum"])) {
    display: none;
}

html.t-relativity .rows li:not([data-t~="relativity"]),
html.t-relativity .cols li:not([data-t~="relativity"]) {
    display: none;
}

html.t-relativity .band:not(:has(li[data-t~="relativity"])),
html.t-relativity article.row:not(:has(li[data-t~="relativity"])) {
    display: none;
}

html.t-astrophysics .rows li:not([data-t~="astrophysics"]),
html.t-astrophysics .cols li:not([data-t~="astrophysics"]) {
    display: none;
}

html.t-astrophysics .band:not(:has(li[data-t~="astrophysics"])),
html.t-astrophysics article.row:not(:has(li[data-t~="astrophysics"])) {
    display: none;
}

/* Cards makes a band display:contents so its heading is a grid cell of its own,
   which split heading from titles across two columns. A shared band that has a
   match becomes one cell again. Written per topic and gated on :has() so it
   cannot outrank the rule that hides a band with no match -- which is exactly
   what the earlier, broader version did. */
html.t-mechanics.v-cards .band:not(.per):has(li[data-t~="mechanics"]) {
    display: flex;
    flex-direction: column;
}

html.t-electromagnetism.v-cards .band:not(.per):has(li[data-t~="electromagnetism"]) {
    display: flex;
    flex-direction: column;
}

html.t-thermodynamics.v-cards .band:not(.per):has(li[data-t~="thermodynamics"]) {
    display: flex;
    flex-direction: column;
}

html.t-waves-optics.v-cards .band:not(.per):has(li[data-t~="waves-optics"]) {
    display: flex;
    flex-direction: column;
}

html.t-modern-quantum.v-cards .band:not(.per):has(li[data-t~="modern-quantum"]) {
    display: flex;
    flex-direction: column;
}

html.t-relativity.v-cards .band:not(.per):has(li[data-t~="relativity"]) {
    display: flex;
    flex-direction: column;
}

html.t-astrophysics.v-cards .band:not(.per):has(li[data-t~="astrophysics"]) {
    display: flex;
    flex-direction: column;
}

/* Order inside a filtered card: heading, what matched, then the links -- which
   is where the unfiltered card already puts them, pinned to the bottom.
   The links live inside .bandhead, so they cannot be reordered against .cols
   from where they sit. Making .bandhead `display: contents` while filtering
   dissolves it, promoting the label and the links to children of the band,
   which is the flex container doing the ordering. No markup change needed.
   .rcount is already hidden while filtering, so it takes no order. */
/* .band carries a rule and 13px of padding for list view, where bands stack and
   need separating. In cards that is inert, because the band is display:contents
   and has no box -- until filtering gives it one, at which point the list-view
   padding came back and pushed the second band in a row 11px below the first,
   labels, links and all. Cards never wants either. */
html[class*="t-"].v-cards .band:not(.per) {
    border-top: 0;
    padding: 0;
}

html[class*="t-"].v-cards .band:not(.per) > .bandhead {
    display: contents;
}

html[class*="t-"].v-cards .band:not(.per) > .bandhead > .rlabel {
    order: 0;
}

html[class*="t-"].v-cards .band:not(.per) > .cols {
    order: 1;
}

html[class*="t-"].v-cards .band:not(.per) > .bandhead > .rlinks {
    order: 2;
    margin-top: auto;
    padding-top: 6px;
}


/* ---- subtopic chips ----
 *
 * Rendered once, all of them, and revealed by CSS under their own topic. That
 * keeps topic switching free of DOM work, and means the second row simply does
 * not exist until a topic narrows it to something worth reading.
 *
 * The hiding rules mirror the topic ones exactly -- rows, then empty bands,
 * then empty years -- because a subtopic filter is the same operation on a
 * different attribute. */
.subsw {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 2px 16px;
    margin: 2px 0 16px;
}

/* A step quieter than the topics, and the active one takes the accent rather
   than more weight: at this size bold alone did not read as chosen. */
.subsw button {
    display: none;
    font: inherit;
    font-size: 12.5px;
    padding: 3px 0;
    color: var(--text-5);
    background: none;
    border: 0;
    cursor: pointer;
    transition: color .15s;
}

.subsw button:hover {
    color: var(--text-2);
}

.subsw button:focus {
    outline: none;
}

.subsw button.on {
    color: var(--accent);
    font-weight: 600;
}

html.t-mechanics .subsw button[data-of="mechanics"] { display: inline-flex; }
html.t-electromagnetism .subsw button[data-of="electromagnetism"] { display: inline-flex; }
html.t-thermodynamics .subsw button[data-of="thermodynamics"] { display: inline-flex; }
html.t-waves-optics .subsw button[data-of="waves-optics"] { display: inline-flex; }
html.t-modern-quantum .subsw button[data-of="modern-quantum"] { display: inline-flex; }
html.t-relativity .subsw button[data-of="relativity"] { display: inline-flex; }
html.t-astrophysics .subsw button[data-of="astrophysics"] { display: inline-flex; }

html.s-kinematics .rows li:not([data-s~="kinematics"]),
html.s-kinematics .cols li:not([data-s~="kinematics"]) {
    display: none;
}

html.s-kinematics .band:not(:has(li[data-s~="kinematics"])),
html.s-kinematics article.row:not(:has(li[data-s~="kinematics"])) {
    display: none;
}

html.s-kinematics.v-cards .band:not(.per):has(li[data-s~="kinematics"]) {
    display: flex;
    flex-direction: column;
}

html.s-dynamics .rows li:not([data-s~="dynamics"]),
html.s-dynamics .cols li:not([data-s~="dynamics"]) {
    display: none;
}

html.s-dynamics .band:not(:has(li[data-s~="dynamics"])),
html.s-dynamics article.row:not(:has(li[data-s~="dynamics"])) {
    display: none;
}

html.s-dynamics.v-cards .band:not(.per):has(li[data-s~="dynamics"]) {
    display: flex;
    flex-direction: column;
}

html.s-rigid-body .rows li:not([data-s~="rigid-body"]),
html.s-rigid-body .cols li:not([data-s~="rigid-body"]) {
    display: none;
}

html.s-rigid-body .band:not(:has(li[data-s~="rigid-body"])),
html.s-rigid-body article.row:not(:has(li[data-s~="rigid-body"])) {
    display: none;
}

html.s-rigid-body.v-cards .band:not(.per):has(li[data-s~="rigid-body"]) {
    display: flex;
    flex-direction: column;
}

html.s-fluids .rows li:not([data-s~="fluids"]),
html.s-fluids .cols li:not([data-s~="fluids"]) {
    display: none;
}

html.s-fluids .band:not(:has(li[data-s~="fluids"])),
html.s-fluids article.row:not(:has(li[data-s~="fluids"])) {
    display: none;
}

html.s-fluids.v-cards .band:not(.per):has(li[data-s~="fluids"]) {
    display: flex;
    flex-direction: column;
}

html.s-gravitation .rows li:not([data-s~="gravitation"]),
html.s-gravitation .cols li:not([data-s~="gravitation"]) {
    display: none;
}

html.s-gravitation .band:not(:has(li[data-s~="gravitation"])),
html.s-gravitation article.row:not(:has(li[data-s~="gravitation"])) {
    display: none;
}

html.s-gravitation.v-cards .band:not(.per):has(li[data-s~="gravitation"]) {
    display: flex;
    flex-direction: column;
}

html.s-statics .rows li:not([data-s~="statics"]),
html.s-statics .cols li:not([data-s~="statics"]) {
    display: none;
}

html.s-statics .band:not(:has(li[data-s~="statics"])),
html.s-statics article.row:not(:has(li[data-s~="statics"])) {
    display: none;
}

html.s-statics.v-cards .band:not(.per):has(li[data-s~="statics"]) {
    display: flex;
    flex-direction: column;
}

html.s-electrostatics .rows li:not([data-s~="electrostatics"]),
html.s-electrostatics .cols li:not([data-s~="electrostatics"]) {
    display: none;
}

html.s-electrostatics .band:not(:has(li[data-s~="electrostatics"])),
html.s-electrostatics article.row:not(:has(li[data-s~="electrostatics"])) {
    display: none;
}

html.s-electrostatics.v-cards .band:not(.per):has(li[data-s~="electrostatics"]) {
    display: flex;
    flex-direction: column;
}

html.s-circuits .rows li:not([data-s~="circuits"]),
html.s-circuits .cols li:not([data-s~="circuits"]) {
    display: none;
}

html.s-circuits .band:not(:has(li[data-s~="circuits"])),
html.s-circuits article.row:not(:has(li[data-s~="circuits"])) {
    display: none;
}

html.s-circuits.v-cards .band:not(.per):has(li[data-s~="circuits"]) {
    display: flex;
    flex-direction: column;
}

html.s-magnetostatics .rows li:not([data-s~="magnetostatics"]),
html.s-magnetostatics .cols li:not([data-s~="magnetostatics"]) {
    display: none;
}

html.s-magnetostatics .band:not(:has(li[data-s~="magnetostatics"])),
html.s-magnetostatics article.row:not(:has(li[data-s~="magnetostatics"])) {
    display: none;
}

html.s-magnetostatics.v-cards .band:not(.per):has(li[data-s~="magnetostatics"]) {
    display: flex;
    flex-direction: column;
}

html.s-induction .rows li:not([data-s~="induction"]),
html.s-induction .cols li:not([data-s~="induction"]) {
    display: none;
}

html.s-induction .band:not(:has(li[data-s~="induction"])),
html.s-induction article.row:not(:has(li[data-s~="induction"])) {
    display: none;
}

html.s-induction.v-cards .band:not(.per):has(li[data-s~="induction"]) {
    display: flex;
    flex-direction: column;
}

html.s-em-waves .rows li:not([data-s~="em-waves"]),
html.s-em-waves .cols li:not([data-s~="em-waves"]) {
    display: none;
}

html.s-em-waves .band:not(:has(li[data-s~="em-waves"])),
html.s-em-waves article.row:not(:has(li[data-s~="em-waves"])) {
    display: none;
}

html.s-em-waves.v-cards .band:not(.per):has(li[data-s~="em-waves"]) {
    display: flex;
    flex-direction: column;
}

html.s-heat-transfer .rows li:not([data-s~="heat-transfer"]),
html.s-heat-transfer .cols li:not([data-s~="heat-transfer"]) {
    display: none;
}

html.s-heat-transfer .band:not(:has(li[data-s~="heat-transfer"])),
html.s-heat-transfer article.row:not(:has(li[data-s~="heat-transfer"])) {
    display: none;
}

html.s-heat-transfer.v-cards .band:not(.per):has(li[data-s~="heat-transfer"]) {
    display: flex;
    flex-direction: column;
}

html.s-gas-laws .rows li:not([data-s~="gas-laws"]),
html.s-gas-laws .cols li:not([data-s~="gas-laws"]) {
    display: none;
}

html.s-gas-laws .band:not(:has(li[data-s~="gas-laws"])),
html.s-gas-laws article.row:not(:has(li[data-s~="gas-laws"])) {
    display: none;
}

html.s-gas-laws.v-cards .band:not(.per):has(li[data-s~="gas-laws"]) {
    display: flex;
    flex-direction: column;
}

html.s-entropy .rows li:not([data-s~="entropy"]),
html.s-entropy .cols li:not([data-s~="entropy"]) {
    display: none;
}

html.s-entropy .band:not(:has(li[data-s~="entropy"])),
html.s-entropy article.row:not(:has(li[data-s~="entropy"])) {
    display: none;
}

html.s-entropy.v-cards .band:not(.per):has(li[data-s~="entropy"]) {
    display: flex;
    flex-direction: column;
}

html.s-kinetic-theory .rows li:not([data-s~="kinetic-theory"]),
html.s-kinetic-theory .cols li:not([data-s~="kinetic-theory"]) {
    display: none;
}

html.s-kinetic-theory .band:not(:has(li[data-s~="kinetic-theory"])),
html.s-kinetic-theory article.row:not(:has(li[data-s~="kinetic-theory"])) {
    display: none;
}

html.s-kinetic-theory.v-cards .band:not(.per):has(li[data-s~="kinetic-theory"]) {
    display: flex;
    flex-direction: column;
}

html.s-phase-transitions .rows li:not([data-s~="phase-transitions"]),
html.s-phase-transitions .cols li:not([data-s~="phase-transitions"]) {
    display: none;
}

html.s-phase-transitions .band:not(:has(li[data-s~="phase-transitions"])),
html.s-phase-transitions article.row:not(:has(li[data-s~="phase-transitions"])) {
    display: none;
}

html.s-phase-transitions.v-cards .band:not(.per):has(li[data-s~="phase-transitions"]) {
    display: flex;
    flex-direction: column;
}

html.s-statistical-mechanics .rows li:not([data-s~="statistical-mechanics"]),
html.s-statistical-mechanics .cols li:not([data-s~="statistical-mechanics"]) {
    display: none;
}

html.s-statistical-mechanics .band:not(:has(li[data-s~="statistical-mechanics"])),
html.s-statistical-mechanics article.row:not(:has(li[data-s~="statistical-mechanics"])) {
    display: none;
}

html.s-statistical-mechanics.v-cards .band:not(.per):has(li[data-s~="statistical-mechanics"]) {
    display: flex;
    flex-direction: column;
}

html.s-oscillations .rows li:not([data-s~="oscillations"]),
html.s-oscillations .cols li:not([data-s~="oscillations"]) {
    display: none;
}

html.s-oscillations .band:not(:has(li[data-s~="oscillations"])),
html.s-oscillations article.row:not(:has(li[data-s~="oscillations"])) {
    display: none;
}

html.s-oscillations.v-cards .band:not(.per):has(li[data-s~="oscillations"]) {
    display: flex;
    flex-direction: column;
}

html.s-wave-propagation .rows li:not([data-s~="wave-propagation"]),
html.s-wave-propagation .cols li:not([data-s~="wave-propagation"]) {
    display: none;
}

html.s-wave-propagation .band:not(:has(li[data-s~="wave-propagation"])),
html.s-wave-propagation article.row:not(:has(li[data-s~="wave-propagation"])) {
    display: none;
}

html.s-wave-propagation.v-cards .band:not(.per):has(li[data-s~="wave-propagation"]) {
    display: flex;
    flex-direction: column;
}

html.s-sound .rows li:not([data-s~="sound"]),
html.s-sound .cols li:not([data-s~="sound"]) {
    display: none;
}

html.s-sound .band:not(:has(li[data-s~="sound"])),
html.s-sound article.row:not(:has(li[data-s~="sound"])) {
    display: none;
}

html.s-sound.v-cards .band:not(.per):has(li[data-s~="sound"]) {
    display: flex;
    flex-direction: column;
}

html.s-interference .rows li:not([data-s~="interference"]),
html.s-interference .cols li:not([data-s~="interference"]) {
    display: none;
}

html.s-interference .band:not(:has(li[data-s~="interference"])),
html.s-interference article.row:not(:has(li[data-s~="interference"])) {
    display: none;
}

html.s-interference.v-cards .band:not(.per):has(li[data-s~="interference"]) {
    display: flex;
    flex-direction: column;
}

html.s-diffraction .rows li:not([data-s~="diffraction"]),
html.s-diffraction .cols li:not([data-s~="diffraction"]) {
    display: none;
}

html.s-diffraction .band:not(:has(li[data-s~="diffraction"])),
html.s-diffraction article.row:not(:has(li[data-s~="diffraction"])) {
    display: none;
}

html.s-diffraction.v-cards .band:not(.per):has(li[data-s~="diffraction"]) {
    display: flex;
    flex-direction: column;
}

html.s-geometrical-optics .rows li:not([data-s~="geometrical-optics"]),
html.s-geometrical-optics .cols li:not([data-s~="geometrical-optics"]) {
    display: none;
}

html.s-geometrical-optics .band:not(:has(li[data-s~="geometrical-optics"])),
html.s-geometrical-optics article.row:not(:has(li[data-s~="geometrical-optics"])) {
    display: none;
}

html.s-geometrical-optics.v-cards .band:not(.per):has(li[data-s~="geometrical-optics"]) {
    display: flex;
    flex-direction: column;
}

html.s-polarization .rows li:not([data-s~="polarization"]),
html.s-polarization .cols li:not([data-s~="polarization"]) {
    display: none;
}

html.s-polarization .band:not(:has(li[data-s~="polarization"])),
html.s-polarization article.row:not(:has(li[data-s~="polarization"])) {
    display: none;
}

html.s-polarization.v-cards .band:not(.per):has(li[data-s~="polarization"]) {
    display: flex;
    flex-direction: column;
}

html.s-atomic .rows li:not([data-s~="atomic"]),
html.s-atomic .cols li:not([data-s~="atomic"]) {
    display: none;
}

html.s-atomic .band:not(:has(li[data-s~="atomic"])),
html.s-atomic article.row:not(:has(li[data-s~="atomic"])) {
    display: none;
}

html.s-atomic.v-cards .band:not(.per):has(li[data-s~="atomic"]) {
    display: flex;
    flex-direction: column;
}

html.s-nuclear .rows li:not([data-s~="nuclear"]),
html.s-nuclear .cols li:not([data-s~="nuclear"]) {
    display: none;
}

html.s-nuclear .band:not(:has(li[data-s~="nuclear"])),
html.s-nuclear article.row:not(:has(li[data-s~="nuclear"])) {
    display: none;
}

html.s-nuclear.v-cards .band:not(.per):has(li[data-s~="nuclear"]) {
    display: flex;
    flex-direction: column;
}

html.s-particle .rows li:not([data-s~="particle"]),
html.s-particle .cols li:not([data-s~="particle"]) {
    display: none;
}

html.s-particle .band:not(:has(li[data-s~="particle"])),
html.s-particle article.row:not(:has(li[data-s~="particle"])) {
    display: none;
}

html.s-particle.v-cards .band:not(.per):has(li[data-s~="particle"]) {
    display: flex;
    flex-direction: column;
}

html.s-solid-state .rows li:not([data-s~="solid-state"]),
html.s-solid-state .cols li:not([data-s~="solid-state"]) {
    display: none;
}

html.s-solid-state .band:not(:has(li[data-s~="solid-state"])),
html.s-solid-state article.row:not(:has(li[data-s~="solid-state"])) {
    display: none;
}

html.s-solid-state.v-cards .band:not(.per):has(li[data-s~="solid-state"]) {
    display: flex;
    flex-direction: column;
}

html.s-quantum-mechanics .rows li:not([data-s~="quantum-mechanics"]),
html.s-quantum-mechanics .cols li:not([data-s~="quantum-mechanics"]) {
    display: none;
}

html.s-quantum-mechanics .band:not(:has(li[data-s~="quantum-mechanics"])),
html.s-quantum-mechanics article.row:not(:has(li[data-s~="quantum-mechanics"])) {
    display: none;
}

html.s-quantum-mechanics.v-cards .band:not(.per):has(li[data-s~="quantum-mechanics"]) {
    display: flex;
    flex-direction: column;
}

html.s-special-relativity .rows li:not([data-s~="special-relativity"]),
html.s-special-relativity .cols li:not([data-s~="special-relativity"]) {
    display: none;
}

html.s-special-relativity .band:not(:has(li[data-s~="special-relativity"])),
html.s-special-relativity article.row:not(:has(li[data-s~="special-relativity"])) {
    display: none;
}

html.s-special-relativity.v-cards .band:not(.per):has(li[data-s~="special-relativity"]) {
    display: flex;
    flex-direction: column;
}

html.s-general-relativity .rows li:not([data-s~="general-relativity"]),
html.s-general-relativity .cols li:not([data-s~="general-relativity"]) {
    display: none;
}

html.s-general-relativity .band:not(:has(li[data-s~="general-relativity"])),
html.s-general-relativity article.row:not(:has(li[data-s~="general-relativity"])) {
    display: none;
}

html.s-general-relativity.v-cards .band:not(.per):has(li[data-s~="general-relativity"]) {
    display: flex;
    flex-direction: column;
}

html.s-stellar .rows li:not([data-s~="stellar"]),
html.s-stellar .cols li:not([data-s~="stellar"]) {
    display: none;
}

html.s-stellar .band:not(:has(li[data-s~="stellar"])),
html.s-stellar article.row:not(:has(li[data-s~="stellar"])) {
    display: none;
}

html.s-stellar.v-cards .band:not(.per):has(li[data-s~="stellar"]) {
    display: flex;
    flex-direction: column;
}

html.s-orbital-mechanics .rows li:not([data-s~="orbital-mechanics"]),
html.s-orbital-mechanics .cols li:not([data-s~="orbital-mechanics"]) {
    display: none;
}

html.s-orbital-mechanics .band:not(:has(li[data-s~="orbital-mechanics"])),
html.s-orbital-mechanics article.row:not(:has(li[data-s~="orbital-mechanics"])) {
    display: none;
}

html.s-orbital-mechanics.v-cards .band:not(.per):has(li[data-s~="orbital-mechanics"]) {
    display: flex;
    flex-direction: column;
}

html.s-cosmology .rows li:not([data-s~="cosmology"]),
html.s-cosmology .cols li:not([data-s~="cosmology"]) {
    display: none;
}

html.s-cosmology .band:not(:has(li[data-s~="cosmology"])),
html.s-cosmology article.row:not(:has(li[data-s~="cosmology"])) {
    display: none;
}

html.s-cosmology.v-cards .band:not(.per):has(li[data-s~="cosmology"]) {
    display: flex;
    flex-direction: column;
}

html.s-observational .rows li:not([data-s~="observational"]),
html.s-observational .cols li:not([data-s~="observational"]) {
    display: none;
}

html.s-observational .band:not(:has(li[data-s~="observational"])),
html.s-observational article.row:not(:has(li[data-s~="observational"])) {
    display: none;
}

html.s-observational.v-cards .band:not(.per):has(li[data-s~="observational"]) {
    display: flex;
    flex-direction: column;
}

.viewsw {
    display: inline-flex;
    border: 1px solid var(--border);
    border-radius: 99px;
    overflow: hidden;
}

.viewsw button {
    font: inherit;
    font-size: 12.5px;
    padding: 5px 15px;
    color: var(--text-2b);
    background: none;
    border: 0;
    cursor: pointer;
    letter-spacing: .02em;
    border-radius: 99px;
}

.viewsw button:hover {
    color: var(--text-1b);
}

/* The browser's default focus ring is a rectangle, so on click it drew a blue
   box inside the pill. :focus-visible keeps the ring for keyboard users, where
   it is the only way to see where you are, and drops it for a mouse click,
   which does not need it. The radius above makes it follow the pill. */
.viewsw button:focus {
    outline: none;
}

.viewsw button:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -3px;
}

/* Keyed off the class BOOT sets on <html> before first paint, not off a class
   the view script adds afterwards: the switch must never render with neither
   side selected, because there is always a view on screen.
   :not(.v-cards) for the List side, mirroring the layout rule above, so the
   two agree even when the boot script never runs and the page falls back to
   List with no class on <html> at all. */
html.v-cards .viewsw button[data-v="cards"],
html:not(.v-cards) .viewsw button[data-v="list"] {
    background: var(--border);
    color: var(--text-max);
}

/* ---- generated titles ----
 *
 * 36% of problems are published with a slot number and no name, so the title
 * shown for them is ours. The mark says so. It is deliberately quiet: the point
 * is that a reader who cares can tell, not that every row shouts about it.
 *
 * Nudged up and set smaller rather than made a true <sup>, which would push the
 * line box taller and break the tight rhythm of the card lists. Not selectable,
 * so copying a title copies the title.
 */
.gen {
    font-size: .8em;
    vertical-align: .35em;
    line-height: 0;
    margin-left: .12em;
    color: var(--text-3);
    -webkit-user-select: none;
    user-select: none;
}

/* In the footer the mark is the subject of the sentence, not an annotation on
 * something else, so it sits on the baseline and takes the text's own colour. */
footer .gen {
    vertical-align: baseline;
    font-size: 1em;
    margin-left: 0;
    color: inherit;
}
