/* =========================================================
   personal. a museum wall you drive past sideways and at some point
   end up at the beginning again.

   the wall is a row of equally wide sections, each with the wall image
   in it exactly once. two things follow from that: the lamps sit at the
   same spot in every section (so the space around them can be kept
   free), and the endless jump-back is always a multiple of one section
   width, so it is invisible.
   ========================================================= */

:root {
    /* wall image. ratio = width / height of the file (1536 / 1024).
       if that does not match, the picture gets distorted.

       the path has to be absolute: a relative url() inside a custom property
       gets resolved against the file where var() is USED, not against this
       one. it happened to work while this file sat in the site root and broke
       the moment it moved into /personal/. */
    --wall-image: url("/assets/images/personal/wall-dithered.png");
    --wall-image-ratio: 1.5;

    /* where in the picture the panelling starts, as a share of the height.
       nothing is hung below that */
    --wainscot-ratio: 0.62;

    --rail: 46px; /* height of the top bar */
    --gap: 8px; /* distance between the frames */

    /* grid of one section. the columns divide up the section width, so
       purely relative - picture and hanging cannot drift apart.
       the number of rows is set by the script, and in such a way that the
       cells come out square. only then does "frame 3 wide, 4 high" also mean
       "aspect ratio 3:4", and the search for the matching frame is correct.
       the value here is only the state until the script does its maths */
    --columns: 40; /* start value, the script computes it from the target cell size */
    --rows: 15;

    --section: calc(100vh * var(--wall-image-ratio));

    --cream: #f3efc2;
    --brown: #6b4a2f;
    --dark: #2b1f16;
}

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    background-color: #1c2019;
    font-family: monospace;
    color: var(--dark);
    overflow: hidden; /* only the room is scrolled, never the page */
}

/* ---------- top bar, lies over the wall ---------- */
.wall-bar {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 10;
    height: var(--rail);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 0 16px;

    background-image: linear-gradient(to bottom, rgba(8, 10, 6, 0.8), rgba(8, 10, 6, 0));
    color: var(--cream);
    font-size: 0.72rem;
    text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.7);

    /* the bar itself does not catch the mouse, otherwise the pull stands
       still across its whole width. only the link does */
    pointer-events: none;
}

.wall-bar a {
    pointer-events: auto;
    color: var(--cream);
}

/* ---------- the room: the only thing that scrolls ---------- */
.room {
    position: fixed;
    inset: 0;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-behavior: auto; /* has to be abrupt, otherwise the jump-back animates */
    scrollbar-width: none;
    outline: none;
}

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

.wall {
    display: flex;
    width: max-content;
    height: 100%;
}

/* ---------- one wall section ---------- */
.section {
    position: relative;
    flex: 0 0 auto;
    width: var(--section);
    height: 100%;

    /* 100% 100% instead of cover: width and height stand in the ratio of the
       file, so this is exactly one to one and nothing gets distorted */
    background-image: var(--wall-image);
    background-size: 100% 100%;
    background-repeat: no-repeat;

    /* if a hanging does not fit into the grid, it grows out of the section.
       better to cut it off than to blow up the section width, because the
       endless run hangs off that */
    overflow: hidden;
}

/* ---------- the hanging ---------- */
/* the grid is purely relative: columns divide the section width, rows the
   wainscot height. that way wall image and hanging never drift apart.
   which picture lies in which cell is computed by the script */
.images {
    position: absolute;
    left: 0;
    top: var(--rail);
    width: 100%;
    /* up to just above the panelling */
    height: calc(100vh * var(--wainscot-ratio) - var(--rail));

    display: grid;
    grid-template-columns: repeat(var(--columns), 1fr);
    grid-template-rows: repeat(var(--rows), 1fr);
}

/* ---------- one frame ---------- */
/* place in the grid and choice of frame come from the script, only the looks
   are in here. the frame itself is a cut-out png from tools/frames */
.frame {
    margin: var(--gap);
    position: relative;
    color: inherit;
    text-decoration: none;

    /* font sizes in the marker refer to the frame itself, not to the window -
       otherwise a small frame gets the same type as a large one and it does
       not fit into the opening */
    container-type: size;

    transform: rotate(var(--tilt, 0deg)) scale(var(--zoom, 1));
    transition: transform 0.18s ease-out;
}

