/* Table pane: a page whose table scrolls instead of the window.

   The window's scrolling stops at the page frame and the table's own box
   takes it over, which makes the browser do the work that used to be hand
   built: the horizontal scrollbar sits at the bottom of the viewport
   because that is where the box ends, the head sticks because the box is a
   real scrollport, and panning is whatever the platform already does -
   shift+wheel, trackpad, touch with momentum, arrows, Home/End, PageUp.

   No JS.

   A page opts in with data-shell="pane" on <html>, via the html_attrs block
   in layouts/base.html. Opt-in rather than global because the shell is a
   fixed-height layout and only a page built around one scrolling region
   wants it: anything that must stay visible goes above the pane, everything
   else scrolls inside it.

   The shape a page builds:

       <div class="pane-fill">          the column
           <div class="pane-toolbar">   a fixed row, natural height
           <div class="pane-fill">      nesting, for a page that has some
               <div class="pane">       the scroller, and the card

   One class does both jobs above: .pane-fill is any box between the shell
   and the pane, and every one of them has to be a column that hands the
   leftover height down, or the pane has no height to resolve its own
   against.

   Only the table rules at the end know what a table is; the pane itself
   does not care what it holds. They retune .table from tables.css, so this
   file must load after it. */


/* === The shell ========================================================== */

/* A definite height the whole way down, or the pane's flex:1 has nothing to
   resolve against and the box grows to its content instead of scrolling.
   100dvh, not 100vh: on mobile the browser chrome retracts, and vh is frozen
   at the tallest state, which would put the pane's bottom edge (and so its
   scrollbar) under it.

   min-height: 0 goes with every height in this file. layout.css floors body,
   .wrapper, .main-panel and .content at 100vh - the tallest state again - and
   a min-height outranks a height, so a floor left standing anywhere in the
   chain restores the overhang the heights are here to remove. */
html[data-shell="pane"],
html[data-shell="pane"] body {
    height: 100%;
    min-height: 0;
    overflow: hidden;
}

html[data-shell="pane"] .wrapper {
    height: 100dvh;
    min-height: 0;
}

/* .main-panel is offset by the topbar with `top`, so its height is the window
   less that. Its floor comes from two places, a rule in layout.css and the
   inline style js/components/sidebar.js writes on every other page - which is
   why that script leaves this shell alone. */
