/* ---- */
/* Base */
/* ---- */

/* The shared sheet carries the reset, the body font and the margin block */

:root {
    /* Colors */
    --page-background: #ffffff;

    --text: #1b1f24;
    --muted: #6b7280;

    --border: #d8dde3;
    --header-background: #f2f5f8;

    /* Empty cell stripes */
    --empty-cell-light: #fbfcfd;
    --empty-cell-dark: #f4f6f9;

    --chip-background: #f7faff;
    --chip-hover-background: #e9f1ff;

    /* When filtered */
    --quiet-background: #fcfdff;
    --quiet-text: #b6bdc6;
    --quiet-dim: #ccd2d9;

    --accent: #2d6bd8; /* Same blue in diagrams */

    /* Pips */
    --easy: #2f9e44;
    --medium: #c47a05;
    --hard: #d32f2f;

    /* Toast */
    --toast-text: #ffffff;
    --toast-background: #222222;

    /* Sizes */
    --radius: 8px; /* Corner rounding */
}

body {
    background: var(--page-background);
    color: var(--text);
}

#root {
    display: flex;
    flex-direction: column;
    height: 100svh;
}

/* --------------- */
/* Utility Classes */
/* --------------- */

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* Used with .offsetHeight to clear a currently running animation */
.no-transition {transition: none !important}

/* Difficulty banding */

/* A custom property rather than a background, so the dimmed and open chip rules can still override it.
   An inline background would beat every selector */
[data-band="easy"] {--band-color: var(--easy)}
[data-band="medium"] {--band-color: var(--medium)}
[data-band="hard"] {--band-color: var(--hard)}

/* ---------------- */
/* Utility Elements */
/* ---------------- */

button {
    background: none;
    font: inherit;
    cursor: pointer;
    border: 1px solid var(--border);
    border-radius: var(--radius);

    &:focus-visible {
        outline: 1px solid var(--accent);
        outline-offset: -1px;
    }

    @media (hover: hover) {
        &:hover:not(:disabled) {
            border-color: var(--accent);
            background: var(--chip-hover-background);
        }
    }
}

svg {display: block} /* Center the SVG vertically */

/* Links mimic the accent color */
a {
    color: inherit;
    text-underline-offset: 2px;

    &:focus-visible {
        outline: 2px solid var(--accent);
        outline-offset: 2px;
    }

    @media (hover: hover) {&:hover {color: var(--accent)}}
}

button.icon {
    display: grid; /* Needed for Firefox to center icon */
    place-items: center;
    width: 34px;
    height: 34px;
    border: none;
    border-radius: 50%;
    color: var(--muted);

    svg {
        width: 18px;
        height: 18px;
    }
}

/* ------ */
/* Header */
/* ------ */

header {
    color: var(--muted);
    font-size: 0.875rem;

    a {text-decoration-color: var(--quiet-dim)}

    #breadcrumbs {
        display: flex;
        align-items: center;
        gap: 0.5rem;
        padding: 0 1rem;
        background: var(--header-background); /* The tinted ground is the separator, so the page opens on one rule instead of two */

        a:first-child {
            display: grid;
            place-items: center;
            padding: 0.5rem;
            margin-left: -0.5rem;
            text-decoration: none;

            svg {
                width: 16px;
                height: 16px;
            }
        }

        span {color: var(--quiet-dim)}
    }

    #title-row {
        display: flex;
        flex-wrap: wrap;
        align-items: baseline;
        gap: 0 1rem; /* Gap between links in title bar */
        padding: 0.75rem 1rem;
        border-bottom: 1px solid var(--border);

        h1 {
            color: var(--text);
            font-size: 1.5rem;
        }

        #filters-toggle {
            margin-left: auto;
            padding: 0.35rem 0.75rem;

            &[aria-expanded="true"] {
                border-color: var(--accent);
                color: var(--accent);
            }
        }
    }
}

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

