/* rhys.wtf's layout pieces every app can use (rhyswtf-apps, `layout`):
   the safe-area gutters, and panes, columns that sit side by side when
   they fit and are swiped one or two at a time when they don't, with a
   bar of them along the bottom. Served at /static/layout.css and linked
   after the app's own stylesheet. Edit it in rhyswtf-web and sync, never
   in an app.

   Sizes follow the window's width, never a device: how many panes are in
   sight falls out of one minimum, --pane-min (18rem: what a dashboard's
   side column needs before its widgets wrap). The same markup, snapping
   and bar at every width; only the number in sight changes. */

/* The gutters, clear of a phone's rounded corners, notch and home
   indicator (the page needs viewport-fit=cover to reach under them). */
:root {
    --gutter: 1rem;
    --safe-left: max(var(--gutter), env(safe-area-inset-left));
    --safe-right: max(var(--gutter), env(safe-area-inset-right));
    --safe-bottom: env(safe-area-inset-bottom, 0px);
}

/* Panes: a row of them (`.panes`, its children `.pane`), each a column
   of the app's own content.

   - How many are in sight: --in-sight, at most --count (the row's
     `panes--of-N`, N panes with content; empty ones are hidden). Each
     pane is its --weight of the row: 1, or --wide for the `pane--wide`
     one (1.4 when all three are in sight, as the desktop's middle column
     always was; 1 otherwise, so one or two in sight share the row
     evenly). The row says it has one with `panes--wide`.
   - The row scrolls sideways, a pane at a time (snapping, and never
     skipping one on a fling), and never hands a sideways swipe on to
     the page; the page itself scrolls up and down as a whole, so a
     phone's toolbars fold away, pull-to-refresh and tapping the status
     bar work, and the panes line up as columns do. The browser's back
     gesture starts at the screen's edge, so it's untouched.
   - It opens on `pane--start` (scroll-initial-target; elsewhere the
     link's #id or the browser bundle's `panes`, while the row waits
     unseen, below).
   - Going to a pane by its #id moves the row, not the page down: the
     scroll margin reaches past the page's top.
   - All in sight, nothing scrolls: the panes are simply side by side. */
.panes {
    --in-sight: 1;
    --count: 1;
    --wide: 1;
    --extra: 0;
    --sum: calc(min(var(--in-sight), var(--count)) + var(--extra));
    display: flex;
    align-items: flex-start;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    /* The browser bundle measures the panes from it. */
    position: relative;
    /* The whole width of the window, the panes' content lined up with the
       page's: half the gap between panes on each side of every pane. */
    margin-left: calc(-1 * var(--safe-left));
    margin-right: calc(-1 * var(--safe-right));
    padding-left: calc(var(--safe-left) - var(--gutter) / 2);
    padding-right: calc(var(--safe-right) - var(--gutter) / 2);
    scroll-padding-left: calc(var(--safe-left) - var(--gutter) / 2);
    scroll-padding-right: calc(var(--safe-right) - var(--gutter) / 2);
}

.panes::-webkit-scrollbar {
    display: none;
}

.panes--of-2 { --count: 2; }
.panes--of-3 { --count: 3; }
.panes--wide { --extra: calc(var(--wide) - 1); }

/* A pane's padding is --pane-padding when it says (half the gap between
   panes on each side otherwise), so an app can set it on its pane even
   though this file comes after its own. */
.pane {
    flex: 0 0 calc(100% * var(--weight, 1) / var(--sum));
    min-width: 0;
    padding: var(--pane-padding, 0 calc(var(--gutter) / 2));
    scroll-snap-align: start;
    scroll-snap-stop: always;
    scroll-margin-top: 100vh;
}

.pane--wide {
    --weight: var(--wide);
}

.pane:empty {
    display: none;
}

.pane--start {
    scroll-initial-target: nearest;
}

