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

/* ---- search filters ----
   A filter button beside the field, a popover to set the filters in, and the
   applied ones as removable pills under the field. The pills are the state:
   the search starts scoped to this page's olympiad and says so with a pill
   rather than silently, and removing it widens the search to the subject.

   The popover is not in here. .railinner scrolls, and a box with one axis set
   to auto computes the other to auto as well, so anything absolutely
   positioned inside it is either clipped or grows a horizontal scrollbar. The
   script puts the popover on <body> and positions it against the button. */
.railsearch {
    display: flex;
    align-items: flex-start;
    gap: 6px;
}

.railsearch input {
    flex: 1 1 auto;
    min-width: 0;
}

/* Same field, square: same height, same surface, same focus treatment. The
   glyph is the one Font Awesome already loads for the magnifier, so it costs
   no request. */
.fbtn {
    flex: none;
    width: var(--field);
    height: var(--field);
    padding: 0;
    border: 1px solid transparent;
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--text-3);
    cursor: pointer;
    font-family: "Font Awesome 5 Free";
    font-weight: 900;
    font-size: 13px;
    line-height: 1;
    transition: background .15s, border-color .15s, color .15s;
}

.fbtn::before {
    content: "\f0b0";
}

.fbtn:hover {
    background: var(--surface-4);
    color: var(--text);
}

.fbtn.on {
    background: var(--bg);
    border-color: var(--accent);
    color: var(--text);
}

.fbtn:focus-visible,
.railfilters .fpill:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* The pills sit in the sticky header under the field, and the block is not a
   sibling of .raillist any more, so `.raillist.searching ~ ...` cannot reveal
   it; the script sets .ready on the same condition. The negative pull eats the
   field block's bottom padding, which is spacing before the list rather than
   before this. */
.railfilters {
    display: none;
    margin-top: -10px;
    padding: 0 8px 10px;
}

.railfilters.ready {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

.railfilters .fpill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    max-width: 100%;
    padding: 2px 7px 2px 8px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--text-1b);
    font: inherit;
    font-size: 11.5px;
    line-height: 1.6;
    cursor: pointer;
    transition: background .15s, border-color .15s, color .15s;
}

.railfilters .fpill span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The × is the whole point of the pill -- it is a control, not a label -- so
   it brightens with the pill rather than sitting quietly beside it. */
.railfilters .fpill::after {
    content: "\00d7";
    font-size: 13px;
    line-height: 1;
    color: var(--text-4);
}

.railfilters .fpill:hover {
    border-color: var(--text-4);
    color: var(--text-max);
}

.railfilters .fpill:hover::after {
    color: var(--text-1b);
}

/* The scope pill nobody asked for. It is applied before the reader has
   touched anything, so it is drawn as a default rather than as a choice: no
   fill, dashed edge. It solidifies the moment the olympiad is set by hand. */
.railfilters .fpill.auto {
    background: none;
    border-style: dashed;
    color: var(--text-3);
}

/* ---- the filter popover ----
   On <body>, fixed, positioned against the button by the script. Above the
   rail's own z-index of 110. */
.fpop {
    position: fixed;
    z-index: 130;
    width: 268px;
    max-width: calc(100vw - 24px);
    box-sizing: border-box;
    padding: 12px;
    display: grid;
    gap: 10px;
    background: var(--bg-alt);
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 10px 30px var(--shadow);
}

.fpop[hidden] {
    display: none;
}

/* The drawer form: in the rail, after the header, pushing the results down
   instead of sitting on top of them. Static, so nothing here is at risk of
   being clipped by .railinner's scrolling. */
.fpop.inline {
    position: static;
    width: auto;
    max-width: none;
    margin: 0 8px 12px;
    box-shadow: none;
}

.fpop label {
    display: grid;
    gap: 4px;
    min-width: 0;
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text-3);
}

.fpop .frow {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

/* Native selects and number inputs bring a platform arrow and a spinner that
   look wrong here, the same objection .railsearch input already answers for
   the search field. Reset and rebuilt to match it: one height, one radius,
   one focus treatment across all three. */
.fpop select,
.fpop input {
    -webkit-appearance: none;
    -moz-appearance: textfield;
    appearance: none;
    box-sizing: border-box;
    width: 100%;
    min-width: 0;
    height: 32px;
    padding: 0 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface-2);
    color: var(--text);
    font-family: inherit;
    font-size: 13.5px;
    line-height: 1.2;
    letter-spacing: normal;
    text-transform: none;
    transition: background .15s, border-color .15s;
}

.fpop select {
    padding-right: 26px;
}

.fpop select:hover,
.fpop input:hover {
    background: var(--surface-4);
}

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

.fpop input::-webkit-outer-spin-button,
.fpop input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* The chevron the reset removed, drawn back on the wrapper. Masked rather than
   coloured into the SVG so it follows the theme, the technique the search
   field's cancel button already uses. */
.fsel {
    position: relative;
    display: block;
    min-width: 0;
}

