/*
 * Copyright (C) 2026 Misha Nasledov
 *
 * SPDX-License-Identifier: MIT
 */

/*
 * panes.css -- the tiled layout, and nothing else.
 *
 * Its own file, because none of it applies until panes.js has put
 * `tiled' on the body: everything here is under that class or inside the
 * layout's own elements, so a page with the tiler off is styled by the
 * page's own stylesheet alone. That is the same claim panes.js makes
 * about the markup, made about the stylesheet.
 *
 * The tree is nested flex boxes. A split is a box with a direction, its
 * children carry a fraction as `flex-grow' and a minimum as `min-width',
 * and the browser does the arithmetic -- there is no measuring here.
 *
 * WHAT IS NOT HERE is what a page puts *in* a pane. That a <details>
 * adopted into one has to become a column is this file's to say -- it is
 * the price of adopting the document, and every page that does pays it.
 * Which of a page's own boxes takes the room a pane has is the page's to
 * say: no stylesheet shipped with a tiler can know which of somebody's
 * boxes is the one that wants it.
 *
 * THE COLORS are read under this file's own names -- `--pane-line' and
 * the rest -- each with a fallback, so a page that maps nothing still
 * gets a layout it can see and a page that maps them gets its own
 * theme. Reading `--line' directly would be worse than reading nothing:
 * a page that has a `--line' meaning something else would get a layout
 * quietly wearing the wrong color, which is the failure nobody
 * investigates.
 *
 * The one without a fixed fallback is `--pane-held', the color a
 * selected tab, a divider under the pointer and a drop target are drawn
 * in. Unmapped, it is `AccentColor': whatever the page's own sliders and
 * checkboxes are drawn in, which is the system's accent where the
 * browser exposes it -- an installed app, say -- and the browser's own
 * where it does not. A page that maps it keeps its own.
 */

/* The element the page hands to createPanes, which puts this class on it
   -- so these rules key on something the module knows it has rather than
   on the id one page happened to choose. Empty until there is a layout,
   and nothing in the document should know it is there. */
.panesroot:empty { display: none; }

/*
 * The page, tiled: the chrome across the top at whatever height it wants,
 * and the layout taking the rest of the window.
 *
 * `100dvh' rather than `100vh' because a mobile browser's toolbars come
 * and go, and a layout that is a toolbar taller than the window has its
 * bottom row under one. Nothing tiles on a phone, but a narrow laptop
 * window crossing the threshold is the same arithmetic.
 *
 * And `--pane-height' over it, because `dvh' accounts for retracting
 * browser chrome and nothing else: a page with a text box at the bottom
 * has the on-screen keyboard drawn over it, and the only thing that
 * reports the box actually visible is `visualViewport'. A page that
 * tracks it sets this to what it found.
 */