.frame:hover,
.frame:focus-visible {
    --tilt: 0deg;
    --zoom: 1.05;
    z-index: 5; /* otherwise the neighbours lie over the enlarged frame */
}

/* the moulding. fills the box - that is why the percentage values of the
   opening, which the script sets from the manifest, are correct.
   pixelated: the frames are deliberately tight at 260px, when scaling up
   the pixels should stay put instead of blurring.
   drop-shadow instead of box-shadow: the shadow has to follow the cut-out
   shape, not the rectangle around it */
.rail {
    display: block;
    position: relative;
    z-index: 1; /* lies over the picture and crops it */
    width: 100%;
    height: 100%;
    image-rendering: pixelated;
    filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.55));
    transition: filter 0.18s ease-out;
}

.frame:hover .rail,
.frame:focus-visible .rail {
    filter: drop-shadow(0 12px 20px rgba(0, 0, 0, 0.7));
}

/* the picture lies BEHIND the rail and sticks out a bit under it - the
   opening of the frame pngs is transparent, so the rail crops the picture
   with its own edge. exactly like a real rebate, and a measurement that is
   off by one pixel does not show because of it.
   position and size are set by the script in percent */
.inner {
    position: absolute;
    z-index: 0;
    overflow: hidden;
}

/* frames whose opening is still opaque (openingClear: false in the manifest):
   there the picture lies in front of the rail, otherwise the old backing
   board covers it */
.inner.front {
    z-index: 2;
}

/* cover, not contain: the frame was chosen by its opening to match the
   picture after all, so hardly anything is missing. that way the opening is
   filled edge to edge instead of having a strip of wall next to it */
.inner img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    image-rendering: pixelated; /* the thumbnails are dithered */
    transition: filter 0.18s ease-out;
}

/* awake: the motif steps back so the type carries on top of it */
.frame:hover .inner img,
.frame:focus-visible .inner img {
    filter: brightness(0.5) saturate(0.6);
}

/* ---------- marker, appears on hover ---------- */
/* lies over the whole frame, not only over the opening: so the text may run
   onto the rail. with a narrow picture in a thick rail it would otherwise be
   squeezed into a postage stamp.
   no little box and no border around it, but cream type with a hard outline -
   just like the stickers on the start page and the objects in the bait shop */
.marker {
    position: absolute;
    inset: 0;
    z-index: 3; /* also over a picture that lies in front of the rail */

    display: grid;
    align-content: center;
    justify-items: center;
    gap: 0.2em;
    padding: 2% 4%;

    /* cqw = percent of the frame width. that way the type is smaller in a
       small frame and always fits into the opening */
    font-size: clamp(0.5rem, 8cqw, 1.3rem);
    line-height: 1.15;
    text-align: center;
    overflow-wrap: anywhere;

    /* hard outline in four directions plus a soft shadow. the hard one makes
       the edge, the soft one carries the text also where it runs onto bright
       gold - the rail is not darkened on hover after all.
       text-shadow and not drop-shadow: that would be five filter passes per
       frame, and those sit on the gpu even at opacity 0 */
    color: #fff7dd;
    text-shadow: 1px 1px 0 var(--dark), -1px 1px 0 var(--dark), 1px -1px 0 var(--dark),
        -1px -1px 0 var(--dark), 0 2px 7px rgba(0, 0, 0, 0.9);

    opacity: 0;
    transition: opacity 0.15s ease-out;
    pointer-events: none;
}

.marker .caption {
    font-size: 0.62em;
    opacity: 0.85;
}

.frame:hover .marker,
.frame:focus-visible .marker {
    opacity: 1;
}

/* ---------- pull at the edge ---------- */
body.pull-left .room,
body.pull-right .room {
    cursor: ew-resize;
}

@media (prefers-reduced-motion: reduce) {
    .frame {
        transition: none;
    }
    .frame:hover,
    .frame:focus-visible {
        --zoom: 1;
    }
}

/* ---------- without javascript ---------- */
.no-js {
    position: fixed;
    inset: var(--rail) 0 auto 0;
    z-index: 20;
    margin: 0;
    padding: 10px 16px;
    background-color: var(--cream);
    font-size: 0.72rem;
}
