/*
 * The search page: the result list, and the filters beside it.
 *
 * One form wraps both, so the filters and the sort control submit together and
 * the whole page still works with the sheet shut or with no JavaScript at all.
 *
 * The sheet is the default. The filter column only appears when the window is
 * wide enough to hold it *outside* the page's usual measure, so the result list
 * is the same width here as everywhere else on the site rather than being
 * squeezed by a sidebar. Nothing about the markup changes between the two.
 */

.search {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
}

/* Filters --------------------------------------------------------------- */

/*
 * The sheet waits off the right-hand edge, and something sitting outside the
 * viewport is something a browser may let you scroll to - Safari flashes a
 * horizontal scrollbar as it slides. Clipped rather than hidden, because `hidden`
 * would turn the root into a scroll container and take sticky positioning with
 * it; and scoped to pages that have a sheet at all, because clipping the root
 * everywhere would catch a chapter that legitimately overflows - a wide table,
 * ASCII art in a <pre> - and leave it unreachable rather than scrollable.
 */
html:has(.filters) {
    overflow-x: clip;
}

/*
 * The sheet: pinned to the viewport, off the right edge until disclosure.js adds
 * is-open and puts the scrim behind it.
 */
.filters {
    position: fixed;
    inset: 0 0 0 auto;
    z-index: 160;

    width: min(22.5rem, 88vw);

    display: flex;
    flex-direction: column;

    background: var(--color-section-background);
    box-shadow: var(--shadow-overlay);
    padding-right: env(safe-area-inset-right);

    translate: 100% 0;
    visibility: hidden;
    transition:
        translate 180ms ease,
        visibility 180ms;
}

.filters.is-open {
    translate: 0 0;
    visibility: visible;
}

.filters__head,
.filters__foot {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-3);
    background: var(--color-section-background);
}

.filters__head {
    border-bottom: 1px solid var(--color-rule);
    padding-top: calc(var(--space-3) + env(safe-area-inset-top));
}

.filters__title {
    flex: 1;
    font-weight: 500;
}

/* Pinned between the two fixed ends, which is the one place the filters really
   do need a scroll container of their own. */
.filters__body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

.filters__foot {
    flex-direction: column;
    align-items: stretch;
    border-top: 1px solid var(--color-rule);
    padding-bottom: calc(var(--space-3) + env(safe-area-inset-bottom));
}

.filters__defaults {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-1) var(--space-3);
    font-size: 0.8125rem;
}

.filters__close {
    min-width: var(--tap-target);
    min-height: var(--tap-target);
    font-size: 1.5rem;
    line-height: 1;
}

/* Filter groups --------------------------------------------------------- */

/*
 * Shut, a group names itself and what it currently holds, so the whole filter
 * set reads as a summary of the query without opening anything. They all start
 * shut, every time - a group that opened itself because it was set would take
 * that away on exactly the searches where it is most worth having.
 */
.filter-group {
    border-bottom: 1px solid var(--color-rule);
}

.filter-group:last-child {
    border-bottom: 0;
}

.filter-group__summary {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
        'name chevron'
        'value chevron';
    align-items: center;
    gap: 0 var(--space-2);

    min-height: var(--tap-target);
    padding: var(--space-2) var(--space-3);

    cursor: pointer;
    list-style: none;
}

.filter-group__summary::-webkit-details-marker {
    display: none;
}

/* Only where there is a pointer: on a touchscreen the highlight sticks to
   whatever was tapped last and reads as a selection. */
@media (hover: hover) {
    .filter-group__summary:hover {
        background: var(--color-button-hover-background);
    }
}

.filter-group__name {
    grid-area: name;
    line-height: 1.4;
}

.filter-group__value {
    grid-area: value;
    overflow: hidden;
    font-size: 0.8125rem;
    line-height: 1.4;
    white-space: nowrap;
    text-overflow: ellipsis;
    color: var(--color-muted);
}

.filter-group__value--warning {
    color: var(--color-warning-foreground);
}

