/* Visitor-facing styles.
 *
 * Values that an artist controls arrive as custom properties from the generated
 * /theme.css. Choices that switch a whole layout arrive as attributes on <body>,
 * and the rules for each live here rather than being generated per request. */

/* ── Reset ────────────────────────────────────────────────────────────────── */

*,
*::before,
*::after {
    box-sizing: border-box;
}

/* An element given the hidden attribute must disappear even when a class sets a
   display of its own. */
[hidden] {
    display: none !important;
}

/* ── Theme defaults ───────────────────────────────────────────────────────── */
/* Overridden per site by /theme.css, generated from validated settings. Nothing
   artist-supplied is ever interpolated into CSS directly. */

:root {
    --color-bg: #ffffff;
    --color-text: #111111;
    --color-muted: #888888;
    --color-accent: #111111;
    --font-heading: system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
    --font-body: system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
    --font-size: 15px;
    --heading-size: 32px;
    --feed-columns: 4;
    --grid-ratio: 1 / 1;
    --gap: 4px; /* overridden per site by the generated theme */
}

html,
body {
    margin: 0;
    padding: 0;
    background: var(--color-bg);
    color: var(--color-text);
    font-family: var(--font-body);
    font-size: var(--font-size);
    line-height: 1.6;
}

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

h1, h2, h3 {
    font-family: var(--font-heading);
    font-weight: 400;
}

/* ── Shell ────────────────────────────────────────────────────────────────── */

/* One grid whose shape follows the menu placement: a top bar stacks the header
   above the work, a side bar puts them beside each other. */

.shell {
    display: grid;
    gap: 2rem;
    padding: 0 1.5rem;
}

body[data-menu="top"] .shell {
    grid-template-columns: 1fr;
}

body[data-menu="left"] .shell,
body[data-menu="right"] .shell {
    grid-template-columns: minmax(9rem, 13rem) 1fr;
    align-items: start;
    gap: 3rem;
}

body[data-menu="right"] .shell {
    grid-template-columns: 1fr minmax(9rem, 13rem);
}

body[data-menu="right"] .site-header {
    order: 2;
}

/* A side bar stays put while the work scrolls past it. */
body[data-menu="left"] .site-header,
body[data-menu="right"] .site-header {
    position: sticky;
    top: 0;
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    padding: 2.5rem 0;
}

/* In a column the two controls act on different axes, which is what makes both of
   them useful: the logo setting decides how far down the bar the block sits, and
   the menu setting decides how its contents line up across the bar's width. */

/* Stated for every value rather than leaning on the initial one, so the computed
   result is the same thing the setting says. */
body[data-menu="left"][data-header-align="start"] .site-header,
body[data-menu="right"][data-header-align="start"] .site-header {
    justify-content: flex-start;
}

body[data-menu="left"][data-header-align="center"] .site-header,
body[data-menu="right"][data-header-align="center"] .site-header {
    justify-content: center;
}

body[data-menu="left"][data-header-align="end"] .site-header,
body[data-menu="right"][data-header-align="end"] .site-header {
    justify-content: flex-end;
}

body[data-menu="left"][data-menu-align="start"] .site-header,
body[data-menu="right"][data-menu-align="start"] .site-header {
    align-items: flex-start;
    text-align: left;
}

body[data-menu="left"][data-menu-align="center"] .site-header,
body[data-menu="right"][data-menu-align="center"] .site-header {
    align-items: center;
    text-align: center;
}

body[data-menu="left"][data-menu-align="end"] .site-header,
body[data-menu="right"][data-menu-align="end"] .site-header {
    align-items: flex-end;
    text-align: right;
}

/* The nav follows the header rather than keeping its own alignment, so the logo
   and the links cannot end up on opposite sides of a narrow column. */
body[data-menu="left"] .site-nav,
body[data-menu="right"] .site-nav {
    align-items: inherit;
}

/* ── Header ───────────────────────────────────────────────────────────────── */

.site-header {
    padding: 3rem 0 2rem;
}

body[data-menu="top"] .site-header {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
    align-items: flex-start;
}

body[data-menu="top"][data-header-align="center"] .site-header {
    align-items: center;
    text-align: center;
}

body[data-menu="top"][data-header-align="end"] .site-header {
    align-items: flex-end;
    text-align: right;
}

/* Menu on the same line as the logo. The header becomes a row, and the menu is
   pushed to the far side unless it was asked to sit next to the logo. */