/* Peek rows (`panes--peek`): a side pane (`pane--side`, the first: a list
   of places, say) and a main one (the page, `pane--start`), for a list
   beside its detail rather than columns of the same kind.

   - Side by side from the same 39rem as above (where the side pane has
     --pane-side, 18rem, and the main one at least 21rem): the side pane
     --pane-side wide, holding still while the page scrolls past it
     (sticky, scrolling itself when taller than the window), the main one
     the rest. The row is no scroller then, so the main pane's own sticky
     parts stick to the window as they would without panes.
   - Narrower, they're swiped, edge to edge (each pane pads its own
     content with --pane-padding), and the side pane is narrower than the window by --peek,
     so a strip of the main one shows beside it: it's still there, a
     swipe away, or a tap on that strip (`pane-peek`, below). The main
     pane is the window's whole width: on it, the side pane is reached by
     a swipe from anywhere (never only from the edge, which is the
     browser's back gesture) or a `pane-jump` link.
   - It always opens on the main pane, and swiping doesn't change the
     address: coming back to the page means coming back to the page, not
     the list. */
.panes--peek {
    --pane-side: 18rem;
    --peek: max(3rem, 15%);
    --pane-padding: 0;
    margin-left: 0;
    margin-right: 0;
    padding-left: 0;
    padding-right: 0;
    scroll-padding-left: 0;
    scroll-padding-right: 0;
}

.panes--peek > .pane {
    flex: 0 0 100%;
}

.panes--peek > .pane--side {
    flex-basis: calc(100% - var(--peek));
}

/* The strip of the main pane beside the side pane, as a link to it
   (`layout::peek`): over the strip, so a tap there brings the main pane
   in rather than following whatever is under it. Shown only while the
   row is at its start (where the side pane is in sight), by the row's
   own scroll where the browser can tell (scroll-driven animations:
   Chromium, Safari 26); elsewhere it stays hidden, and a swipe does it. */
.panes--peek > .pane-peek {
    position: absolute;
    top: 0;
    bottom: 0;
    left: calc(100% - var(--peek));
    width: var(--peek);
    visibility: hidden;
    -webkit-tap-highlight-color: transparent;
}

@supports (animation-timeline: scroll()) {
    .panes--peek > .pane-peek {
        animation: pane-peek linear both;
        animation-timeline: scroll(nearest inline);
    }
}

@keyframes pane-peek {
    from { visibility: visible; }
    10%, to { visibility: hidden; }
}

/* A link to another pane of two (`href="#id"`), for a page where the bar
   would be too much (a peek row's way to its side pane): a plain jump
   without script, at once and without history with it. Hidden once both
   panes are in sight. The app draws it. */

/* Focus moved to a pane by the bar (for the keyboard and screen readers):
   outlined only when the keyboard did it. Panes aren't focusable in the
   markup (see layout.rs), so opening a page at a pane's #id draws no ring. */
.pane:focus {
    outline: none;
}

.pane:focus-visible {
    outline: 2px solid var(--color-link-hover, currentColor);
    outline-offset: -2px;
    border-radius: var(--radius, 0.5rem);
}

/* The bar: one link per pane (`.pane-bar`, `pane-bar--of-N`), the ones in
   sight filled in (`data-shown`, from the browser bundle; the start one
   from the server), with a chevron towards those out of sight. At the
   foot of the page's main column, held at the window's bottom while it's
   in view (sticky, so it takes its own room and covers nothing at the
   end), clear of the home indicator and gesture bar. Each link a 44px
   target at least. When the window narrows, the links out of sight give
   way first, down to a dot and a chevron; the one in sight keeps its
   name longest. */
.pane-bar {
    position: sticky;
    bottom: 0;
    z-index: 5;
    display: flex;
    justify-content: center;
    gap: 0.4rem;
    margin: 1rem calc(-1 * var(--safe-right)) 0 calc(-1 * var(--safe-left));
    padding: 0.4rem var(--safe-right) max(0.4rem, var(--safe-bottom)) var(--safe-left);
    background: color-mix(in srgb, var(--color-bg, Canvas) 92%, transparent);
    border-top: 1px solid color-mix(in srgb, var(--color-border, CanvasText) 25%, transparent);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    font-family: var(--font-ui, sans-serif);
}