/*
 * A dot while the group says something the results below do not yet show. Small
 * on purpose: the change is one button press from being real, so this is a note
 * that the two are out of step, not a warning.
 */
.filter-group__pending {
    display: none;

    width: 0.4em;
    height: 0.4em;
    margin-right: 0.45em;
    vertical-align: 0.1em;

    border-radius: 50%;
    background: var(--color-accent);
}

.filter-group.is-modified .filter-group__pending {
    display: inline-block;
}

/*
 * The same thing said out loud. Taken out of the document rather than merely
 * hidden from view, or a screen reader would announce every group as changed
 * from the moment the page loaded.
 */
.filter-group__pending-note {
    display: none;
}

.filter-group.is-modified .filter-group__pending-note {
    display: inline;
}

.filter-group__chevron {
    grid-area: chevron;
    color: var(--color-muted);
    transition: rotate 120ms ease;
}

.filter-group[open] .filter-group__chevron {
    rotate: 180deg;
}

.filter-group__body {
    padding: 0 var(--space-3) var(--space-3);
}

/* Rows ------------------------------------------------------------------ */

.filter-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);

    min-height: var(--tap-target);
    padding: var(--space-1) 0;

    cursor: pointer;
}

.filter-row__label {
    flex: 1;
    min-width: 0;
    line-height: 1.35;
}

.filter-row__count {
    font-size: 0.8125rem;
    font-variant-numeric: tabular-nums;
    color: var(--color-muted);
}

/* A checkbox that has hit the limit on its group is off the table, not gone. */
.filter-row input:disabled,
.filter-row input:disabled ~ .filter-row__label {
    opacity: 0.45;
    cursor: default;
}

.filter-range {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.filter-range span {
    color: var(--color-muted);
}

/* Author ---------------------------------------------------------------- */

/*
 * In the flow, under the field, rather than floating over it.
 *
 * An overlay would be clipped: the filters scroll inside .filters__body, and an
 * absolutely positioned list is cut off by any ancestor that scrolls - which hid
 * most of ten suggestions behind the bottom edge of the panel. Sitting in the
 * flow, the list just makes the panel taller and scrolls with everything else,
 * and it behaves the same in the sidebar as it does in the sheet.
 */
.author-suggestions {
    max-height: 240px;
    overflow-y: auto;
    margin: var(--space-1) 0 0;
    padding: var(--space-1);
    list-style: none;

    border: 1px solid var(--color-border);
    border-radius: var(--radius-small);
    background: var(--color-field-background);
}

.author-suggestions__option {
    display: block;
    width: 100%;
    min-height: var(--tap-target);
    padding: var(--space-2);

    border: 0;
    border-radius: var(--radius-small);
    background: none;

    text-align: left;
}

.author-suggestions__option:hover,
.author-suggestions__option:focus {
    background: var(--color-button-hover-background);
}

.chosen-list {
    margin: var(--space-2) 0 0;
    padding: 0;
    list-style: none;
}

.chosen {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-height: var(--tap-target);
}

.chosen__name {
    flex: 1;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* Results --------------------------------------------------------------- */

.results__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
}

.results__count {
    flex: 1;
    min-width: 12rem;
}

.results__sort {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    white-space: nowrap;
}

.results__sort .select {
    width: auto;
}

/* Sort and the way into the filters, kept together: both change what the list
   below shows, and both belong at the end of the line that describes it. */
.results__controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
}

/*
 * Wide enough for the count whether or not there is one. The label going from
 * "Filters" to "Filters (8)" is 25px, and since the pair is anchored to the
 * right-hand end of the line, every one of those pixels came out of where the
 * sort control sat - so applying a filter shunted it sideways.
 */
.filters-trigger {
    min-width: 7rem;
}

/*
 * Three columns rather than space-between, the outer two equal: the page count
 * then sits at the middle of the line rather than at the middle of whatever the
 * buttons happened to leave, which moved it as the labels changed.
 *
 * No padding below it. The list above needs separating from it; what comes
 * after is the end of the page, which the container already pads - and the two
 * together left a band of nothing between the buttons and the footer.
 */