body[data-menu="top"][data-menu-inline="yes"] .site-header {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
}

body[data-menu="top"][data-menu-inline="yes"][data-menu-align="start"] .site-header {
    justify-content: flex-start;
    gap: 2.5rem;
}

body[data-menu="top"][data-menu-inline="yes"][data-menu-align="center"] .site-header {
    justify-content: center;
    gap: 2.5rem;
}

/* The tagline drops to its own line rather than joining the row. */
body[data-menu="top"][data-menu-inline="yes"] .site-tagline {
    order: 3;
    flex-basis: 100%;
}

.site-title {
    display: inline-block;
    color: inherit;
    text-decoration: none;
    font-family: var(--font-heading);
    /* The site's own setting is the ceiling, not the size: the title still has to
       give way on a narrow screen, where 3.6vw is the smaller number. */
    font-size: clamp(1.3rem, 3.6vw, var(--heading-size));
    letter-spacing: 0.14em;
    text-transform: uppercase;
}

.site-title:hover {
    opacity: 0.65;
}

.site-tagline {
    margin: 0;
    font-size: 0.85rem;
    letter-spacing: 0.05em;
    color: var(--color-muted);
}

.site-logo {
    display: block;
    /* A logo is uploaded at whatever size its file happens to be, and the one
       that prompted this was 500px wide. Without a ceiling it sets the width of
       the header, and because the shell's track is sized from its contents, of
       the whole page with it -- so a phone got a 500px layout and every
       photograph hung off the side of the screen. */
    max-width: 100%;
    height: auto;
}

.site-header {
    min-width: 0; /* nothing in the header may widen the shell, as a logo once did */
}

.site-main {
    min-width: 0; /* lets a grid child shrink inside the shell */
    padding-bottom: 6rem;
}

/* With a side bar the header sits beside the work rather than above it, so the
   shell's gap is horizontal only and the content would otherwise start flush
   against the top of the window. */
body[data-menu="left"] .site-main,
body[data-menu="right"] .site-main {
    padding-top: 2.5rem;
}

/* ── Navigation ───────────────────────────────────────────────────────────── */

.site-nav {
    display: flex;
    flex-wrap: wrap;
    gap: 1.25rem;
}

body[data-menu="top"][data-menu-align="center"] .site-nav {
    justify-content: center;
}

body[data-menu="top"][data-menu-align="end"] .site-nav {
    justify-content: flex-end;
}

/* A side bar is a column, so its menu stacks however it is aligned. */
body[data-menu="left"] .site-nav,
body[data-menu="right"] .site-nav {
    flex-direction: column;
    gap: 0.6rem;
    margin-top: 1.5rem;
}

.site-nav__link {
    color: var(--color-muted);
    text-decoration: none;
    font-size: 0.78rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    padding-bottom: 2px;
    border-bottom: 1px solid transparent;
}

.site-nav__link:hover {
    color: var(--color-text);
}

.site-nav__link.is-active {
    color: var(--color-text);
    border-bottom-color: var(--color-text);
}

/* An address is a literal, not a label. It keeps its own case, because the part
   before the @ is case-sensitive and uppercasing it would misquote it, and it
   loses the tracking, which makes a long address harder to read rather than
   easier. It may wrap anywhere: in a vertical bar there is not much width, and
   an address that overflows is worse than one broken across two lines. */
.site-nav__link--contact {
    text-transform: none;
    letter-spacing: 0.02em;
    overflow-wrap: anywhere;
}

/* ── Grid layouts ─────────────────────────────────────────────────────────── */

/* Uniform grid: every cell the same shape, images cropped to fill. */
body[data-grid="grid"] .grid,
body[data-grid="grid"] .sequence {
    display: grid;
    grid-template-columns: repeat(var(--feed-columns), 1fr);
    gap: var(--gap);
}

body[data-grid="grid"] .tile__frame {
    aspect-ratio: var(--grid-ratio);
    overflow: hidden;
}

body[data-grid="grid"] .tile__frame img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Masonry: items keep their own proportions and pack into columns, so rows do not
   line up. CSS multi-column does this natively — no script, and no layout pass
   waiting on images to load. */
body[data-grid="masonry"] .grid,
body[data-grid="masonry"] .sequence {
    display: block;
    column-count: var(--feed-columns);
    column-gap: var(--gap);
}