.fsel::after {
    content: "";
    position: absolute;
    right: 10px;
    top: 50%;
    width: 9px;
    height: 6px;
    transform: translateY(-50%);
    background: var(--text-3);
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E") center/9px no-repeat;
    pointer-events: none;
}

.fpop .fdash {
    color: var(--text-4);
    flex: none;
}

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

/* The line of the problem's own text that matched, shown only when the hit was
   on the text rather than on the title -- otherwise the reader is looking at a
   title that has nothing to do with what they typed. Clamped to two lines: it
   is evidence, not the problem. */
.railprobs .px {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
    font-size: 12.5px;
    line-height: 1.35;
    color: var(--text-2);
    margin-top: 2px;
}

/* Brightness and weight, not a highlighter block -- the same idiom the topic
   switch uses for its active state. Both halves have to do work: in the
   excerpt the surrounding text is already dimmed, so lifting the match to
   --text is contrast enough, but a title is drawn at --text already and the
   weight would be carrying it alone. A title matched end to end -- searching
   "black box" and finding "Black Box" -- would then be indistinguishable from
   a title that happened to be bold. */
/* padding:0 because Bootstrap pads mark by .1875em, which is invisible on one
   highlight and absurd on several: a subsequence match on "blkbx" draws four
   of them, and "Blackbody" rendered as "Bl ac kb ody". */
.railprobs mark {
    background: none;
    padding: 0;
    color: var(--text);
    font-weight: 600;
}

.railprobs .pt mark {
    color: var(--text-max);
}

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

/* ---- the sitting a set of bands belongs to ---- */

/* A stage is a level above a round, so it cannot be styled like one: OPhO's
   "Open" and "Invitational" sit over "Theory" and "Experimental", and at the
   same weight the four read as one list of four. Small caps and a rule running
   to the edge say "everything under here", where a band heading says "these
   problems". Only stages the data names are drawn -- one sitting emits nothing
   and the edition is laid out exactly as it was. */
.stage {
    flex: 0 0 100%;
    max-width: 100%;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 30px 0 0;
    /* The bands below keep a 14px right gutter, and without the same one here
       the rule ran 14px past the "Solutions" link it sits over -- ending in
       empty space instead of level with the block it heads. */
    padding-right: 14px;
}

.stage:first-child {
    margin-top: 2px;
}

.stage::after {
    content: "";
    flex: 1 1 auto;
    height: 1px;
    background: var(--surface-2);
}

.slabel {
    font-size: 12.5px;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--text-3b);
    white-space: nowrap;
}

/* The divider already draws the line this band's border would repeat, and two
   rules 13px apart read as a mistake. Cards cannot borrow this -- the band is
   display:contents there -- and buys its spacing in margins instead.
   The padding is larger than the 13px the border took with it, not smaller: a
   stage heading sits over a round heading, and with the two nearly touching the
   eye reads "INVITATIONAL Part I" as one line of two weights rather than as one
   thing inside another. Above the divider is larger still, so it stays nearer
   what it opens than what it closes. */
.stage + .band {
    border-top: 0;
    padding-top: 15px;
}

/* A round of one states its problem on the heading, so the row beneath it would
   print the same title twice. It stays in the markup rather than being dropped:
   the topic filter decides what to hide by asking whether a band
   :has(li[data-t~="..."]), and a band with no rows at all answers no to every
   topic and disappears whenever a filter is on. */
.band.one > .rows,
.band.one > .cols {
    display: none;
}

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

/* .band is display:contents here, so its cells are children of the grid and a
   divider dropped between them would take one cell of three and sit beside the
   problems it is meant to head. */
html.v-cards .stage {
    grid-column: 1 / -1;

    /* The 24px row-gap falls above AND below this row, so the divider sat
       equidistant between the block it closes and the block it opens and read
       as belonging to neither. A grid has no per-row gap, and .stage + .band's
       padding cannot close the one below because that band is display:contents
       here, so the margins do it: +14 makes the gap above 38px, and the -14
       shortens this row's track by as much, pulling the first cell of the next
       one to 10px below the rule. */
    margin: 14px 0 -14px;

    /* Both gutters list needs are wrong here, at every width, and this has to
       go on outranking the narrow-width one below. .probs keeps none of the
       bootstrap row's negative side margins in cards, and a band is
       display:contents so its own gutter goes with its box -- the grid's edges
       already carry the inset, and padding here would push the rule 15px
       inside the cells it heads rather than level with them. */
    padding: 0;
}

/* .stage:first-child above is outranked by this section's selector, and
   without repeating it the first divider took the full 14px and started the
   problems 12px below the year sitting beside them. */
html.v-cards .stage:first-child {
    margin-top: 2px;
}

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. Not so for a round
   of one, whose heading has absorbed the cell: hiding it there left the round
   unnamed and its problem looking like a loose one. */
html.v-cards .band.per:not(.one) > .bandhead {
    display: none;
}

/* .rows is display:contents here, so the hidden row would otherwise come back
   as a grid cell of its own. */
html.v-cards .band.one > .rows {
    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. A stage
       divider is no more a column than a band is and needs the same gutter at
       the same width, or its label starts 15px left of every title under it. */
    .band,
    .stage {
        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;
}