.pagination {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: var(--space-3);
    padding-top: var(--space-4);
}

/* Ranged to the ends of the line, rather than stretched across their columns. */
.pagination > :first-child {
    justify-self: start;
}

.pagination > :last-child {
    justify-self: end;
}

/* Both as wide as the wider label needs, so the pair matches. Never narrower
   than the label itself: shrunk to fit they broke "‹ Previous" over two lines
   inside a button, which is worse than the pair not matching. */
.pagination .button {
    min-width: 7rem;
    white-space: nowrap;
}

/*
 * Under which the three do not fit on one line. Two 7rem buttons and the gap
 * either side take 248px, the count takes 108 at its longest - "Page 330 of
 * 330", the archive being 6,595 stories at 20 to a page - and the gutter takes
 * 24, so the line needs 380px of screen. Squeezed into less, the count is what
 * gives: on an iPhone SE it broke across two lines between the buttons.
 *
 * So it takes a line of its own instead, and the buttons keep the ends of the
 * one below - the same arrangement with the fold in a different place. Measured
 * against the longest count rather than the one on screen, because a pager that
 * changes shape at page 100 is worse than one that is stacked throughout.
 *
 * In em rather than px, those 380 being 380 only at a default text size: every
 * width in that sum is set in rem, so a reader who has turned their browser's
 * default up needs the fold sooner. An em in a media query is that default,
 * whatever the page then does with it, so the two move together.
 */
@media (max-width: 23.75em) {
    .pagination {
        grid-template-columns: 1fr 1fr;
        row-gap: var(--space-2);
    }

    .pagination__position {
        grid-row: 1;
        grid-column: 1 / -1;
        justify-self: center;
    }

    .pagination .button,
    .pagination__spacer {
        grid-row: 2;
    }
}

.pagination__position {
    font-size: 0.875rem;
    color: var(--color-muted);
}

/* Holds the column a missing button would have had, so a lone Next stays on the
   right where a Next button belongs. */
.pagination__spacer {
    width: 1px;
}

.results__warning {
    max-width: 55ch;
    margin-bottom: var(--space-4);
    padding: var(--space-3) var(--space-4);

    border: 1px solid var(--color-warning-foreground);
    border-radius: var(--radius-small);

    font-size: 0.9375rem;
    color: var(--color-warning-foreground);
}

.results__empty {
    padding: var(--space-8) 0;
    max-width: 55ch;
}

.results__empty p {
    margin-top: var(--space-3);
}

.results__empty .button {
    margin-top: var(--space-5);
}

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

.story-list {
    border-top: 1px solid var(--color-rule);
}

.story {
    padding: var(--space-5) 0;
    border-bottom: 1px solid var(--color-rule);
}

/*
 * The same size as the summary below it. Being a link is enough to mark a title
 * out in a list this plain, and setting it larger made every entry announce
 * itself before you had read any of it.
 */
.story__heading {
    font-size: 1rem;
    line-height: 1.65;
}

.story__summary {
    margin: var(--space-2) 0;
}

/*
 * One dim line of metadata. Wrapping is by flex gap rather than by margins on
 * inline items, which is what stopped the old site's details reading as a
 * run-on paragraph once it wrapped.
 */
.details {
    display: flex;
    flex-wrap: wrap;
    gap: 2px var(--space-3);

    font-size: 0.8125rem;
    line-height: 1.6;
    color: var(--color-muted);
}

/*
 * The two lines are one block: they sit closer to each other than the pair of
 * them sits to whatever is above.
 *
 * The block carries its own top margin rather than living off the summary's
 * bottom one, because a fair few stories in the archive have no summary at all
 * and were left with their metadata flush against the title. The two margins
 * collapse to one where both are present.
 */
.details-block {
    margin-top: var(--space-2);
}

.details + .details {
    margin-top: 2px;
}

.details > * {
    white-space: nowrap;
}