body[data-grid="masonry"] .tile {
    /* Block, not inline-block. An inline-block sits on a text baseline, so the
       line box reserves room for descenders underneath it: with the gap set to
       20px the columns were 20px apart and the rows 27px, and no margin
       accounts for the difference. A block has no baseline to sit on. */
    display: block;
    width: 100%;
    margin-bottom: var(--gap);
    break-inside: avoid; /* a tile must not be split across two columns */
}

/* With scripting, feed.js deals the tiles into columns in reading order, so the
   sequence runs left to right instead of down each column. The column count is
   still set above; the script reads it back, breakpoints included. */
body[data-grid="masonry"] .is-masonry {
    display: flex;
    align-items: flex-start;
    gap: var(--gap);
}

.masonry__column {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--gap);
}

body[data-grid="masonry"] .is-masonry .tile {
    margin-bottom: 0;
}

body[data-grid="masonry"] .tile__frame img,
body[data-grid="column"] .tile__frame img {
    width: 100%;
    height: auto;
}

/* One wide column: a single piece at a time, at its own proportions. */
body[data-grid="column"] .grid,
body[data-grid="column"] .sequence {
    display: grid;
    grid-template-columns: 1fr;
    /* A single column needs real separation between pieces whatever the grid gap
       is set to, so the setting raises the floor rather than replacing it. */
    gap: max(2rem, var(--gap));
    max-width: 46rem;
    margin: 0 auto;
}

/* ── Tiles ────────────────────────────────────────────────────────────────── */

.tile {
    margin: 0;
    color: inherit;
    text-decoration: none;
}

.tile__frame {
    display: block;
    position: relative;
    color: inherit;
}

.tile__frame img {
    display: block;
}

.tile--audio .tile__frame {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    padding: 0.75rem;
    background: color-mix(in srgb, var(--color-muted) 15%, transparent);
}

body[data-grid="grid"] .tile--audio .tile__frame {
    aspect-ratio: var(--grid-ratio);
}

.tile--audio audio {
    width: 100%;
    max-width: 20rem;
}

/* ── Captions ─────────────────────────────────────────────────────────────── */

.tile__caption {
    display: block;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    color: var(--color-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

body[data-caption="none"] .tile__caption {
    display: none;
}

body[data-caption="below"] .tile__caption {
    padding-top: 0.4rem;
}

/* Over the image rather than beneath it, on a band of the site's own background
   rather than a gradient: a gradient is a photographic effect of its own, applied
   to work that did not ask for one. A flat band is plainly a label. */
body[data-caption="overlay"] .tile__frame .tile__caption {
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    padding: 0.4rem 0.6rem;
    color: var(--color-muted);
    background: var(--color-bg);
}

/* An audio tile has no image to sit over, so its caption stays plain. */
body[data-caption="overlay"] .tile--audio .tile__caption {
    position: static;
    padding: 0;
    color: var(--color-muted);
    background: none;
}

/* Only while pointing at the piece.

   Guarded by a pointer query: on a touch screen there is no hover, and a caption
   that waits for one would never appear at all. Opacity rather than display, so a
   caption beneath the work does not push the page around as the pointer moves. */
@media (hover: hover) {
    body[data-caption-hover="yes"] .tile__caption {
        opacity: 0;
        transition: opacity 120ms ease;
    }

    body[data-caption-hover="yes"] .tile:hover .tile__caption,
    body[data-caption-hover="yes"] .tile:focus-within .tile__caption {
        opacity: 1;
    }
}

/* Someone who has asked for less movement gets the caption without the fade. */
@media (hover: hover) and (prefers-reduced-motion: reduce) {
    body[data-caption-hover="yes"] .tile__caption {
        transition: none;
    }
}

/* The attribution on a community-feed tile is always shown: work away from the
   site it belongs to has to say whose it is. */
.tile__credit {
    display: block;
    padding-top: 0.35rem;
    font-size: 0.72rem;
    letter-spacing: 0.05em;
}

.tile__credit a {
    color: var(--color-muted);
    text-decoration: none;
}

.tile__credit a:hover {
    color: var(--color-text);
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* ── Empty states ─────────────────────────────────────────────────────────── */

.placeholder {
    padding: 4rem 0;
    color: var(--color-muted);
    font-style: italic;
}

body[data-menu="top"][data-header-align="center"] .placeholder {
    text-align: center;
}

#sentinel {
    height: 80px;
}

/* ── Error pages ──────────────────────────────────────────────────────────── */

/* An error is its own statement rather than a page of work, so the block is
   centred in whatever room the layout leaves it, in every menu position. */

.error-page {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.9rem;
    min-height: min(60vh, 26rem);
    padding: 3rem 0;
    text-align: center;
}

/* Decoration, and hidden from assistive technology on those grounds: the heading
   below it says the same thing in words. */
.error-page__code {
    margin: 0;
    font-family: var(--font-heading);
    font-size: clamp(4.5rem, 16vw, 9rem);
    line-height: 0.85;
    letter-spacing: -0.03em;
    color: var(--color-muted);
    opacity: 0.4;
}

.error-page__heading {
    margin: 0;
    font-size: clamp(1.15rem, 2.4vw, 1.5rem);
    letter-spacing: 0.06em;
}

.error-page__body {
    margin: 0;
    max-width: 28rem;
    color: var(--color-muted);
}

.error-page__home {
    margin-top: 0.6rem;
    padding-bottom: 3px;
    color: var(--color-muted);
    font-size: 0.78rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    text-decoration: none;
    border-bottom: 1px solid currentColor;
}

.error-page__home:hover {
    color: var(--color-text);
}

/* ── Lightbox ─────────────────────────────────────────────────────────────── */

#lightbox {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--color-bg);
    cursor: zoom-out;
    /* Horizontal drags belong to the lightbox's swipe, not to the browser, which
       would otherwise take them as a scroll or a history swipe and cancel the
       gesture halfway. Pinching to look closer still works. */
    touch-action: pinch-zoom;
}

/* Once zoomed in, a drag is for moving around the enlarged image, so the browser
   gets it back and the swipe stands aside. */
#lightbox.is-zoomed {
    touch-action: auto;
}