.pane-bar__item {
    flex: 0 1000 auto;
    min-width: 44px;
    max-width: 12rem;
    min-height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    padding: 0 0.75rem;
    border: 1px solid transparent;
    border-radius: 999px;
    color: var(--color-muted, inherit);
    text-decoration: none;
    font-size: 0.9rem;
    line-height: 1.2;
    -webkit-tap-highlight-color: transparent;
}

.pane-bar__dot {
    flex: none;
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 50%;
    border: 1.5px solid currentColor;
}

.pane-bar__label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pane-bar__item[data-shown] {
    flex-shrink: 1;
    max-width: 16rem;
    color: var(--color-text-strong, inherit);
    background: var(--color-surface, Canvas);
    border-color: var(--color-border, currentColor);
}

.pane-bar__item[data-shown] .pane-bar__dot {
    background: var(--pane-accent, currentColor);
    border-color: var(--pane-accent, currentColor);
}

.pane-bar__item:not([data-shown]):has(+ [data-shown])::before {
    content: "‹";
    order: -1;
}

.pane-bar__item[data-shown] + .pane-bar__item:not([data-shown])::after {
    content: "›";
}

.pane-bar__item:hover {
    color: var(--color-text-strong, inherit);
}

.pane-bar__item:focus-visible {
    outline: 2px solid var(--color-link-hover, currentColor);
    outline-offset: 2px;
}

/* The two widths where more panes come into sight: the only media
   queries. Media queries can't read custom properties, so these are
   --pane-min (18rem) worked through:
   - two in sight once two panes fit with the gutters: 2 × 18rem + 3rem;
   - three (the --wide one 1.4 times the others) at 3.4 × 18rem + 4rem.
   The bar goes once every pane is in sight, and so does the wait
   for the browser bundle (at the end); a peek row is side by side from
   the first. */
@media (width >= 39rem) {
    .panes { --in-sight: 2; }
    .pane-bar--of-2,
    .pane-jump,
    .panes--peek > .pane-peek { display: none; }

    /* A peek row, side by side (above). */
    .panes--peek {
        overflow: visible;
    }

    .panes--peek > .pane {
        flex: 1 1 0;
    }

    /* The row is no scroller now, so the page would be: it would open
       scrolled down to the main pane, past the header. */
    .panes--peek > .pane--start {
        scroll-initial-target: none;
    }

    .panes--peek > .pane--side {
        flex: 0 0 var(--pane-side);
        position: sticky;
        top: 0;
        max-height: 100vh;
        max-height: 100dvh;
        overflow-y: auto;
    }

    .panes--peek:has(> .pane--start:not(:first-child)):not([data-placed], :has(:target)) {
        animation: none;
    }
}

@media (width >= 65rem) {
    .panes { --in-sight: 3; --wide: 1.4; }
    .pane-bar--of-3 { display: none; }

    /* All in sight: nothing to wait for (below). */
    .panes:has(> .pane--start:not(:first-child)):not([data-placed], :has(:target)) {
        animation: none;
    }
}

/* Without the browser bundle, a browser that knows which link's pane is
   in sight (scroll-target-group) marks it as the swipe goes. */
@supports (scroll-target-group: auto) {
    .pane-bar {
        scroll-target-group: auto;
    }

    html:not([data-web]) .pane-bar__item[data-shown]:not(:target-current) {
        color: var(--color-muted, inherit);
        background: none;
        border-color: transparent;
    }

    html:not([data-web]) .pane-bar__item:target-current {
        color: var(--color-text-strong, inherit);
        background: var(--color-surface, Canvas);
        border-color: var(--color-border, currentColor);
    }
}

/* Where the browser can't open on the start pane by itself (Safari has
   no scroll-initial-target) and the address names no pane, the row waits
   unseen for the browser bundle to put it there (a moment), or shows as
   it is after a second without it: never a flash of the first pane.
   Only while the start pane may be out of sight. */
@supports not (scroll-initial-target: nearest) {
    .panes:has(> .pane--start:not(:first-child)):not([data-placed], :has(:target)) {
        animation: panes-reveal 0s linear 1s both;
    }
}

@keyframes panes-reveal {
    from { visibility: hidden; }
    to { visibility: visible; }
}