/* Genre and ship names are the one thing here worth breaking a line for. */
.details > .details__wide {
    white-space: normal;
}

/*
 * Wide enough for a filter column that costs the results nothing at all.
 *
 * The results keep the page's ordinary centred column - same width, same
 * position, same alignment with the header as every other page - and the filters
 * hang off its right edge into what would otherwise be blank margin. The grid is
 * pushed right by exactly the width it adds, so the results column lands where
 * it would have landed with no filters beside it.
 *
 * Every term here is in rem, and that is the point. The results measure 50rem,
 * so a reader whose default text size is larger has a wider column and less
 * margin to hang anything in; a breakpoint in px would keep firing at the same
 * window width and push the filters off the side of the screen - with the
 * trigger hidden, because as far as the stylesheet was concerned the sidebar had
 * appeared. In rem the whole thing scales together:
 *
 *   margin each side = (100vw - 50rem) / 2       (the padding terms cancel)
 *   needed           = 16.5rem + 1.5rem + 2rem   (column, gap, gutter)
 *   so the sidebar fits from 50rem + 2 x 20rem   = 90rem
 *
 * which is 1440px at a 16px default and moves with the reader from there.
 *
 * The gutter is 2rem rather than 1rem because a classic scrollbar - Windows, or
 * macOS set to show them always - comes out of the layout width but not out of
 * what the media query matched on. At one rem of slack a 17px scrollbar ate the
 * lot and put the panel a pixel over the edge. The extra rem is paid for out of
 * the column rather than off the breakpoint, so a 1440px laptop keeps its
 * sidebar.
 */
@media (min-width: 90rem) {
    .search {
        grid-template-columns: minmax(0, 1fr) 16.5rem;
        gap: 1.5rem;
        margin-right: calc(-16.5rem - 1.5rem);
    }

    /* Placed rather than ordered: the filters stay first in the document, so
       they are still what you reach first by keyboard, as they are in the
       sheet. Only where they sit on the screen changes. */
    .results {
        grid-column: 1;
        grid-row: 1;
    }

    /* Back into the flow as an ordinary column. */
    .filters {
        grid-column: 2;
        grid-row: 1;

        position: static;
        z-index: auto;
        width: auto;

        border: 1px solid var(--color-rule);
        border-radius: var(--radius);
        box-shadow: none;
        padding-right: 0;

        translate: none;
        visibility: visible;
    }

    .filters__head {
        border-radius: var(--radius) var(--radius) 0 0;
        padding-top: var(--space-3);
    }

    .filters__foot {
        border-radius: 0 0 var(--radius) var(--radius);
        padding-bottom: var(--space-3);
    }

    /* Nothing to open and nothing to close. */
    .filters__close,
    .filters-trigger {
        display: none;
    }

    /* No fixed ends to sit between, so no scroll container and nothing to clip
       the author suggestions. */
    .filters__body {
        overflow-y: visible;
    }
}

/*
 * With JavaScript off there is nothing to open or close the sheet with, so the
 * filters stay where they are - above the results, as an ordinary form - and the
 * two controls that would only have sat there doing nothing are dropped.
 */
@media not all and (min-width: 90rem) {
    html:not(.js) .filters {
        position: static;
        width: auto;

        margin-bottom: var(--space-6);
        border: 1px solid var(--color-rule);
        border-radius: var(--radius);
        box-shadow: none;

        translate: none;
        visibility: visible;
    }

    html:not(.js) .filters__body {
        overflow-y: visible;
    }

    html:not(.js) .filters__close,
    html:not(.js) .filters-trigger {
        display: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .filters {
        transition: none;
    }

    .filter-group__chevron {
        transition: none;
    }
}

/* The group that has been emptied, and the reason the button below is dead. */
.filter-group.is-invalid .filter-group__name {
    color: var(--color-warning-foreground);
}

.filters__invalid {
    text-align: center;
}

/* What this list is: the tag that was followed, or just "Search results". */
.results__heading {
    margin-bottom: var(--space-4);
}