#lightbox-figure {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    max-width: 92vw;
    margin: 0;
}

#lightbox-img {
    max-width: 100%;
    max-height: 78vh;
    object-fit: contain;
    cursor: e-resize; /* signals that a click advances rather than closes */
    /* A swipe that starts on the image must move it, not pick it up to drop
       somewhere else. */
    -webkit-user-drag: none;
    user-select: none;
}

/* The neighbours a swipe brings in, centred like the image itself and moved
   into place by the script. They take the same limits as the image, so a
   neighbour arrives at the size it will be shown at. */
.lightbox__peek {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

.lightbox__peek[hidden] {
    display: none;
}

.lightbox__peek img {
    max-width: 92vw;
    max-height: 78vh;
    object-fit: contain;
}

/* Over the left half a click goes back instead. */
#lightbox-img.is-back {
    cursor: w-resize;
}

#lightbox-caption {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    font-size: 0.8rem;
    letter-spacing: 0.06em;
    color: var(--color-muted);
    text-align: center;
}

/* The capture settings, a step quieter than the title: they answer a question
   someone went looking for rather than announcing themselves. */
.lightbox__exif {
    font-size: 0.72rem;
    letter-spacing: 0.04em;
}

.lightbox__tools {
    position: fixed;
    top: 1rem;
    right: 1rem;
    display: flex;
    gap: 0.5rem;
}

.lightbox__button {
    padding: 0.35rem 0.6rem;
    border: 1px solid var(--color-muted);
    border-radius: 0;
    background: var(--color-bg);
    color: var(--color-text);
    font-family: inherit;
    font-size: 0.72rem;
    letter-spacing: 0.06em;
    cursor: pointer;
}

.lightbox__button:hover {
    border-color: var(--color-text);
}

/* In fullscreen the image should use the whole screen, not 78% of a viewport that
   no longer has a header above it. */
#lightbox:fullscreen {
    background: #000000;
}

#lightbox:fullscreen #lightbox-figure {
    max-width: 100vw;
}

#lightbox:fullscreen #lightbox-img,
#lightbox:fullscreen .lightbox__peek img {
    max-height: 100vh;
    max-width: 100vw;
}

/* One block, pinned: the title and the settings keep their order over the bottom
   of the image rather than each finding its own place. */
#lightbox:fullscreen #lightbox-caption {
    position: fixed;
    bottom: 1rem;
    color: #cccccc;
}

#lightbox:fullscreen .lightbox__exif {
    color: #aaaaaa;
}

body.is-locked {
    overflow: hidden;
}

/* ── Albums ───────────────────────────────────────────────────────────────── */