body.tiled
{
    max-width: none;
    margin: 0;
    padding: 0.6em 0.8em;
    height: var(--pane-height, 100dvh);
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* The drawer above, the layout under it. */
body.tiled .panesroot
{
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    position: relative;
}

body.tiled .panesroot > .panebox,
body.tiled .panesroot > .paneleaf { flex: 1 1 auto; min-height: 0; }

/* ---- the tree ----
 *
 * A split is a flex box with a direction; its children carry their
 * fraction as `flex-grow' over a basis of nothing, so what each is worth
 * is its share of what there is and the browser works out the rest. The
 * minimum is on the child rather than on the pane inside it, because the
 * child is what a divider has to refuse to shrink.
 */

.panebox, .paneleaf
{
    display: flex;
    flex: 1 1 0;
    min-width: 0;
    min-height: 0;
}

.panebox[data-dir="row"] { flex-direction: row; }
.panebox[data-dir="col"] { flex-direction: column; }

/* A leaf is its strip of tabs and the pane under them. */
.paneleaf { flex-direction: column; }

/* The divider. As thick as `--pane-split', which panes.js writes onto
   the root from the number its own arithmetic uses -- what refuses a
   drag and what is drawn are one number now rather than two asked to
   agree. A hit area that does not have to be aimed at, and a line down
   the middle that does not move. */
.panesplit
{
    flex: 0 0 var(--pane-split, 6px);
    position: relative;
    background: transparent;
    border: 0;
    padding: 0;

    /* A drag, not a scroll. Without it a finger moves the divider a few
       pixels and the browser takes the rest of the gesture for itself,
       cancelling the pointer -- the tabs had this and the divider did
       not. */
    touch-action: none;
}

.panebox[data-dir="row"] > .panesplit { cursor: col-resize; }
.panebox[data-dir="col"] > .panesplit { cursor: row-resize; }

.panesplit::after
{
    content: "";
    position: absolute;
    inset: 0;
    background: var(--pane-line, #dcdcdc);
    border-radius: 1px;
}

.panebox[data-dir="row"] > .panesplit::after { inset: 0 2px; }
.panebox[data-dir="col"] > .panesplit::after { inset: 2px 0; }

.panesplit:hover::after, .panesplit:focus-visible::after
{
    background: var(--pane-held, AccentColor);
}

.panesplit:focus-visible { outline: none; }

/* ---- a pane ---- */

.pane
{
    display: flex;
    flex-direction: column;
    flex: 1 1 0;
    min-width: 0;
    min-height: 0;
    border: 1px solid var(--pane-line, #dcdcdc);
    border-radius: 4px;
    background: var(--pane-bg, #ffffff);
    overflow: hidden;
}

/*
 * Hidden, and hidden for real.
 *
 * `hidden' is an attribute the browser styles from its own stylesheet,
 * and every rule in this file outranks that one simply by being here: a
 * `.pane { display: flex }' beats a user agent's `[hidden] { display:
 * none }' whatever the selectors weigh, because an author's sheet wins
 * over a user agent's. So anything this file gives a `display' to has to
 * say what `hidden' means for it as well -- panes.js hides the pane
 * behind a tab and an empty drawer with the attribute, and without this
 * line both of them stay on the screen.
 *
 * A pane is not given `display: none', though, and that is on purpose.
 * A box that is not displayed has no layout to keep, and Chromium starts
 * every scroller in it at the beginning again the moment it is moved --
 * and a pane behind a tab is moved whenever the split around its leaf
 * collapses, and a closed one on its way to the drawer and back. So a
 * hidden pane keeps its boxes and is not drawn: `content-visibility'
 * skips what is in it, which also takes it out of the focus order and
 * the accessibility tree, and it is lifted out of the flow and made
 * invisible so that its own border does not show or take room from the
 * one in front.
 */
.panedrawer[hidden], .paneleaf[hidden] { display: none; }

.pane[hidden]
{
    position: absolute;
    visibility: hidden;
    content-visibility: hidden;
}

/* What was a block in a document is a column in a pane: the box that
   wants the room takes it, and the rows around it keep their height.
   Which box that is is said below rather than guessed at. */
.panebody > details,
.panebody > section
{
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    margin: 0;
}

/* A <details> keeps what it discloses in a box of its own, and that box
   is the flex item -- so the column has to go through it, or the text
   area inside is laid out against nothing and comes out three rows tall.
   An engine without ::details-content slots the children straight into
   the rule above and does not need this one; the two together are every
   engine, which is why both are here. */
.panebody > details::details-content
{
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

/* A pane whose mode is not up.
 *
 * The attribute is this module's own (`OFF' in panes.js) rather than
 * `hidden', which is a word most pages are already using for something
 * else -- so the element has to be hidden here, since nothing else now
 * does it. Both rules: the element goes, whether or not there is a
 * layout, and the pane around it goes with it rather than staying as an
 * empty bordered box. */
[data-pane-off] { display: none !important; }

.pane:has(> .panebody > [data-pane-off]) { display: none; }

/* ---- the tabs ----
 *
 * One tab is a pane's header; several are a choice, and the same strip
 * either way. Which is what makes a <details> able to hand its
 * disclosure over to it: the pane always has a header to be one.
 */
.panetabs
{
    flex: 0 0 auto;
    display: flex;
    background: var(--pane-panel, #f4f4f4);
    border-bottom: 1px solid var(--pane-line, #dcdcdc);
    overflow: hidden;
}

/* A tab and the cross that closes it are one item of the strip, so the
   line under the tab in front runs under both of them and the border
   between panes falls after the cross rather than before it. */
.panetabwrap
{
    flex: 0 1 auto;
    min-width: 0;
    display: flex;
    align-items: stretch;
    border-right: 1px solid var(--pane-line, #dcdcdc);
    border-bottom: 2px solid transparent;
}

.panetabwrap:has(> .panetab[aria-selected="true"])
{
    background: var(--pane-bg, #ffffff);
    border-bottom-color: var(--pane-held, AccentColor);
}

.panetab
{
    flex: 0 1 auto;
    min-width: 0;
    padding: 0.25em 0.2em 0.25em 0.7em;
    font: inherit;
    font-size: 0.85em;
    color: var(--pane-dim, #666666);
    background: transparent;
    border: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
    touch-action: none;         /* a tab drags; it does not scroll */
}

.panetab[aria-selected="true"] { color: var(--pane-fg, #202020); }

.panetab.panedragging { opacity: 0.5; }

/* Closing a pane puts it in the drawer, so the cross is a quiet one: on
   the tab in front, and on whichever tab the pointer or the focus is at.
   Hidden by `visibility' and not by `display', or a strip of them would
   change width under the pointer and the tab being aimed at would move
   out from under it. */
.paneshut
{
    flex: 0 0 auto;
    padding: 0 0.45em 0 0.2em;
    font: inherit;
    font-size: 0.85em;
    line-height: 1;
    color: var(--pane-dim, #666666);
    background: transparent;
    border: 0;
    cursor: pointer;
    visibility: hidden;
}

.panetabwrap:hover > .paneshut,
.panetabwrap:focus-within > .paneshut,
.panetab[aria-selected="true"] + .paneshut { visibility: visible; }

.paneshut:hover { color: var(--pane-fg, #202020); }

/* ---- the drawer ----
 *
 * The panes no leaf has room for. A closed pane is put away and not lost,
 * so this is a list of them and not a list of things to be made again.
 */
.panedrawer
{
    flex: 0 0 auto;
    display: flex;
    gap: 0.3em;
    flex-wrap: wrap;
    margin-bottom: 0.4em;
}

/* What the row is, for somebody who has just made it appear by closing
   a pane and is looking for where it went. */
.panedrawerlabel
{
    align-self: center;
    font-size: 0.85em;
    color: var(--pane-dim, #666666);
}

.paneclosed
{
    padding: 0.15em 0.6em;
    font: inherit;
    font-size: 0.85em;
    color: var(--pane-dim, #666666);
    background: var(--pane-panel, #f4f4f4);
    border: 1px dashed var(--pane-line, #dcdcdc);
    border-radius: 3px;
    cursor: pointer;
    touch-action: none;
}

/* The reset, at the far end of the first strip (or of the drawer's row),
   drawn as a button rather than as a tab or a closed pane. */
.panereset
{
    margin-left: auto;
    display: flex;
    align-items: center;
    padding: 0 0.3em;
}

.panereset > button
{
    padding: 0.1em 0.5em;
    font: inherit;
    font-size: 0.85em;
    color: var(--pane-dim, #666666);
    background: none;
    border: 1px solid var(--pane-line, #dcdcdc);
    border-radius: 3px;
    cursor: pointer;
}

.paneclosed:hover, .panetab:hover, .panereset > button:hover
{
    color: var(--pane-fg, #202020);
}

/*
 * The drawer while a tab is in the air.
 *
 * It is one of the three places a tab can be dropped and the only one
 * that is not always on the screen, since an empty drawer is nothing to
 * look at and takes a row of the window to say so. Which leaves closing
 * the first pane by dragging as the one gesture with nowhere to aim -- so
 * for as long as something is being dragged, there is somewhere.
 */
.panesroot.panedrag > .panedrawer[hidden]
{
    display: flex;
    min-height: 1.7em;
    border: 1px dashed var(--pane-line, #dcdcdc);
    border-radius: 3px;
}

.panesroot.panedrag > .panedrawer[hidden]::after
{
    content: "Drop a tab here to close it";
    align-self: center;
    padding: 0 0.6em;
    font-size: 0.85em;
    color: var(--pane-dim, #666666);
}

/* A layout with every pane closed out of it, which is a box with nothing
   in it and needs to say that it is empty rather than broken. */
.paneempty
{
    margin: auto;
    padding: 1em;
    font-size: 0.9em;
    color: var(--pane-dim, #666666);
    text-align: center;
}

/*
 * One pane filling the layout.
 *
 * By drawing the rest and not showing them rather than by not drawing
 * them. A zoom that rendered the zoomed leaf alone would take every
 * other pane out of the document and put it back on the way out, and the
 * document does not treat that as a move -- a scrolled box is at the top
 * again, an <iframe> loads a second time. Nothing moves here: the tree
 * stands as it was, and everything that is not on the way to the one
 * pane stops being drawn.
 *
 * Which leaves a chain of boxes from the layout down to that pane with
 * one visible child each, and a flex box with one child gives it
 * everything -- so the pane fills the layout by the same arithmetic that
 * laid it out in the first place, in flow, still under the drawer rather
 * than over it. `:has' is what says "on the way to it", and panes.css
 * already asks for `:has' twice above.
 */
.panesroot.panezoom .paneleaf:not(.panefront),
.panesroot.panezoom .panebox:not(:has(.panefront)),
.panesroot.panezoom .panesplit { display: none; }

/* And the fractions do not apply to a split with one child in it. Over
   the inline `flex-grow' panes.js writes, which is what `!important' is
   for and just about the only thing it is for: a share of a split is
   meaningless where there is nothing to share with. */
.panesroot.panezoom .panefront,
.panesroot.panezoom .panebox:has(.panefront) { flex-grow: 1 !important; }

/* Where the panes this render did not draw are kept: in the document,
   so that getElementById still finds them, and not drawn, by the same
   means and for the same reason as a pane behind a tab. */
.panekeep
{
    position: absolute;
    width: 0;
    height: 0;
    overflow: hidden;
    visibility: hidden;
    content-visibility: hidden;
}

/* Where a dragged tab would land, over the pane it would land in. */
.panedrop
{
    position: absolute;
    z-index: 3;
    pointer-events: none;
    background: color-mix(in srgb, var(--pane-held, AccentColor) 20%, transparent);
    border: 2px solid var(--pane-held, AccentColor);
    border-radius: 4px;
}

/* And where it would land in a strip: a line between two tabs, centered
   on the gap. */
.panedrop.paneslot
{
    width: 3px;
    border: 0;
    border-radius: 2px;
    background: var(--pane-held, AccentColor);
    transform: translateX(-50%);
}

.panebody
{
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
    padding: 0 0.6em 0.6em;
    display: flex;
    flex-direction: column;
}

/* A pane is a query container, so a page can lay its own boxes out
   against the room this one has rather than against the window's. That
   is the tiler offering a question rather than answering one: the
   page's own rules about what wraps inside a pane are what make use of
   it. */
body.tiled .panebody { container-type: inline-size; }

/*
 * Over every pane: where a popover goes, now that the pane it belongs
 * beside is a box that scrolls and would clip it.
 *
 * Absolute at the document's origin, so a page goes on placing these in
 * page coordinates exactly as it did before there was a layout. Being
 * positioned makes it the containing block of everything in it, and so
 * it is as wide as the one the body gave them: a box of no width is a
 * popover with no room, every word of it on a line of its own. No
 * height, so it lies over nothing a pointer is aiming at.
 */
.paneoverlay
{
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 0;
}

/* ---- a narrow screen ----
 *
 * Two options for a screen the defaults were not drawn for (`strip' and
 * `lone' in panes.js), each a class on the root.
 *
 * `strip: "scroll"': the tabs and the drawer keep their own widths in one
 * row that scrolls sideways, rather than shrinking every name to fit.
 */
.panescroll .panetabs,
.panescroll > .panedrawer
{
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
}

.panescroll .panetabwrap,
.panescroll > .panedrawer > * { flex: 0 0 auto; }

/* And the reset kept at the row's right edge while the tabs or closed
   panes scroll under it: at the end of a row that scrolls it would be out
   of sight on exactly the screen that has no chord for it. */
.panescroll .panetabs > .panereset,
.panescroll > .panedrawer > .panereset
{
    position: sticky;
    right: 0;
    background: var(--pane-bg, #ffffff);
}

/* `lone': a leaf whose one tab is a pane named there has no strip. */
.paneleaf.panebare > .panetabs
{
    display: none;
}