html[data-shell="pane"] .main-panel {
    height: calc(100dvh - var(--topbar-h));
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* min-height: 0 on every flex item in the chain. A flex item's default
   min-height is auto - it refuses to shrink below its content - so without
   this each level would grow to the table's full height and the shell would
   scroll after all. */
html[data-shell="pane"] .content {
    flex: 1;
    height: auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

html[data-shell="pane"] .content > .container-fluid {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* The page's rows keep their natural height; only the pane's column takes the
   slack. Stated explicitly because the header card carries `flex: 1 1 0` of
   its own, which it never exercised while its parent was a block - inside a
   flex column it would swallow the whole window. */
html[data-shell="pane"] .content > .container-fluid > * {
    flex: none;
}

html[data-shell="pane"] .content > .container-fluid > .pane-fill {
    flex: 1;
    min-height: 0;
}


/* === The page ============================================================ */

/* The page's own column: fixed rows at their natural height, and one pane
   taking the rest. */
.pane-fill {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* Two .pane-fill columns side by side, each scrolling its own pane: a main
   list and a narrower catalog beside it. It is a .pane-fill itself, so it
   still hands the shell's leftover height down - it only turns the axis.

   min-width: 0 for the same reason min-height: 0 appears above: a flex item
   will not shrink below its content, and a wide table in the main column
   would push the side column off the page instead of scrolling itself. */
.pane-cols {
    flex-direction: row;
    gap: 1rem;
}

.pane-cols > * {
    min-width: 0;
}

.pane-cols > .pane-side {
    flex: 0 0 20rem;
}

/* Under lg the two columns have no room to sit together, so the axis goes
   back to a column and the side pane's fixed width with it. */
@media (max-width: 991.98px) {
    .pane-cols {
        flex-direction: column;
    }

    .pane-cols > .pane-side {
        flex: none;
    }

    /* Stacked, the side column no longer takes a share of the window, so
       nothing caps its scroller and a long list would run the page long.
       A height of its own is the cap the layout stops providing. */
    .pane-cols > .pane-side .pane-scroll {
        max-height: 60vh;
    }
}

/* A scrolling region inside a .card instead of one. .pane below is the frame
   and the scroller at once, which is right when the list is the whole card;
   this is its scrolling half alone, for a card that has a header to keep
   still above the part that moves. */
.pane-scroll {
    flex: 0 1 auto;
    min-height: 0;
    overflow: auto;
    overscroll-behavior: contain;
}

/* A .card that holds a .pane-scroll: it ends under its last row instead of
   stretching to the column's full height, and gives back whatever the column
   cannot hold, which is what leaves the scroller a height to resolve
   against. Same take-what-you-need, give-back-what-you-must sizing as .pane
   below, without the frame - the card already draws one. */
.pane-card {
    flex: 0 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* Whatever sits above the pane - filters, search, pagination. It does not
   scroll, so nothing has to pin it; the class is only its spacing. */
.pane-toolbar {
    padding-bottom: 1rem;
}

/* The scrolling box, and the card at the same time. A scroll container clips
   to its own border-radius, so the corners hold on their own - no frame and
   no corner masks. Bootstrap's .card cannot do this job: its own
   `overflow-x: auto` would make a second scrollport inside this one. */
.pane {
    /* Grows to its rows and no further, and shrinks past them only when the
       window cannot hold them: `0 1 auto` is take-what-you-need, give-back-
       what-you-must. A short list is a card that ends under its last row,
       with no scrollbar and no empty surface; a long one is capped at the
       window and scrolls, which is where the bar lands at the bottom edge.
       min-height: 0 is what permits the shrinking - a flex item will not go
       below its content height without it, and the pane would push its own
       bottom (and its scrollbar) off the window. */
    flex: 0 1 auto;
    min-height: 0;
    overflow: auto;
    /* The page behind it no longer scrolls, so reaching an end here has
       nothing to hand the gesture on to - without this the browser answers
       with the overscroll bounce or a navigation. */
    overscroll-behavior: contain;
    background: var(--bg-surface);
    border: 1px solid var(--card-border);
    border-radius: var(--pane-radius);
}

:root {
    --pane-radius: 10px;
}


/* --- Its scrollbars ----------------------------------------------------- */

/* These are the page's bars in every practical sense - they are the only
   ones a list page has - but they sit inside a card rather than at the
   window's edge, so they take the inner treatment from layout.css and tune
   it rather than the window's tinted slab with its step arrows. A slab
   would read as a second window frame drawn inside the card.

   Four changes to the inner default. It is a little wider and carries the
   page thumb's ink, not the inner one's, because this is the only bar a
   list page has and the reader aims at it the way they would aim at the
   window's - an inner scroller's whisper of a thumb is right for a menu
   that also scrolls with the wheel, and wrong for the sole handle on a
   table that runs off the side. It takes a wider transparent inset, so the
   ink reads as a lozenge floating on the card rather than as a bar filling
   a channel. And it takes a seam: the track stays transparent, which is
   what makes the gutter the card's own surface and the bar look built into
   it, so the seam is the only thing marking where the content stops.
   Without it the thumb appears to float in the last column. */
/* Both standard properties are put back to their INITIAL values here, and
   that is load-bearing rather than redundant: Chrome ignores every
   ::-webkit-scrollbar rule on an element whose `scrollbar-width` or
   `scrollbar-color` is set to anything else, and layout.css sets
   `scrollbar-width: thin` on `*` so that Firefox gets the compact treatment.
   Inheriting that would cost this element the rules below - the radius, the
   inset and the size are only expressible through the pseudo-elements - and
   leave it drawing the platform's own bar, arrows and all. */
.pane {
    --pane-sb-size: 12px;
    /* Arrow ink, routed through a variable so the hover state below can
       brighten both arrows from one place. */
    --pane-sb-ink: var(--page-scrollbar-thumb);
    scrollbar-width: auto;
    scrollbar-color: auto;
}

/* Which leaves Firefox, where the pseudo-elements do not exist and those two
   properties are the only control there is. It gets the nearest thing: a thin
   bar, the same ink, no track. */
@supports not selector(::-webkit-scrollbar) {
    .pane {
        scrollbar-width: thin;
        scrollbar-color: var(--page-scrollbar-thumb) transparent;
    }
}

.pane::-webkit-scrollbar {
    width: var(--pane-sb-size);
    height: var(--pane-sb-size);
}

.pane::-webkit-scrollbar-track {
    background-color: transparent;
    border-left: 1px solid var(--border-subtle);
}

.pane::-webkit-scrollbar-track:horizontal {
    border-left: none;
    border-top: 1px solid var(--border-subtle);
}

/* Where the two tracks meet. Left bare so the card's corner radius is the
   only thing drawn there. */
.pane::-webkit-scrollbar-corner {
    background-color: transparent;
}

.pane::-webkit-scrollbar-thumb {
    background-color: var(--page-scrollbar-thumb);
    border-radius: 999px;
    border: 3px solid transparent;
    background-clip: content-box;
}

.pane::-webkit-scrollbar-thumb:hover {
    background-color: var(--page-scrollbar-thumb-hover);
}

/* Step arrows, at both ends of both bars. Drawn as conic-gradient wedges
   rather than image assets, the way layout.css draws the window's, so they
   follow the colour tokens like everything else - and scaled down a step,
   since this bar is narrower than that one.

   The seam runs through them too: a button that dropped the border would
   break the gutter's line at all four ends. `background-origin: border-box`
   is what keeps `center` the centre of the whole button rather than of what
   is left over inside that border, so the arrow stays on the same axis as
   the thumb. The fill stays transparent - the card shows through the gutter
   from end to end. */
.pane::-webkit-scrollbar-button:single-button {
    background-color: transparent;
    background-origin: border-box;
    background-repeat: no-repeat;
    background-position: center;
}

.pane::-webkit-scrollbar-button:single-button:vertical {
    display: block;
    height: var(--pane-sb-size);
    border-left: 1px solid var(--border-subtle);
    background-size: 8px 5px;
}

.pane::-webkit-scrollbar-button:single-button:vertical:decrement {
    background-image: conic-gradient(from 150deg at 50% 0%, var(--pane-sb-ink) 0 60deg, transparent 0);
}

.pane::-webkit-scrollbar-button:single-button:vertical:increment {
    background-image: conic-gradient(from 330deg at 50% 100%, var(--pane-sb-ink) 0 60deg, transparent 0);
}

.pane::-webkit-scrollbar-button:single-button:horizontal {
    display: block;
    width: var(--pane-sb-size);
    border-top: 1px solid var(--border-subtle);
    background-size: 5px 8px;
}

.pane::-webkit-scrollbar-button:single-button:horizontal:decrement {
    background-image: conic-gradient(from 60deg at 0% 50%, var(--pane-sb-ink) 0 60deg, transparent 0);
}

.pane::-webkit-scrollbar-button:single-button:horizontal:increment {
    background-image: conic-gradient(from 240deg at 100% 50%, var(--pane-sb-ink) 0 60deg, transparent 0);
}

.pane::-webkit-scrollbar-button:single-button:hover {
    --pane-sb-ink: var(--page-scrollbar-thumb-hover);
}


/* === Table content ======================================================= */

/* Sticky against the pane, which is the nearest scrollport. The head needs
   its own fill: rows scroll directly behind it, and a transparent head would
   show them through. */
.pane thead th {
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--table-sticky-header-bg);
    border-bottom: 2px solid var(--border-normal);
}

/* Separate borders, not collapsed: a collapsed table drops the per-cell
   borders the sticky head needs to keep its own line while it is detached. */
.pane table {
    margin-bottom: 0;
    border-collapse: separate;
    border-spacing: 0;
}

/* Under `border-collapse: separate` a cell's own top border would double
   every separator, so one border per row does the whole job. */
.pane tbody td {
    border-top: none;
}

.pane tbody tr {
    border-bottom: 1px solid var(--border-subtle);
}

.pane tbody tr:last-child {
    border-bottom: none;
}