.albums {
    display: grid;
    gap: 1.5rem var(--gap);
    grid-template-columns: repeat(var(--feed-columns), 1fr);
}

.albums--list {
    grid-template-columns: 1fr;
    max-width: 34rem;
    margin: 0 auto;
}

.album-card {
    display: block;
    color: inherit;
    text-decoration: none;
}

.album-card__cover {
    display: block;
    aspect-ratio: var(--grid-ratio);
    overflow: hidden;
    background: color-mix(in srgb, var(--color-muted) 15%, transparent);
}

.albums--list .album-card__cover {
    aspect-ratio: 16 / 9;
}

.album-card__cover img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.album-card:hover .album-card__cover img {
    opacity: 0.85;
}

.album-card__blank {
    display: block;
    height: 100%;
}

.album-card__title {
    display: block;
    margin-top: 0.5rem;
    font-family: var(--font-heading);
    font-size: 0.9rem;
}

.album-card__count {
    display: block;
    font-size: 0.72rem;
    color: var(--color-muted);
}

.album__header {
    max-width: 34rem;
    margin: 0 auto 2.5rem;
    text-align: center;
}

.album__title {
    margin: 0 0 0.6rem;
    font-size: 1.2rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.album__description {
    margin: 0;
    color: var(--color-muted);
    font-size: 0.9rem;
}

/* ── Directory ────────────────────────────────────────────────────────────── */

.directory {
    display: grid;
    grid-template-columns: repeat(var(--feed-columns), 1fr);
    gap: 2rem var(--gap);
}

.artist-card {
    display: block;
    color: inherit;
    text-decoration: none;
}

.artist-card__cover {
    display: block;
    aspect-ratio: var(--grid-ratio);
    overflow: hidden;
    background: color-mix(in srgb, var(--color-muted) 15%, transparent);
}

.artist-card__cover img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.artist-card:hover .artist-card__cover img {
    opacity: 0.85;
}

.artist-card__blank {
    display: block;
    height: 100%;
}

.artist-card__name {
    display: block;
    margin-top: 0.6rem;
    font-family: var(--font-heading);
    font-size: 0.9rem;
    letter-spacing: 0.04em;
}

.artist-card__tagline,
.artist-card__count {
    display: block;
    font-size: 0.72rem;
    color: var(--color-muted);
}

/* ── Prose ────────────────────────────────────────────────────────────────── */

.prose {
    max-width: 34rem;
}

body[data-menu="top"][data-header-align="center"] .prose {
    margin: 0 auto;
}

.prose p {
    margin: 0 0 1.2rem;
}

.prose__contact {
    margin-top: 2rem;
}

.prose__links {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    margin: 1.5rem 0 0;
    padding: 0;
    list-style: none;
}

.prose__links a {
    font-size: 0.8rem;
    letter-spacing: 0.06em;
    color: var(--color-muted);
}

.prose__links a:hover {
    color: var(--color-text);
}

/* ── Responsive ───────────────────────────────────────────────────────────── */

@media (max-width: 800px) {
    /* A side bar becomes a top bar: there is no room beside the work. */
    body[data-menu="left"] .shell,
    body[data-menu="right"] .shell {
        grid-template-columns: 1fr;
        gap: 1.5rem;
    }

    body[data-menu="left"] .site-header,
    body[data-menu="right"] .site-header {
        position: static;
        min-height: 0;
        padding: 2rem 0 1rem;
    }

    body[data-menu="left"] .site-main,
    body[data-menu="right"] .site-main {
        padding-top: 0;
    }

    body[data-menu="right"] .site-header {
        order: 0;
    }

    body[data-menu="left"] .site-nav,
    body[data-menu="right"] .site-nav {
        flex-direction: row;
        align-items: flex-start;
    }

    body[data-grid="grid"] .grid,
    body[data-grid="grid"] .sequence,
    .albums,
    .directory {
        grid-template-columns: repeat(2, 1fr);
    }

    body[data-grid="masonry"] .grid,
    body[data-grid="masonry"] .sequence {
        column-count: 2;
    }
}

@media (max-width: 480px) {
    body[data-grid="grid"] .grid,
    body[data-grid="grid"] .sequence,
    .albums,
    .directory {
        grid-template-columns: 1fr;
    }

    body[data-grid="masonry"] .grid,
    body[data-grid="masonry"] .sequence {
        column-count: 1;
    }
}
