/* Thumbnail grid + full-size viewer.
   No JavaScript.  The viewer is a horizontal scroll-snap track that is
   revealed when one of its slides is the :target, so swiping between
   images is ordinary native scrolling -- the most reliable touch
   behaviour available -- and prev/next are plain links. */

/* ---- grid ------------------------------------------------------ */

.grid {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin: 0.75rem 0;
}

/* Each thumbnail is a small white card on the grey, so the mosaic reads
   as a set of separate pieces rather than one continuous field. */
.grid .thumb {
    display: block;
    line-height: 0;
    padding: 3px;
    background: var(--card);
    border-bottom: 0;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
    transition: box-shadow 0.12s ease, transform 0.12s ease;
}

.grid .thumb img {
    display: block;
}

.grid .thumb:hover,
.grid .thumb:focus-visible {
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.28);
    transform: translateY(-1px);
    outline: none;
}

.grid .thumb:focus-visible {
    box-shadow: 0 0 0 2px var(--accent);
}

/* Two gallery pages wrap the run of thumbnails in a float box, which
   would otherwise collapse around the flex grid. */
.leftbox:has(> .grid),
.rightbox:has(> .grid) {
    float: none;
    width: auto;
    margin: 0;
    padding: 0;
}

/* ---- viewer ---------------------------------------------------- */

.lightbox {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: flex;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    background: rgba(18, 18, 18, 0.95);
    /* Hidden with visibility rather than display, so the track keeps its
       layout and stays scrollable.  A display:none container cannot be
       scrolled to a fragment, which would open every link on slide 1. */
    visibility: hidden;
    opacity: 0;
    transition: opacity 0.15s linear;
    scrollbar-width: none;
}

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

body:has(.lb-slide:target) .lightbox {
    visibility: visible;
    opacity: 1;
}

.lb-slide {
    position: relative;
    flex: 0 0 100%;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 3rem 1rem 1.5rem;
    box-sizing: border-box;
    scroll-snap-align: center;
    /* Two rows: the image gets whatever is left after the caption has
       taken the height it needs.  minmax(0,1fr) is load-bearing -- a
       plain 1fr floors at the image's intrinsic height, which lets a
       tall image push the caption off the bottom of the screen. */
    display: grid;
    grid-template-rows: minmax(0, 1fr) auto;
    justify-items: center;
    align-items: center;
    row-gap: 0.75rem;
}

/* Focused on open so the arrow keys reach the scroll container.  It's a
   container, not a control, so the ring would only be noise. */
.lb-slide:focus {
    outline: none;
}

.lb-slide img {
    /* Never upscale past the file's own pixels -- these top out at 800px
       on the long edge and blowing them up just looks soft.
       max-height now resolves against the 1fr row, not the whole slide. */
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    min-height: 0;
    object-fit: contain;
}

.lb-slide figcaption {
    max-width: 44rem;
    color: #e8e8e8;
    text-align: center;
    font-size: 0.9rem;
    line-height: 1.45;
}

.lb-slide figcaption b {
    display: block;
    font-size: 1.05rem;
    font-weight: bold;
}

.lb-slide figcaption span {
    color: #b4b4b4;
}

/* ---- controls -------------------------------------------------- */

/* Absolute, not fixed: every slide carries its own set, and fixed
   positioning would stack all of them on the same spot so the wrong
   slide's link would take the click. */
.lb-close,
.lb-prev,
.lb-next {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #e8e8e8;
    text-decoration: none;
    font-weight: normal;
    -webkit-tap-highlight-color: transparent;
}

.lb-close {
    top: 0.25rem;
    right: 0.5rem;
    width: 2.75rem;
    height: 2.75rem;
    font-size: 1.9rem;
    line-height: 1;
    /* Above the next/prev strips: the close button sits inside the right
       strip's area, and without this the later-painted strip takes the
       click and advances a slide instead of closing. */
    z-index: 2;
}

.lb-close::before {
    content: "\00D7";
}

.lb-prev,
.lb-next {
    /* Starts below the close button rather than at the very top, so the
       two controls don't overlap at all. */
    top: 3.25rem;
    bottom: 0;
    width: 18%;
    max-width: 6rem;
    font-size: 2.25rem;
    opacity: 0.65;
    z-index: 1;
}

.lb-prev {
    left: 0;
}

.lb-next {
    right: 0;
}

.lb-prev::before {
    content: "\2039";
}

.lb-next::before {
    content: "\203A";
}

.lb-close:hover,
.lb-prev:hover,
.lb-next:hover {
    color: #fff;
    opacity: 1;
}

.lb-close:focus-visible,
.lb-prev:focus-visible,
.lb-next:focus-visible {
    outline: 2px solid #e8e8e8;
    outline-offset: -4px;
}

/* On a phone the gesture is the swipe, so the arrow hit zones only get
   in the way of tapping the image. */
@media (max-width: 45rem) {
    .lb-prev,
    .lb-next {
        display: none;
    }

    .lb-slide {
        padding: 2.75rem 0.5rem 0.5rem;
    }
}