#filters {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem; /* Gap between filters */
    margin: 1rem;

    fieldset {
        display: flex;
        flex-wrap: wrap;
        gap: 1rem; /* Gap between options */
        padding: 0.5rem 0.75rem;
        border: 1px solid var(--border);
        border-radius: var(--radius);
    }

    legend {
        color: var(--muted);
        font-size: 0.7rem;
        font-weight: bold;
        text-transform: uppercase;
    }

    label {
        display: flex;
        gap: 0.25rem; /* Gap between label and checkbox/radio */
        text-transform: capitalize;
        cursor: pointer;
    }

    input {
        accent-color: var(--accent);
        cursor: pointer;
    }
}

/* Filters collapsed by default */
header:has(#filters-toggle[aria-expanded="false"]) + #filters {display: none}

/* ---- */
/* Grid */
/* ---- */

#matrix {
    --head-column-width: min(10rem, 30vw);

    flex: 1;
    min-height: 0;
    overflow: auto;
    margin: 1rem;
    font-size: 0.875rem;

    /* Needed here to keep header borders when scrolling */
    border-top: 1px solid var(--border);
    border-left: 1px solid var(--border);

    /* Narrow on -way row headers */
    &[data-transposed] {--head-column-width: 4rem}

    table {
        width: max-content;
        min-width: 100%;
        table-layout: fixed;
        border-collapse: separate; /* Cannot collapse, as it removes header borders when scrolling */
        border-spacing: 0;
    }

    /* Headers and cells */
    :is(th, td) {
        vertical-align: top;
        border-right: 1px solid var(--border);
        border-bottom: 1px solid var(--border);
    }

    /* Headers (all) */
    th {
        position: sticky;
        padding: 0.6rem 0.75rem;
        background: var(--header-background);
        font-weight: normal; /* Drop default th bolding */
        text-align: left;
    }

    /* Column headers */
    thead th {
        top: 0;
        z-index: 1;

        &:not(.corner) {width: 11rem}
    }

    /* Row headers */
    tbody th {
        left: 0;
        z-index: 1;
    }

    .corner {
        left: 0;
        z-index: 2;
        vertical-align: middle;
        width: var(--head-column-width); /* Fixed layout reads the whole column's width off this one cell */
        padding: 0;

        button {margin-inline: auto} /* The swap is a block level grid, so text-align cannot centre it */
    }

    .head-name {font-weight: 600}

    [data-axis="category"] .head-name {text-transform: capitalize}

    .head-description {
        display: block;
        margin-top: 0.2rem;
        color: var(--muted);
        font-size: 0.75rem;
        line-height: 1.35;
    }

    /* Dim the header text on sections with filtered out content */
    [data-quiet] {
        color: var(--quiet-text);

        .head-description {color: var(--quiet-dim)}
    }

    /* Cells */
    td {
        padding: 0.05rem 0.4rem;
        background: var(--page-background);

        /* Only way do gap since flex/grid breaks table layout */
        .chip {margin: 0.35rem 0}

        /* Diagonal stripes indicating empty cells */
        &:empty {background: repeating-linear-gradient(45deg, var(--empty-cell-light) 0 7px, var(--empty-cell-dark) 7px 14px)}
    }
}

/* ----- */
/* Chips */
/* ----- */

.chip {
    display: grid;
    grid-template-columns: 1fr auto; /* The pips hold the right edge while the name wraps freely */
    column-gap: 0.5rem;
    width: 100%; /* A button shrinks to its text, and the chips have to rule one edge down the cell */
    padding: 0.35rem 0.5rem;
    border-radius: 6px;
    background: var(--chip-background);
    text-align: left;
    transition: filter 0.25s; /* For [data-open] glow */

    /* Source order is name, pips, tags, so auto-placement already lands each one in its own slot */
    .chip-name {line-height: 1.3}

    .tags {
        color: var(--muted);
        font-size: 0.75rem;
    }

    .pips {
        display: flex;
        gap: 2px;
        padding-top: 0.35rem;
    }

    .pip {
        width: 5px;
        height: 5px;
        border-radius: 50%;
        background: var(--border);

        &[data-filled] {background: var(--band-color)}
    }

    /* Opacity would wash out the border too */
    &:disabled {
        --band-color: var(--quiet-dim);
        background: var(--quiet-background);
        color: var(--quiet-text);
        cursor: default;

        .tags {color: var(--quiet-dim)}
    }

    /* Bookmark where the visitor was before opening the dialog */
    &[data-open] {
        filter: drop-shadow(0 0 2px var(--accent));

        /* Don't double dip glow with the focus ring*/
        &:focus-visible {outline: none}
    }
}

/* ------ */
/* Viewer */
/* ------ */

#viewer {
    position: fixed;
    inset: 1%;
    width: auto;
    height: auto;
    max-width: none;
    max-height: none;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--page-background);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18);
    color: var(--text);

    /* Scoped to open, or this author display would override the UA's dialog:not([open]) {display: none} */
    &[open] {
        display: grid;
        grid-template-rows: auto minmax(0, 1fr); /* Bar, then the scrolling body */
        grid-template-columns: minmax(0, 1fr);
    }

    /* Light, since a dark scrim hides the page the gutter exists to reveal */
    &::backdrop {background: rgba(0, 0, 0, 0.12)}
}

#viewer-bar {
    display: flex;
    align-items: baseline;
    gap: 0 0.75rem;

    /* Tighter on the right because the row ends in icon buttons, and each already carries 8px of its
       own ground around an 18px glyph */
    padding: 0.75rem 0.6rem 0.75rem 1rem;
    border-bottom: 1px solid var(--border);

    #viewer-title {
        display: flex;
        flex: 1;
        flex-wrap: wrap;
        align-items: baseline;
        min-width: 0; /* Or a long name refuses to shrink and pushes the actions off the end */
        gap: 0 0.75rem;

        h2 {
            font-size: 1.125rem;

            /* Only a tabindex="-1" announce target, not a control: nothing can tab to it, and it has
               no action to confirm, so a ring here would tag something the visitor cannot act on */
            &:focus-visible {outline: none}
        }

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

#viewer-body {
    display: grid;

    /* Both tracks declared for the reason the dialog's are: an undeclared one is sized by its content,
       so a long note set would push the body past the dialog rather than scrolling inside its column */
    grid-template-rows: minmax(0, 1fr);
    grid-template-columns: minmax(0, 1fr) auto;
}

#artwork-frame {
    /* The step buttons sit over the drawing, so a panel is inset to clear them. Declared here rather
       than on .panel-slide because the stacked stepped height in the narrow block reads them back */
    --panel-inset-horizontal: 3rem;
    --panel-inset-vertical: 1rem;

    position: relative; /* The step buttons hang off this */
    display: grid;

    /* One cell for both children, so the layout control sits over the drawing while staying in flow.
       That is what lets it stick to the screen once a stacked drawing runs past a screenful.
       Stretched by its own parent at both widths, so the frame declares no height of its own */
    grid-template-rows: minmax(0, 1fr);

    :is(#artwork, #layout-control) {grid-area: 1 / 1}
}

/* Contain, and never upscale past intended size. The srcset descriptor already sets the image's
   intrinsic size to its layout size, so these caps only ever shrink */
#viewer-body[data-layout="whole"] #artwork {
    display: grid;

    /* Declared, or the implicit row is sized by the image and max-height resolves against it */
    grid-template-rows: minmax(0, 1fr);
    place-items: center;
    padding: 1rem;

    img {
        max-width: 100%;
        max-height: 100%;
    }
}

/* Stepping answers "what happens next", which is how a dive is flown and taught */
#viewer-body[data-layout="stepped"] #artwork {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth; /* The step buttons pass no behavior, so this is what they animate by */
}

.panel-slide {
    flex: 0 0 100%;
    display: grid;
    place-items: center;
    padding: var(--panel-inset-vertical) var(--panel-inset-horizontal);
    scroll-snap-align: center;

    /* preserveAspectRatio contains and centres the panel, so no object-fit is involved */
    svg {
        width: 100%;
        height: 100%;
    }
}

/* Reflowing answers "what shape is the whole thing", which is what someone recalls a dive by */
#viewer-body[data-layout="fitted"] #artwork {
    overflow-y: auto;
    padding: 1rem;

    svg {
        width: 100%;
        height: auto;
    }
}

/* Icon buttons that sit on the drawing rather than in the bar, so they take back the border
   button.icon drops and a ground of their own */
#step-previous,
#step-next {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    border: 1px solid var(--border);
    background: var(--page-background); /* Solid, or the glyph lands on a line of the drawing and vanishes */

    @media (hover: hover) {
        &:hover {color: var(--accent)}
    }
}

#step-previous {left: 0.5rem}
#step-next {right: 0.5rem}

/* Step buttons only belong to the step display */
#artwork-frame:not(:has(.panel-slide)) :is(#step-previous, #step-next) {display: none}

/* Over the drawing rather than in the bar, in the same place at every width, and next to the thing it changes */
#layout-control {
    /* One gap, read twice: the margin sets it in the corner it rests in, the inset sets it against the
       screen edge it pins to. Written apart they drift, and the control lands flush on one of the two */
    --control-gap: 0.75rem;

    /* Sticky rather than absolute: it holds the frame's bottom right where nothing scrolls, and pins
       to the screen where the stacked body does, so no drawing can carry it below the fold */
    position: sticky;
    bottom: var(--control-gap);
    place-self: end end;
    display: flex;
    gap: 2px;
    margin: var(--control-gap);
    padding: 3px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--page-background); /* Solid, for the same reason the step buttons are */
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);

    input {
        position: absolute;
        opacity: 0;
        pointer-events: none; /* Otherwise hidden radio eats clicks */
    }

    label {
        display: grid;
        place-items: center;
        width: 32px;
        height: 32px;
        border-radius: 50%;
        color: var(--muted);
        cursor: pointer;
    }

    svg {
        width: 17px;
        height: 17px;
    }

    @media (hover: hover) {
        label:hover {color: var(--text)}
    }

    /* AFTER the hover rule, so the chosen layout keeps its fill regardless of hover */
    label:has(:checked) {
        background: var(--accent);
        color: var(--page-background);
    }

    label:has(:focus-visible) {
        outline: 2px solid var(--accent);
        outline-offset: 1px;
    }
}

/* One signal, two controls: the i in the bar and the x in the box's own corner. Same shape as the
   filters toggle, where the button holds the state and the stylesheet is what reads it back */
#viewer-bar:has(#notes-toggle[aria-expanded="false"]) + #viewer-body #notes-box {display: none}

/* Nothing written for this dive, so there is nothing to toggle */
#viewer:has(#notes:empty) #notes-toggle {display: none}

/* The one control in that bar carrying a state, so it shows which way the state is. Beats the generic
   button:hover on specificity, so a pressed toggle keeps its ground the way a chosen layout keeps its fill */
#notes-toggle[aria-expanded="true"] {background: var(--chip-hover-background)}

/* Tinted like the grid heads, so the column reads as chrome around the drawing rather than as more page */
#notes-box {
    display: grid;

    /* The x takes a row of its own, so it neither overlaps the prose nor scrolls away with it */
    grid-template-rows: auto minmax(0, 1fr);
    width: 21rem;
    padding: 1.25rem;
    border-left: 1px solid var(--border);
    background: var(--header-background);

    /* Nothing written for this dive. The box carries the column, so hiding it is the whole of it */
    &:has(#notes:empty) {display: none}

    #notes-close {
        justify-self: end;
        margin: -0.5rem -0.6rem 0 0; /* -0.6 to line up with viewer x */
    }
}

/* Still the list itself rather than a wrapper holding one, since :empty above is a true signal only
   while nothing else can sit inside it */
#notes {
    overflow: auto;

    /* Same treatment as a filter legend, since both label a group rather than name a thing.
       The key stays lowercase in notes.js and the casing lives here */
    dt {
        margin-bottom: 0.2rem;
        color: var(--muted);
        font-size: 0.7rem;
        font-weight: bold;
        text-transform: uppercase;
    }

    dd {line-height: 1.5}

    dd + dt {margin-top: 1rem} /* Between notes, never between a label and its own prose */

    a {color: var(--accent)}
}

/* ----- */
/* Toast */
/* ----- */

/* A popover so it appears above dialogs */
#toast {
    position: fixed;
    inset: auto 0 30px;
    margin: 0 auto;
    padding: 12px 18px;
    border: none;
    border-radius: 21px;
    background: var(--toast-background);
    color: var(--toast-text);
    transition: opacity var(--toast-fade) ease-out var(--toast-hold); /* page.js owns both */

    &.hide {opacity: 0}
}

/* ------ */
/* Narrow */
/* ------ */

/* Splitting the body sideways stops paying once the drawing's share drops too far, so the notes take
   the width and the artwork sits above them. A flex column leaves the grid rules inert, so the two
   layouts are one mechanism swapping out rather than two fighting.
   The pair share one scroller. Nothing under the body takes a scrollbar of its own, since a box that
   scrolls inside a box that scrolls is two bars deep for one gesture */
@media (max-width: 900px) {
    #viewer-body {
        display: flex;
        flex-direction: column;
        overflow: auto;
        container-type: inline-size; /* The stepped height below is a share of this width */
    }

    /* Keeps the height its drawing asked for. Left to shrink it would absorb every pixel the notes
       below it need, and the drawing would paint over them rather than the body scrolling */
    #artwork-frame {flex: none}

    /* Holds the screenful the vector is about to fill, so the notes do not jump up and back while a
       dive is in flight. Stepped has its own height below, and whole draws with no fetch at all */
    #artwork-frame:has(#artwork:empty) {height: 100%}

    /* Whole is the one layout that has to fit, since seeing the shape at once is the whole of what it
       is for, so it keeps the screenful as a cap. It costs no second bar: whole scrolls nothing */
    #viewer-body[data-layout="whole"] #artwork-frame {max-height: 100%}

    /* Fitted draws past a screenful and stacked that is the body's scroll to make. Scrolling here as
       well is the second bar inside the first */
    #viewer-body[data-layout="fitted"] #artwork {overflow: visible}

    /* Stepped cannot take a height from its drawing: the panel is sized by the box it is given, so a
       frame sized by the panel is circular. The panel's ratio breaks that, being data rather than
       layout, and it is applied to the width the drawing actually gets rather than the frame's, or the
       inset it never uses is counted as drawing and the frame overshoots by inset / ratio.
       Gated on the panels being in, since the ratio arrives with them and an unset one invalidates the
       whole calc. The empty rule above is what holds the frame open until then */
    #viewer-body[data-layout="stepped"] #artwork-frame:has(.panel-slide) {
        height: calc((100cqw - var(--panel-inset-horizontal) * 2) / var(--panel-aspect) + var(--panel-inset-vertical) * 2);
        max-height: 100%; /* Hands the screenful back for a panel too tall to fit at this width */
    }

    #notes-box {
        flex: 1 0 auto; /* Grows, so the tint reaches the bottom of the screen rather than the last line of prose */
        width: auto; /* The declared width is a column track's worth, and stacked there is no column */
        border-top: 1px solid var(--border);
        border-left: none;
    }
}

/* -------------- */
/* Reduced motion */
/* -------------- */

@media (prefers-reduced-motion: reduce) {
    #toast {transition: none}

    #viewer-body[data-layout="stepped"] #artwork {scroll-behavior: auto}
}
